Compare commits
161 Commits
layout-fix
...
vertex_spi
Author | SHA1 | Date | |
---|---|---|---|
5cdc08f6ea | |||
8779cb7785 | |||
a6ca7dca61 | |||
cce6a79a91 | |||
230a463a39 | |||
83e734bd6a | |||
72ee29669a | |||
b1a02e1710 | |||
2c34f41747 | |||
60d59730a9 | |||
850ec1f8b8 | |||
3da6c25fd8 | |||
0e987959a6 | |||
410b8b8809 | |||
d3392ae0b1 | |||
98e0ecf6a7 | |||
ad45b9880d | |||
0d1646e4df | |||
96f0746ab9 | |||
09dcd48257 | |||
62fc1f835e | |||
3b351c33d1 | |||
793485d201 | |||
3ef5ab7323 | |||
618c80c803 | |||
f7cb308243 | |||
b8583f9af3 | |||
33bf2b7c2d | |||
d48e6c04ce | |||
88f34a7d69 | |||
c8e9b465e2 | |||
278198f5f5 | |||
58718e6bd6 | |||
a814b21693 | |||
5478a4d634 | |||
8f87586495 | |||
7e3a0f524c | |||
922019cc22 | |||
41e9cdb645 | |||
e90add52d2 | |||
3c6ca2cc82 | |||
d1039d9a81 | |||
8b8cee1a5a | |||
5dc92cd72b | |||
d1503605a7 | |||
6426f7a319 | |||
3b9ed5234d | |||
763127605e | |||
b86b19d366 | |||
3dd74c69c5 | |||
01af8e3f2c | |||
474cccda33 | |||
6057b18172 | |||
6a4ff8fa24 | |||
3c79360fd3 | |||
939aafed40 | |||
23417787f8 | |||
8946c1a7de | |||
5fe910b18f | |||
3944cbdc19 | |||
8ac3dd1840 | |||
ff34287e4b | |||
81f2a0eaa1 | |||
f11715a4f4 | |||
89c51371f7 | |||
8076d893db | |||
72f8d520c9 | |||
f9274f8b9a | |||
3c09c03180 | |||
52251e3908 | |||
921444c2c9 | |||
89d234f642 | |||
13771b805b | |||
2a71059490 | |||
053221f155 | |||
4868c361e7 | |||
0c30dbf33e | |||
53370e81e2 | |||
b3fb260c84 | |||
599ca7caf7 | |||
abc0fd5e7b | |||
309b25d201 | |||
9ac7ef20b0 | |||
ebade3594d | |||
dca159d79f | |||
7007d5822a | |||
6f0fdf037f | |||
58621b0eb6 | |||
e8eef5c586 | |||
2b37997a95 | |||
558062efd7 | |||
5e880a4f26 | |||
238956f773 | |||
a5351bc596 | |||
5b6e99b194 | |||
3f3b4a2802 | |||
c19e8d36c9 | |||
2ec31f404b | |||
28a2805450 | |||
2601a1df6c | |||
96d5bb553b | |||
b4d0f442c8 | |||
2a68cab7d6 | |||
009d73fdf6 | |||
42af22f8fd | |||
b85d15b035 | |||
130e376c0c | |||
f884986257 | |||
e23dc3efb1 | |||
dd5e95d7c6 | |||
b72289eadd | |||
7a5d4f03da | |||
18fa277c71 | |||
73cc764091 | |||
9dea514d45 | |||
0bfaa035b9 | |||
85df778785 | |||
e54a92c252 | |||
9145d4cec8 | |||
c611592db6 | |||
6aba809da8 | |||
f0449d79fd | |||
33481ada7f | |||
67195974e7 | |||
70c2376fd0 | |||
177c7de4f9 | |||
eea914ba84 | |||
62e88fbeb3 | |||
5ce27d8341 | |||
eaf62eb635 | |||
9675811bbe | |||
945faf8e92 | |||
9403049671 | |||
40159d9779 | |||
f63653a5b9 | |||
c71dbb5d19 | |||
0f4df2c012 | |||
c6fc4f5a87 | |||
916afa194d | |||
6f2cd11a85 | |||
14652d52bc | |||
a57ee7cdf2 | |||
dbd3e6c29b | |||
665cbca17c | |||
efb9e9f40f | |||
8d35118f63 | |||
553c85456e | |||
68ca206d53 | |||
e30e977140 | |||
f13738d252 | |||
04b927ab7f | |||
993d172de9 | |||
695447611e | |||
06bacfbd72 | |||
cf8bc35d46 | |||
ef859bab84 | |||
a2d0669562 | |||
95365ad6ba | |||
1963b649e8 | |||
db7cdb741c | |||
0fe61ba040 |
@ -3,7 +3,7 @@
|
||||
brew update
|
||||
brew unlink python@2 || true
|
||||
rm '/usr/local/bin/2to3' || true
|
||||
brew install qt5 p7zip ccache ninja || true
|
||||
brew install qt5 molten-vk glslang vulkan-loader p7zip ccache ninja || true
|
||||
pip3 install macpack
|
||||
|
||||
export SDL_VER=2.0.16
|
||||
|
@ -12,20 +12,37 @@ cp build/bin/Release/citra "$REV_NAME"
|
||||
cp -r build/bin/Release/citra-qt.app "$REV_NAME"
|
||||
cp build/bin/Release/citra-room "$REV_NAME"
|
||||
|
||||
# move libs into folder for deployment
|
||||
macpack "${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt" -d "../Frameworks"
|
||||
# move qt frameworks into app bundle for deployment
|
||||
$(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" -executable="${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt"
|
||||
BUNDLE_PATH="$REV_NAME/citra-qt.app"
|
||||
BUNDLE_CONTENTS_PATH="$BUNDLE_PATH/Contents"
|
||||
BUNDLE_EXECUTABLE_PATH="$BUNDLE_CONTENTS_PATH/MacOS/citra-qt"
|
||||
BUNDLE_LIB_PATH="$BUNDLE_CONTENTS_PATH/lib"
|
||||
BUNDLE_RESOURCES_PATH="$BUNDLE_CONTENTS_PATH/Resources"
|
||||
|
||||
CITRA_STANDALONE_PATH="$REV_NAME/citra"
|
||||
|
||||
# move libs into folder for deployment
|
||||
macpack "${REV_NAME}/citra" -d "libs"
|
||||
macpack $BUNDLE_EXECUTABLE_PATH -d "../Frameworks"
|
||||
# move qt frameworks into app bundle for deployment
|
||||
$(brew --prefix)/opt/qt5/bin/macdeployqt $BUNDLE_PATH -executable=$BUNDLE_EXECUTABLE_PATH
|
||||
|
||||
# move libs into folder for deployment
|
||||
macpack $CITRA_STANDALONE_PATH -d "libs"
|
||||
|
||||
# bundle MoltenVK
|
||||
VULKAN_LOADER_PATH=$(brew --prefix vulkan-loader)
|
||||
MOLTENVK_PATH=$(brew --prefix molten-vk)
|
||||
mkdir $BUNDLE_LIB_PATH
|
||||
cp $VULKAN_LOADER_PATH/lib/libvulkan.dylib $BUNDLE_LIB_PATH
|
||||
cp $MOLTENVK_PATH/lib/libMoltenVK.dylib $BUNDLE_LIB_PATH
|
||||
cp -r $MOLTENVK_PATH/share/vulkan $BUNDLE_RESOURCES_PATH
|
||||
install_name_tool -add_rpath "@loader_path/../lib/" $BUNDLE_EXECUTABLE_PATH
|
||||
|
||||
# workaround for libc++
|
||||
install_name_tool -change @loader_path/../Frameworks/libc++.1.0.dylib /usr/lib/libc++.1.dylib "${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt"
|
||||
install_name_tool -change @loader_path/libs/libc++.1.0.dylib /usr/lib/libc++.1.dylib "${REV_NAME}/citra"
|
||||
install_name_tool -change @loader_path/../Frameworks/libc++.1.0.dylib /usr/lib/libc++.1.dylib $BUNDLE_EXECUTABLE_PATH
|
||||
install_name_tool -change @loader_path/libs/libc++.1.0.dylib /usr/lib/libc++.1.dylib $CITRA_STANDALONE_PATH
|
||||
|
||||
# Make the launching script executable
|
||||
chmod +x ${REV_NAME}/citra-qt.app/Contents/MacOS/citra-qt
|
||||
chmod +x $BUNDLE_EXECUTABLE_PATH
|
||||
|
||||
# Verify loader instructions
|
||||
find "$REV_NAME" -type f -exec otool -L {} \;
|
||||
|
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@ -95,6 +95,13 @@ jobs:
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.13"
|
||||
ENABLE_COMPATIBILITY_REPORTING: "ON"
|
||||
- name: Pack
|
||||
run: ./.ci/macos/upload.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: artifacts/
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
@ -116,6 +123,14 @@ jobs:
|
||||
shell: bash
|
||||
- name: Set up MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-components: Glslang
|
||||
vulkan-use-cache: true
|
||||
- name: Test glslangValidator
|
||||
run: glslangValidator --version
|
||||
- name: Build
|
||||
run: ./.ci/windows-msvc/build.sh
|
||||
shell: bash
|
||||
@ -150,7 +165,7 @@ jobs:
|
||||
- name: Deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ccache apksigner -y
|
||||
sudo apt-get install glslang-tools ccache apksigner -y
|
||||
- name: Build
|
||||
run: ./.ci/android/build.sh
|
||||
- name: Copy and sign artifacts
|
||||
|
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -58,3 +58,15 @@
|
||||
[submodule "sdl2"]
|
||||
path = externals/sdl2/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "vulkan-headers"]
|
||||
path = externals/vulkan-headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers
|
||||
[submodule "glslang"]
|
||||
path = externals/glslang
|
||||
url = https://github.com/KhronosGroup/glslang
|
||||
[submodule "glm"]
|
||||
path = externals/glm
|
||||
url = https://github.com/g-truc/glm
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/GPUCode/sirit
|
||||
|
@ -9,6 +9,7 @@ cmake_policy(SET CMP0069 NEW)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
include(DownloadExternals)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakeDependentOption)
|
||||
|
||||
project(citra LANGUAGES C CXX ASM)
|
||||
|
2
dist/qt_themes/colorful/style.qrc
vendored
2
dist/qt_themes/colorful/style.qrc
vendored
@ -13,6 +13,6 @@
|
||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="colorful">
|
||||
<file>style.qss</file>
|
||||
<file alias="style.qss">../default/style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
4
dist/qt_themes/colorful/style.qss
vendored
4
dist/qt_themes/colorful/style.qss
vendored
@ -1,4 +0,0 @@
|
||||
/*
|
||||
This file is intentionally left blank.
|
||||
We do not want to apply any stylesheet for colorful, only icons.
|
||||
*/
|
17
dist/qt_themes/default/default.qrc
vendored
17
dist/qt_themes/default/default.qrc
vendored
@ -1,33 +1,22 @@
|
||||
<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="16x16/connected.png">icons/16x16/connected.png</file>
|
||||
|
||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||
|
||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
|
||||
<file alias="256x256/citra.png">icons/256x256/citra.png</file>
|
||||
|
||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="default">
|
||||
<file>style.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
13
dist/qt_themes/default/style.qss
vendored
Normal file
13
dist/qt_themes/default/style.qss
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
QPushButton#GraphicsAPIStatusBarButton {
|
||||
color: #656565;
|
||||
border: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
padding: 0px 3px 0px 3px;
|
||||
text-align: center;
|
||||
min-width: 60px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
QPushButton#GraphicsAPIStatusBarButton:hover {
|
||||
border: 1px solid #76797C;
|
||||
}
|
21
dist/qt_themes/qdarkstyle/style.qss
vendored
21
dist/qt_themes/qdarkstyle/style.qss
vendored
@ -522,13 +522,12 @@ QToolButton#qt_toolbar_ext_button {
|
||||
|
||||
QPushButton {
|
||||
color: #eff0f1;
|
||||
border-width: 1px;
|
||||
border-color: #54575B;
|
||||
border-style: solid;
|
||||
padding: 6px 4px;
|
||||
border: 1px solid #54575B;
|
||||
border-radius: 2px;
|
||||
padding: 5px 0px 5px 0px;
|
||||
outline: none;
|
||||
min-width: 100px;
|
||||
min-height: 13px;
|
||||
background-color: #232629;
|
||||
}
|
||||
|
||||
@ -1237,3 +1236,17 @@ QPlainTextEdit:disabled {
|
||||
TouchScreenPreview {
|
||||
qproperty-dotHighlightColor: #3daee9;
|
||||
}
|
||||
|
||||
QPushButton#GraphicsAPIStatusBarButton {
|
||||
color: #656565;
|
||||
border: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
padding: 0px 3px 0px 3px;
|
||||
text-align: center;
|
||||
min-width: 60px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
QPushButton#GraphicsAPIStatusBarButton:hover {
|
||||
border: 1px solid #76797C;
|
||||
}
|
22
externals/CMakeLists.txt
vendored
22
externals/CMakeLists.txt
vendored
@ -60,6 +60,19 @@ endif()
|
||||
# Glad
|
||||
add_subdirectory(glad)
|
||||
|
||||
# glslang
|
||||
set(SKIP_GLSLANG_INSTALL ON)
|
||||
set(ENABLE_GLSLANG_BINARIES OFF)
|
||||
set(ENABLE_SPVREMAPPER OFF)
|
||||
set(ENABLE_CTEST OFF)
|
||||
add_subdirectory(glslang)
|
||||
|
||||
# Sirit
|
||||
add_subdirectory(sirit)
|
||||
|
||||
# glm
|
||||
add_subdirectory(glm)
|
||||
|
||||
# inih
|
||||
add_subdirectory(inih)
|
||||
|
||||
@ -154,3 +167,12 @@ if(ANDROID)
|
||||
add_subdirectory(libyuv)
|
||||
target_include_directories(yuv INTERFACE ./libyuv/include)
|
||||
endif()
|
||||
|
||||
# VMA
|
||||
add_library(vma INTERFACE)
|
||||
target_include_directories(vma INTERFACE ./vma)
|
||||
|
||||
# vulkan-headers
|
||||
add_library(vulkan-headers INTERFACE)
|
||||
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
|
||||
|
||||
|
1862
externals/glad/include/glad/glad.h
vendored
1862
externals/glad/include/glad/glad.h
vendored
File diff suppressed because it is too large
Load Diff
1037
externals/glad/src/glad.c
vendored
1037
externals/glad/src/glad.c
vendored
File diff suppressed because it is too large
Load Diff
1
externals/glm
vendored
Submodule
1
externals/glm
vendored
Submodule
Submodule externals/glm added at cc98465e35
1
externals/glslang
vendored
Submodule
1
externals/glslang
vendored
Submodule
Submodule externals/glslang added at c0cf8ad876
2
externals/sdl2/CMakeLists.txt
vendored
2
externals/sdl2/CMakeLists.txt
vendored
@ -39,9 +39,9 @@ set(SDL_JOYSTICK ON CACHE BOOL "")
|
||||
set(SDL_HAPTIC OFF CACHE BOOL "")
|
||||
set(SDL_HIDAPI ON CACHE BOOL "")
|
||||
set(SDL_POWER OFF CACHE BOOL "")
|
||||
set(SDL_THREADS ON CACHE BOOL "")
|
||||
set(SDL_TIMERS ON CACHE BOOL "")
|
||||
set(SDL_FILE ON CACHE BOOL "")
|
||||
set(SDL_THREADS ON CACHE BOOL "")
|
||||
set(SDL_LOADSO ON CACHE BOOL "")
|
||||
set(SDL_CPUINFO OFF CACHE BOOL "")
|
||||
set(SDL_FILESYSTEM OFF CACHE BOOL "")
|
||||
|
1
externals/sirit
vendored
Submodule
1
externals/sirit
vendored
Submodule
Submodule externals/sirit added at f0b6bbe55b
19670
externals/vma/vk_mem_alloc.h
vendored
Normal file
19670
externals/vma/vk_mem_alloc.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
externals/vulkan-headers
vendored
Submodule
1
externals/vulkan-headers
vendored
Submodule
Submodule externals/vulkan-headers added at 98f440ce68
@ -122,6 +122,7 @@ else()
|
||||
|
||||
if (MINGW)
|
||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||
add_compile_options("-Wa,-mbig-obj")
|
||||
if (COMPILE_WITH_DWARF)
|
||||
add_compile_options("-gdwarf")
|
||||
endif()
|
||||
|
@ -30,7 +30,8 @@
|
||||
android:supportsRtl="true"
|
||||
android:isGame="true"
|
||||
android:banner="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:debuggable="true">
|
||||
|
||||
<activity
|
||||
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
||||
|
@ -19,6 +19,8 @@ add_library(citra-android SHARED
|
||||
default_ini.h
|
||||
emu_window/emu_window.cpp
|
||||
emu_window/emu_window.h
|
||||
emu_window/emu_window_vk.cpp
|
||||
emu_window/emu_window_vk.h
|
||||
game_info.cpp
|
||||
game_info.h
|
||||
game_settings.cpp
|
||||
|
@ -114,7 +114,13 @@ void Config::ReadValues() {
|
||||
sdl2_config->GetString("Premium", "texture_filter_name", "none");
|
||||
|
||||
// Renderer
|
||||
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", true);
|
||||
Settings::values.graphics_api =
|
||||
static_cast<Settings::GraphicsAPI>(sdl2_config->GetInteger("Renderer", "graphics_api", 2));
|
||||
Settings::values.async_command_recording =
|
||||
sdl2_config->GetBoolean("Renderer", "async_command_recording", true);
|
||||
Settings::values.spirv_shader_gen =
|
||||
sdl2_config->GetBoolean("Renderer", "spirv_shader_gen", true);
|
||||
Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "renderer_debug", false);
|
||||
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
||||
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
|
||||
Settings::values.shaders_accurate_mul =
|
||||
|
176
src/android/app/src/main/jni/emu_window/emu_window_vk.cpp
Normal file
176
src/android/app/src/main/jni/emu_window/emu_window_vk.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <android/native_window_jni.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/main.h"
|
||||
#include "jni/emu_window/emu_window_vk.h"
|
||||
#include "jni/id_cache.h"
|
||||
#include "jni/input_manager.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
static bool IsPortraitMode() {
|
||||
return JNI_FALSE != IDCache::GetEnvForThread()->CallStaticBooleanMethod(
|
||||
IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode());
|
||||
}
|
||||
|
||||
static void UpdateLandscapeScreenLayout() {
|
||||
Settings::values.layout_option =
|
||||
static_cast<Settings::LayoutOption>(IDCache::GetEnvForThread()->CallStaticIntMethod(
|
||||
IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout()));
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
render_window = surface;
|
||||
StopPresenting();
|
||||
}
|
||||
|
||||
bool EmuWindow_Android_Vulkan::OnTouchEvent(int x, int y, bool pressed) {
|
||||
if (pressed) {
|
||||
return TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
||||
}
|
||||
|
||||
TouchReleased();
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::OnTouchMoved(int x, int y) {
|
||||
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::OnFramebufferSizeChanged() {
|
||||
UpdateLandscapeScreenLayout();
|
||||
const bool is_portrait_mode{IsPortraitMode()};
|
||||
const int bigger{window_width > window_height ? window_width : window_height};
|
||||
const int smaller{window_width < window_height ? window_width : window_height};
|
||||
if (is_portrait_mode) {
|
||||
UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode);
|
||||
} else {
|
||||
UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode);
|
||||
}
|
||||
}
|
||||
|
||||
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(ANativeWindow* surface) {
|
||||
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android_Vulkan");
|
||||
|
||||
if (!surface) {
|
||||
LOG_CRITICAL(Frontend, "surface is nullptr");
|
||||
return;
|
||||
}
|
||||
|
||||
Network::Init();
|
||||
|
||||
host_window = surface;
|
||||
CreateWindowSurface();
|
||||
|
||||
if (core_context = CreateSharedContext(); !core_context) {
|
||||
LOG_CRITICAL(Frontend, "CreateSharedContext() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
OnFramebufferSizeChanged();
|
||||
}
|
||||
|
||||
bool EmuWindow_Android_Vulkan::CreateWindowSurface() {
|
||||
if (!host_window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
window_info.type = Frontend::WindowSystemType::Android;
|
||||
window_info.render_surface = host_window;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::DestroyWindowSurface() {
|
||||
/*if (!egl_surface) {
|
||||
return;
|
||||
}
|
||||
if (eglGetCurrentSurface(EGL_DRAW) == egl_surface) {
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
if (!eglDestroySurface(egl_display, egl_surface)) {
|
||||
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
|
||||
}
|
||||
egl_surface = EGL_NO_SURFACE;*/
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::DestroyContext() {
|
||||
/*if (!egl_context) {
|
||||
return;
|
||||
}
|
||||
if (eglGetCurrentContext() == egl_context) {
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
if (!eglDestroyContext(egl_display, egl_context)) {
|
||||
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
|
||||
}
|
||||
if (!eglTerminate(egl_display)) {
|
||||
LOG_CRITICAL(Frontend, "eglTerminate() failed");
|
||||
}
|
||||
egl_context = EGL_NO_CONTEXT;
|
||||
egl_display = EGL_NO_DISPLAY;*/
|
||||
}
|
||||
|
||||
EmuWindow_Android_Vulkan::~EmuWindow_Android_Vulkan() {
|
||||
DestroyWindowSurface();
|
||||
DestroyContext();
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_Vulkan::CreateSharedContext() const {
|
||||
return std::make_unique<SharedContext_Android>();
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::StopPresenting() {
|
||||
/*if (presenting_state == PresentingState::Running) {
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}*/
|
||||
presenting_state = PresentingState::Stopped;
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::TryPresenting() {
|
||||
if (presenting_state != PresentingState::Running) {
|
||||
if (presenting_state == PresentingState::Initial) {
|
||||
/*eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);*/
|
||||
presenting_state = PresentingState::Running;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/*eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0);
|
||||
if (VideoCore::g_renderer) {
|
||||
VideoCore::g_renderer->TryPresent(0);
|
||||
eglSwapBuffers(egl_display, egl_surface);
|
||||
}*/
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::PollEvents() {
|
||||
if (!render_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
host_window = render_window;
|
||||
render_window = nullptr;
|
||||
|
||||
DestroyWindowSurface();
|
||||
CreateWindowSurface();
|
||||
OnFramebufferSizeChanged();
|
||||
presenting_state = PresentingState::Initial;
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::MakeCurrent() {
|
||||
core_context->MakeCurrent();
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::DoneCurrent() {
|
||||
core_context->DoneCurrent();
|
||||
}
|
59
src/android/app/src/main/jni/emu_window/emu_window_vk.h
Normal file
59
src/android/app/src/main/jni/emu_window/emu_window_vk.h
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "core/frontend/emu_window.h"
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
class SharedContext_Android : public Frontend::GraphicsContext {};
|
||||
|
||||
class EmuWindow_Android_Vulkan : public Frontend::EmuWindow {
|
||||
public:
|
||||
EmuWindow_Android_Vulkan(ANativeWindow* surface);
|
||||
~EmuWindow_Android_Vulkan();
|
||||
|
||||
void Present();
|
||||
|
||||
/// Called by the onSurfaceChanges() method to change the surface
|
||||
void OnSurfaceChanged(ANativeWindow* surface);
|
||||
|
||||
/// Handles touch event that occur.(Touched or released)
|
||||
bool OnTouchEvent(int x, int y, bool pressed);
|
||||
|
||||
/// Handles movement of touch pointer
|
||||
void OnTouchMoved(int x, int y);
|
||||
|
||||
void PollEvents() override;
|
||||
void MakeCurrent() override;
|
||||
void DoneCurrent() override;
|
||||
|
||||
void TryPresenting();
|
||||
void StopPresenting();
|
||||
|
||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
private:
|
||||
void OnFramebufferSizeChanged();
|
||||
bool CreateWindowSurface();
|
||||
void DestroyWindowSurface();
|
||||
void DestroyContext();
|
||||
|
||||
ANativeWindow* render_window{};
|
||||
ANativeWindow* host_window{};
|
||||
|
||||
int window_width{1080};
|
||||
int window_height{2220};
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> core_context;
|
||||
|
||||
enum class PresentingState {
|
||||
Initial,
|
||||
Running,
|
||||
Stopped,
|
||||
};
|
||||
PresentingState presenting_state{};
|
||||
};
|
@ -23,7 +23,6 @@
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/camera/factory.h"
|
||||
#include "core/frontend/mic.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/savestate.h"
|
||||
@ -33,7 +32,7 @@
|
||||
#include "jni/camera/ndk_camera.h"
|
||||
#include "jni/camera/still_image_camera.h"
|
||||
#include "jni/config.h"
|
||||
#include "jni/emu_window/emu_window.h"
|
||||
#include "jni/emu_window/emu_window_vk.h"
|
||||
#include "jni/game_info.h"
|
||||
#include "jni/game_settings.h"
|
||||
#include "jni/id_cache.h"
|
||||
@ -49,7 +48,7 @@ namespace {
|
||||
|
||||
ANativeWindow* s_surf;
|
||||
|
||||
std::unique_ptr<EmuWindow_Android> window;
|
||||
std::unique_ptr<EmuWindow_Android_Vulkan> window;
|
||||
|
||||
std::atomic<bool> stop_run{true};
|
||||
std::atomic<bool> pause_emulation{false};
|
||||
@ -147,7 +146,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||
return Core::System::ResultStatus::ErrorLoader;
|
||||
}
|
||||
|
||||
window = std::make_unique<EmuWindow_Android>(s_surf);
|
||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf);
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
src/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so
Normal file
BIN
src/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so
Normal file
Binary file not shown.
@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -7,10 +7,7 @@
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
// This needs to be included before getopt.h because the latter #defines symbols used by it
|
||||
#include "common/microprofile.h"
|
||||
|
||||
#include "citra/config.h"
|
||||
#include "citra/emu_window/emu_window_sdl2.h"
|
||||
#include "citra/lodepng_image_interface.h"
|
||||
@ -18,22 +15,18 @@
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/dumping/backend.h"
|
||||
#include "core/file_sys/cia_container.h"
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/movie.h"
|
||||
#include "input_common/main.h"
|
||||
#include "network/network.h"
|
||||
@ -368,7 +361,7 @@ int main(int argc, char** argv) {
|
||||
const auto secondary_window =
|
||||
use_secondary_window ? std::make_unique<EmuWindow_SDL2>(false, true) : nullptr;
|
||||
|
||||
Frontend::ScopeAcquireContext scope(*emu_window);
|
||||
const auto scope = emu_window->Acquire();
|
||||
|
||||
LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
|
@ -109,7 +109,8 @@ void Config::ReadValues() {
|
||||
sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
|
||||
|
||||
// Renderer
|
||||
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);
|
||||
Settings::values.graphics_api =
|
||||
static_cast<Settings::GraphicsAPI>(sdl2_config->GetInteger("Renderer", "graphics_api", 0));
|
||||
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
||||
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
|
||||
#ifdef __APPLE__
|
||||
|
@ -137,7 +137,9 @@ void EmuWindow_SDL2::Fullscreen() {
|
||||
|
||||
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(is_secondary) {
|
||||
// Initialize the window
|
||||
if (Settings::values.use_gles) {
|
||||
const bool is_opengles =
|
||||
Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::OpenGLES;
|
||||
if (is_opengles) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
@ -192,7 +194,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(i
|
||||
}
|
||||
|
||||
render_window_id = SDL_GetWindowID(render_window);
|
||||
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
auto gl_load_func = is_opengles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
|
||||
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
|
||||
|
@ -250,6 +250,8 @@ if (APPLE)
|
||||
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||
target_sources(citra-qt PRIVATE
|
||||
applesurfacehelper.h
|
||||
applesurfacehelper.mm
|
||||
macos_authorization.h
|
||||
macos_authorization.mm
|
||||
)
|
||||
@ -266,9 +268,13 @@ endif()
|
||||
create_target_directory_groups(citra-qt)
|
||||
|
||||
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
|
||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia)
|
||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia)
|
||||
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
if (NOT WIN32)
|
||||
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
target_compile_definitions(citra-qt PRIVATE
|
||||
# Use QStringBuilder for string concatenation to reduce
|
||||
# the overall number of temporary strings created.
|
||||
|
11
src/citra_qt/applesurfacehelper.h
Normal file
11
src/citra_qt/applesurfacehelper.h
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace AppleSurfaceHelper {
|
||||
|
||||
void* GetSurfaceLayer(void* surface);
|
||||
|
||||
} // namespace AppleSurfaceHelper
|
16
src/citra_qt/applesurfacehelper.mm
Normal file
16
src/citra_qt/applesurfacehelper.mm
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "citra_qt/applesurfacehelper.h"
|
||||
|
||||
namespace AppleSurfaceHelper {
|
||||
|
||||
void* GetSurfaceLayer(void* surface) {
|
||||
NSView* view = static_cast<NSView*>(surface);
|
||||
return view.layer;
|
||||
}
|
||||
|
||||
} // AppleSurfaceHelper
|
@ -6,10 +6,10 @@
|
||||
#include <QDragEnterEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QMessageBox>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLFunctions_4_3_Core>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
#include <fmt/format.h>
|
||||
#include "citra_qt/bootmanager.h"
|
||||
#include "citra_qt/main.h"
|
||||
@ -18,15 +18,20 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/3ds.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_emu.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include "citra_qt/applesurfacehelper.h"
|
||||
#endif
|
||||
|
||||
#if !defined(WIN32)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
|
||||
|
||||
EmuThread::~EmuThread() = default;
|
||||
@ -44,7 +49,7 @@ static GMainWindow* GetMainWindow() {
|
||||
|
||||
void EmuThread::run() {
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
Frontend::ScopeAcquireContext scope(core_context);
|
||||
const auto scope = core_context.Acquire();
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
|
||||
@ -55,6 +60,7 @@ void EmuThread::run() {
|
||||
});
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||
emit HideLoadingScreen();
|
||||
|
||||
core_context.MakeCurrent();
|
||||
|
||||
@ -113,88 +119,242 @@ void EmuThread::run() {
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
|
||||
bool is_secondary)
|
||||
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
||||
event_handler(event_handler), is_secondary{is_secondary} {
|
||||
class OpenGLSharedContext : public Frontend::GraphicsContext {
|
||||
public:
|
||||
/// Create the original context that should be shared from
|
||||
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
|
||||
QSurfaceFormat format;
|
||||
|
||||
// disable vsync for any shared contexts
|
||||
auto format = shared_context->format();
|
||||
format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0);
|
||||
this->setFormat(format);
|
||||
format.setVersion(4, 4);
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
|
||||
context->setShareContext(shared_context);
|
||||
context->setScreen(this->screen());
|
||||
context->setFormat(format);
|
||||
context->create();
|
||||
if (Settings::values.renderer_debug) {
|
||||
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
|
||||
}
|
||||
|
||||
setSurfaceType(QWindow::OpenGLSurface);
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
format.setSwapInterval(0);
|
||||
|
||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||
}
|
||||
|
||||
OpenGLWindow::~OpenGLWindow() {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
||||
void OpenGLWindow::Present() {
|
||||
if (!isExposed())
|
||||
return;
|
||||
|
||||
context->makeCurrent(this);
|
||||
if (VideoCore::g_renderer) {
|
||||
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create main openGL context");
|
||||
}
|
||||
}
|
||||
context->swapBuffers(this);
|
||||
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
|
||||
f->glFinish();
|
||||
QWindow::requestUpdate();
|
||||
}
|
||||
|
||||
bool OpenGLWindow::event(QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::UpdateRequest:
|
||||
/// Create the shared contexts for rendering and presentation
|
||||
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
|
||||
|
||||
// disable vsync for any shared contexts
|
||||
auto format = share_context->format();
|
||||
format.setSwapInterval(main_surface ? Settings::values.use_vsync_new.GetValue() : 0);
|
||||
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setShareContext(share_context);
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create shared openGL context");
|
||||
}
|
||||
|
||||
if (!main_surface) {
|
||||
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
|
||||
offscreen_surface->setFormat(format);
|
||||
offscreen_surface->create();
|
||||
surface = offscreen_surface.get();
|
||||
} else {
|
||||
surface = main_surface;
|
||||
}
|
||||
}
|
||||
|
||||
~OpenGLSharedContext() {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
context->swapBuffers(surface);
|
||||
}
|
||||
|
||||
void MakeCurrent() override {
|
||||
// We can't track the current state of the underlying context in this wrapper class because
|
||||
// Qt may make the underlying context not current for one reason or another. In particular,
|
||||
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
|
||||
// Instead of always just making the context current (which does not have any caching to
|
||||
// check if the underlying context is already current) we can check for the current context
|
||||
// in the thread local data by calling `currentContext()` and checking if its ours.
|
||||
if (QOpenGLContext::currentContext() != context.get()) {
|
||||
context->makeCurrent(surface);
|
||||
}
|
||||
}
|
||||
|
||||
void DoneCurrent() override {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
||||
QOpenGLContext* GetShareContext() const {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
private:
|
||||
// Avoid using Qt parent system here since we might move the QObjects to new threads
|
||||
// As a note, this means we should avoid using slots/signals with the objects too
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
|
||||
QSurface* surface;
|
||||
};
|
||||
|
||||
class DummyContext : public Frontend::GraphicsContext {};
|
||||
|
||||
class RenderWidget : public QWidget {
|
||||
public:
|
||||
RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
|
||||
setAttribute(Qt::WA_NativeWindow);
|
||||
setAttribute(Qt::WA_PaintOnScreen);
|
||||
}
|
||||
|
||||
virtual ~RenderWidget() = default;
|
||||
|
||||
virtual void Present() {}
|
||||
|
||||
void paintEvent(QPaintEvent* event) override {
|
||||
Present();
|
||||
return true;
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::FocusIn:
|
||||
case QEvent::FocusOut:
|
||||
case QEvent::FocusAboutToChange:
|
||||
case QEvent::Enter:
|
||||
case QEvent::Leave:
|
||||
case QEvent::Wheel:
|
||||
case QEvent::TabletMove:
|
||||
case QEvent::TabletPress:
|
||||
case QEvent::TabletRelease:
|
||||
case QEvent::TabletEnterProximity:
|
||||
case QEvent::TabletLeaveProximity:
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchUpdate:
|
||||
case QEvent::TouchEnd:
|
||||
case QEvent::InputMethodQuery:
|
||||
case QEvent::TouchCancel:
|
||||
return QCoreApplication::sendEvent(event_handler, event);
|
||||
case QEvent::Drop:
|
||||
GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
|
||||
return true;
|
||||
case QEvent::DragEnter:
|
||||
case QEvent::DragMove:
|
||||
GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
|
||||
return true;
|
||||
default:
|
||||
return QWindow::event(event);
|
||||
update();
|
||||
}
|
||||
|
||||
void resizeEvent(QResizeEvent* ev) override {
|
||||
render_window->resize(ev->size());
|
||||
render_window->OnFramebufferSizeChanged();
|
||||
}
|
||||
|
||||
void keyPressEvent(QKeyEvent* event) override {
|
||||
InputCommon::GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
|
||||
void keyReleaseEvent(QKeyEvent* event) override {
|
||||
InputCommon::GetKeyboard()->ReleaseKey(event->key());
|
||||
}
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) override {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchBeginEvent
|
||||
|
||||
const auto pos{event->pos()};
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
const auto [x, y] = render_window->ScaleTouch(pos);
|
||||
render_window->TouchPressed(x, y);
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
|
||||
}
|
||||
}
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event) override {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchUpdateEvent
|
||||
|
||||
const auto pos{event->pos()};
|
||||
const auto [x, y] = render_window->ScaleTouch(pos);
|
||||
render_window->TouchMoved(x, y);
|
||||
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
|
||||
}
|
||||
|
||||
void mouseReleaseEvent(QMouseEvent* event) override {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchEndEvent
|
||||
|
||||
if (event->button() == Qt::LeftButton)
|
||||
render_window->TouchReleased();
|
||||
else if (event->button() == Qt::RightButton)
|
||||
InputCommon::GetMotionEmu()->EndTilt();
|
||||
}
|
||||
|
||||
std::pair<unsigned, unsigned> GetSize() const {
|
||||
return std::make_pair(width(), height());
|
||||
}
|
||||
|
||||
QPaintEngine* paintEngine() const override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
GRenderWindow* render_window;
|
||||
};
|
||||
|
||||
class OpenGLRenderWidget : public RenderWidget {
|
||||
public:
|
||||
explicit OpenGLRenderWidget(GRenderWindow* parent, bool is_secondary)
|
||||
: RenderWidget(parent), is_secondary(is_secondary) {
|
||||
windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
|
||||
}
|
||||
|
||||
void SetContext(std::unique_ptr<OpenGLSharedContext>&& context_) {
|
||||
context = std::move(context_);
|
||||
}
|
||||
|
||||
void Present() override {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (!Core::System::GetInstance().IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
context->MakeCurrent();
|
||||
const auto f = context->GetShareContext()->extraFunctions();
|
||||
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||
context->SwapBuffers();
|
||||
f->glFinish();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<OpenGLSharedContext> context{};
|
||||
bool is_secondary;
|
||||
};
|
||||
|
||||
class VulkanRenderWidget : public RenderWidget {
|
||||
public:
|
||||
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
|
||||
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
|
||||
}
|
||||
};
|
||||
|
||||
static Frontend::WindowSystemType GetWindowSystemType() {
|
||||
// Determine WSI type based on Qt platform.
|
||||
QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("windows"))
|
||||
return Frontend::WindowSystemType::Windows;
|
||||
else if (platform_name == QStringLiteral("xcb"))
|
||||
return Frontend::WindowSystemType::X11;
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
return Frontend::WindowSystemType::Wayland;
|
||||
else if (platform_name == QStringLiteral("cocoa"))
|
||||
return Frontend::WindowSystemType::MacOS;
|
||||
|
||||
LOG_CRITICAL(Frontend, "Unknown Qt platform!");
|
||||
return Frontend::WindowSystemType::Windows;
|
||||
}
|
||||
|
||||
void OpenGLWindow::exposeEvent(QExposeEvent* event) {
|
||||
QWindow::requestUpdate();
|
||||
QWindow::exposeEvent(event);
|
||||
static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
|
||||
Frontend::EmuWindow::WindowSystemInfo wsi;
|
||||
wsi.type = GetWindowSystemType();
|
||||
|
||||
// Our Win32 Qt external doesn't have the private API.
|
||||
#if defined(WIN32)
|
||||
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
|
||||
#elif defined(__APPLE__)
|
||||
wsi.render_surface =
|
||||
window ? AppleSurfaceHelper::GetSurfaceLayer(reinterpret_cast<void*>(window->winId()))
|
||||
: nullptr;
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
wsi.display_connection = pni->nativeResourceForWindow("display", window);
|
||||
if (wsi.type == Frontend::WindowSystemType::Wayland)
|
||||
wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
|
||||
else
|
||||
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
|
||||
#endif
|
||||
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
|
||||
|
||||
return wsi;
|
||||
}
|
||||
|
||||
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_secondary_)
|
||||
@ -218,11 +378,11 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_se
|
||||
GRenderWindow::~GRenderWindow() = default;
|
||||
|
||||
void GRenderWindow::MakeCurrent() {
|
||||
core_context->MakeCurrent();
|
||||
main_context->MakeCurrent();
|
||||
}
|
||||
|
||||
void GRenderWindow::DoneCurrent() {
|
||||
core_context->DoneCurrent();
|
||||
main_context->DoneCurrent();
|
||||
}
|
||||
|
||||
void GRenderWindow::PollEvents() {
|
||||
@ -393,34 +553,70 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
||||
OnFramebufferSizeChanged();
|
||||
}
|
||||
|
||||
void GRenderWindow::InitRenderTarget() {
|
||||
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
|
||||
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
|
||||
if (graphics_api == Settings::GraphicsAPI::OpenGL ||
|
||||
graphics_api == Settings::GraphicsAPI::OpenGLES) {
|
||||
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
|
||||
// Bind the shared contexts to the main surface in case the backend wants to take over
|
||||
// presentation
|
||||
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
|
||||
child_widget->windowHandle());
|
||||
}
|
||||
|
||||
return std::make_unique<DummyContext>();
|
||||
}
|
||||
|
||||
bool GRenderWindow::InitRenderTarget() {
|
||||
ReleaseRenderTarget();
|
||||
|
||||
{
|
||||
// Create a dummy render widget so that Qt
|
||||
// places the render window at the correct position.
|
||||
const RenderWidget dummy_widget{this};
|
||||
}
|
||||
|
||||
first_frame = false;
|
||||
|
||||
GMainWindow* parent = GetMainWindow();
|
||||
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
|
||||
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext(),
|
||||
is_secondary);
|
||||
child_window->create();
|
||||
child_widget = createWindowContainer(child_window, this);
|
||||
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
|
||||
switch (graphics_api) {
|
||||
case Settings::GraphicsAPI::OpenGL:
|
||||
case Settings::GraphicsAPI::OpenGLES:
|
||||
if (!InitializeOpenGL()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Settings::GraphicsAPI::Vulkan:
|
||||
if (!InitializeVulkan()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the Window System information with the new render target
|
||||
window_info = GetWindowSystemInfo(child_widget->windowHandle());
|
||||
|
||||
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||
|
||||
layout()->addWidget(child_widget);
|
||||
// Reset minimum required size to avoid resizing issues on the main window after restarting.
|
||||
setMinimumSize(1, 1);
|
||||
|
||||
core_context = CreateSharedContext();
|
||||
resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
OnFramebufferSizeChanged();
|
||||
BackupGeometry();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GRenderWindow::ReleaseRenderTarget() {
|
||||
if (child_widget) {
|
||||
layout()->removeWidget(child_widget);
|
||||
delete child_widget;
|
||||
child_widget->deleteLater();
|
||||
child_widget = nullptr;
|
||||
}
|
||||
main_context.reset();
|
||||
}
|
||||
|
||||
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
||||
@ -445,6 +641,29 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
|
||||
setMinimumSize(minimal_size.first, minimal_size.second);
|
||||
}
|
||||
|
||||
bool GRenderWindow::InitializeOpenGL() {
|
||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||
auto child = new OpenGLRenderWidget(this, is_secondary);
|
||||
child_widget = child;
|
||||
child_widget->windowHandle()->create();
|
||||
auto context = std::make_shared<OpenGLSharedContext>(child->windowHandle());
|
||||
main_context = context;
|
||||
child->SetContext(
|
||||
std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GRenderWindow::InitializeVulkan() {
|
||||
auto child = new VulkanRenderWidget(this);
|
||||
child_widget = child;
|
||||
child_widget->windowHandle()->create();
|
||||
main_context = std::make_unique<DummyContext>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
||||
this->emu_thread = emu_thread;
|
||||
}
|
||||
@ -456,31 +675,3 @@ void GRenderWindow::OnEmulationStopping() {
|
||||
void GRenderWindow::showEvent(QShowEvent* event) {
|
||||
QWidget::showEvent(event);
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
|
||||
return std::make_unique<GLContext>(QOpenGLContext::globalShareContext());
|
||||
}
|
||||
|
||||
GLContext::GLContext(QOpenGLContext* shared_context)
|
||||
: context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
||||
surface(std::make_unique<QOffscreenSurface>(nullptr)) {
|
||||
|
||||
// disable vsync for any shared contexts
|
||||
auto format = shared_context->format();
|
||||
format.setSwapInterval(0);
|
||||
|
||||
context->setShareContext(shared_context);
|
||||
context->setFormat(format);
|
||||
context->create();
|
||||
surface->setParent(shared_context->parent());
|
||||
surface->setFormat(format);
|
||||
surface->create();
|
||||
}
|
||||
|
||||
void GLContext::MakeCurrent() {
|
||||
context->makeCurrent(surface.get());
|
||||
}
|
||||
|
||||
void GLContext::DoneCurrent() {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
@ -27,19 +27,6 @@ namespace VideoCore {
|
||||
enum class LoadCallbackStage;
|
||||
}
|
||||
|
||||
class GLContext : public Frontend::GraphicsContext {
|
||||
public:
|
||||
explicit GLContext(QOpenGLContext* shared_context);
|
||||
|
||||
void MakeCurrent() override;
|
||||
|
||||
void DoneCurrent() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
std::unique_ptr<QOffscreenSurface> surface;
|
||||
};
|
||||
|
||||
class EmuThread final : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
@ -126,26 +113,6 @@ signals:
|
||||
void HideLoadingScreen();
|
||||
};
|
||||
|
||||
class OpenGLWindow : public QWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
|
||||
bool is_secondary = false);
|
||||
|
||||
~OpenGLWindow();
|
||||
|
||||
void Present();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
void exposeEvent(QExposeEvent* event) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
QWidget* event_handler;
|
||||
bool is_secondary;
|
||||
};
|
||||
|
||||
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
|
||||
Q_OBJECT
|
||||
|
||||
@ -185,13 +152,15 @@ public:
|
||||
return has_focus;
|
||||
}
|
||||
|
||||
void InitRenderTarget();
|
||||
bool InitRenderTarget();
|
||||
|
||||
/// Destroy the previous run's child_widget which should also destroy the child_window
|
||||
void ReleaseRenderTarget();
|
||||
|
||||
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
|
||||
|
||||
std::pair<u32, u32> ScaleTouch(const QPointF pos) const;
|
||||
|
||||
public slots:
|
||||
|
||||
void OnEmulationStarting(EmuThread* emu_thread);
|
||||
@ -211,29 +180,29 @@ signals:
|
||||
void MouseActivity();
|
||||
|
||||
private:
|
||||
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
|
||||
void TouchBeginEvent(const QTouchEvent* event);
|
||||
void TouchUpdateEvent(const QTouchEvent* event);
|
||||
void TouchEndEvent();
|
||||
|
||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||
|
||||
std::unique_ptr<GraphicsContext> core_context;
|
||||
|
||||
QByteArray geometry;
|
||||
|
||||
/// Native window handle that backs this presentation widget
|
||||
QWindow* child_window = nullptr;
|
||||
|
||||
/// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
|
||||
/// put the child_window into a widget then add it to the layout. This child_widget can be
|
||||
/// parented to GRenderWindow and use Qt's lifetime system
|
||||
QWidget* child_widget = nullptr;
|
||||
bool InitializeOpenGL();
|
||||
bool InitializeVulkan();
|
||||
|
||||
EmuThread* emu_thread;
|
||||
|
||||
// Main context that will be shared with all other contexts that are requested.
|
||||
// If this is used in a shared context setting, then this should not be used directly, but
|
||||
// should instead be shared from
|
||||
std::shared_ptr<Frontend::GraphicsContext> main_context;
|
||||
|
||||
/// Temporary storage of the screenshot taken
|
||||
QImage screenshot_image;
|
||||
|
||||
QByteArray geometry;
|
||||
|
||||
QWidget* child_widget = nullptr;
|
||||
|
||||
bool first_frame = false;
|
||||
bool has_focus = false;
|
||||
|
||||
|
@ -455,6 +455,8 @@ void Config::ReadDebuggingValues() {
|
||||
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
||||
ReadBasicSetting(Settings::values.use_gdbstub);
|
||||
ReadBasicSetting(Settings::values.gdbstub_port);
|
||||
ReadBasicSetting(Settings::values.renderer_debug);
|
||||
ReadBasicSetting(Settings::values.dump_command_buffers);
|
||||
|
||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||
for (const auto& service_module : Service::service_module_map) {
|
||||
@ -601,6 +603,10 @@ void Config::ReadPathValues() {
|
||||
void Config::ReadRendererValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Renderer"));
|
||||
|
||||
ReadGlobalSetting(Settings::values.physical_device);
|
||||
ReadGlobalSetting(Settings::values.async_command_recording);
|
||||
ReadGlobalSetting(Settings::values.spirv_shader_gen);
|
||||
ReadGlobalSetting(Settings::values.graphics_api);
|
||||
ReadGlobalSetting(Settings::values.use_hw_renderer);
|
||||
ReadGlobalSetting(Settings::values.use_hw_shader);
|
||||
#ifdef __APPLE__
|
||||
@ -965,6 +971,8 @@ void Config::SaveDebuggingValues() {
|
||||
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
||||
WriteBasicSetting(Settings::values.use_gdbstub);
|
||||
WriteBasicSetting(Settings::values.gdbstub_port);
|
||||
WriteBasicSetting(Settings::values.renderer_debug);
|
||||
WriteBasicSetting(Settings::values.dump_command_buffers);
|
||||
|
||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||
for (const auto& service_module : Settings::values.lle_modules) {
|
||||
@ -1077,6 +1085,10 @@ void Config::SavePathValues() {
|
||||
void Config::SaveRendererValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Renderer"));
|
||||
|
||||
WriteGlobalSetting(Settings::values.graphics_api);
|
||||
WriteGlobalSetting(Settings::values.physical_device);
|
||||
WriteGlobalSetting(Settings::values.async_command_recording);
|
||||
WriteGlobalSetting(Settings::values.spirv_shader_gen);
|
||||
WriteGlobalSetting(Settings::values.use_hw_renderer);
|
||||
WriteGlobalSetting(Settings::values.use_hw_shader);
|
||||
#ifdef __APPLE__
|
||||
|
@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QUrl>
|
||||
#include "citra_qt/configuration/configure_debug.h"
|
||||
#include "citra_qt/debugger/console.h"
|
||||
@ -11,7 +12,9 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "qcheckbox.h"
|
||||
#include "ui_configure_debug.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
|
||||
ConfigureDebug::ConfigureDebug(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureDebug>()) {
|
||||
@ -23,8 +26,40 @@ ConfigureDebug::ConfigureDebug(QWidget* parent)
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
});
|
||||
|
||||
connect(ui->toggle_renderer_debug, &QCheckBox::clicked, this, [this](bool checked) {
|
||||
if (checked && Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::Vulkan) {
|
||||
try {
|
||||
Vulkan::Instance debug_inst{true};
|
||||
} catch (vk::LayerNotPresentError&) {
|
||||
ui->toggle_renderer_debug->toggle();
|
||||
QMessageBox::warning(this, tr("Validation layer not available"),
|
||||
tr("Unable to enable debug renderer because the layer "
|
||||
"<strong>VK_LAYER_KHRONOS_validation</strong> is missing. "
|
||||
"Please install the Vulkan SDK or the appropriate package "
|
||||
"of your distribution"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->toggle_dump_command_buffers, &QCheckBox::clicked, this, [this](bool checked) {
|
||||
if (checked && Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::Vulkan) {
|
||||
try {
|
||||
Vulkan::Instance debug_inst{false, true};
|
||||
} catch (vk::LayerNotPresentError&) {
|
||||
ui->toggle_dump_command_buffers->toggle();
|
||||
QMessageBox::warning(this, tr("Command buffer dumping not available"),
|
||||
tr("Unable to enable command buffer dumping because the layer "
|
||||
"<strong>VK_LAYER_LUNARG_api_dump</strong> is missing. "
|
||||
"Please install the Vulkan SDK or the appropriate package "
|
||||
"of your distribution"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
|
||||
ui->toggle_cpu_jit->setEnabled(!is_powered_on);
|
||||
ui->toggle_renderer_debug->setEnabled(!is_powered_on);
|
||||
ui->toggle_dump_command_buffers->setEnabled(!is_powered_on);
|
||||
}
|
||||
|
||||
ConfigureDebug::~ConfigureDebug() = default;
|
||||
@ -37,6 +72,8 @@ void ConfigureDebug::SetConfiguration() {
|
||||
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
|
||||
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
|
||||
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit.GetValue());
|
||||
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
|
||||
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
|
||||
}
|
||||
|
||||
void ConfigureDebug::ApplyConfiguration() {
|
||||
@ -49,6 +86,8 @@ void ConfigureDebug::ApplyConfiguration() {
|
||||
filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||
Log::SetGlobalFilter(filter);
|
||||
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
||||
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
|
||||
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
|
||||
}
|
||||
|
||||
void ConfigureDebug::RetranslateUI() {
|
||||
|
@ -22,5 +22,6 @@ public:
|
||||
void RetranslateUI();
|
||||
void SetConfiguration();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::ConfigureDebug> ui;
|
||||
};
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>443</width>
|
||||
<height>300</height>
|
||||
<width>454</width>
|
||||
<height>356</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -122,6 +122,23 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_renderer_debug">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enables debug reporting in the currently selected graphics API. Causes measurable performance loss, don't enable unless for debugging purposes</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable debug renderer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_dump_command_buffers">
|
||||
<property name="text">
|
||||
<string>Dump command buffers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -11,17 +11,29 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "ui_configure_graphics.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
|
||||
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
DiscoverPhysicalDevices();
|
||||
SetupPerGameUI();
|
||||
SetConfiguration();
|
||||
|
||||
ui->hw_renderer_group->setEnabled(ui->hw_renderer_group->isEnabled() &&
|
||||
ui->toggle_hw_renderer->isChecked());
|
||||
ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
const bool not_running = !Core::System::GetInstance().IsPoweredOn();
|
||||
const bool hw_renderer_enabled = ui->toggle_hw_renderer->isChecked();
|
||||
ui->toggle_hw_renderer->setEnabled(not_running);
|
||||
ui->hw_renderer_group->setEnabled(hw_renderer_enabled && not_running);
|
||||
ui->toggle_vsync_new->setEnabled(not_running);
|
||||
ui->graphics_api_combo->setEnabled(not_running);
|
||||
ui->toggle_shader_jit->setEnabled(not_running);
|
||||
ui->toggle_disk_shader_cache->setEnabled(hw_renderer_enabled && not_running);
|
||||
ui->physical_device_combo->setEnabled(not_running);
|
||||
SetPhysicalDeviceComboVisibility(ui->graphics_api_combo->currentIndex());
|
||||
|
||||
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureGraphics::SetPhysicalDeviceComboVisibility);
|
||||
|
||||
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
|
||||
const bool checked = ui->toggle_hw_renderer->isChecked();
|
||||
@ -71,6 +83,12 @@ void ConfigureGraphics::SetConfiguration() {
|
||||
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue());
|
||||
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
|
||||
ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new.GetValue());
|
||||
ui->graphics_api_combo->setCurrentIndex(
|
||||
static_cast<int>(Settings::values.graphics_api.GetValue()));
|
||||
ui->physical_device_combo->setCurrentIndex(
|
||||
static_cast<int>(Settings::values.physical_device.GetValue()));
|
||||
ui->toggle_async_recording->setChecked(Settings::values.async_command_recording.GetValue());
|
||||
ui->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue());
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue());
|
||||
@ -90,6 +108,14 @@ void ConfigureGraphics::ApplyConfiguration() {
|
||||
ui->toggle_disk_shader_cache, use_disk_shader_cache);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new,
|
||||
use_vsync_new);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.graphics_api,
|
||||
ui->graphics_api_combo);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.physical_device,
|
||||
ui->physical_device_combo);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_command_recording,
|
||||
ui->toggle_async_recording, async_command_recording);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.spirv_shader_gen,
|
||||
ui->spirv_shader_gen, spirv_shader_gen);
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
|
||||
@ -129,3 +155,22 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_vsync_new, Settings::values.use_vsync_new,
|
||||
use_vsync_new);
|
||||
}
|
||||
|
||||
void ConfigureGraphics::DiscoverPhysicalDevices() {
|
||||
Vulkan::Instance instance{};
|
||||
const auto physical_devices = instance.GetPhysicalDevices();
|
||||
|
||||
ui->physical_device_combo->clear();
|
||||
for (const vk::PhysicalDevice& physical_device : physical_devices) {
|
||||
const QString name = QString::fromLocal8Bit(physical_device.getProperties().deviceName);
|
||||
ui->physical_device_combo->addItem(name);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
|
||||
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
|
||||
const bool is_visible = graphics_api == Settings::GraphicsAPI::Vulkan;
|
||||
ui->physical_device_label->setVisible(is_visible);
|
||||
ui->physical_device_combo->setVisible(is_visible);
|
||||
ui->spirv_shader_gen->setVisible(is_visible);
|
||||
}
|
||||
|
@ -30,12 +30,18 @@ public:
|
||||
|
||||
void SetupPerGameUI();
|
||||
|
||||
private:
|
||||
void DiscoverPhysicalDevices();
|
||||
void SetPhysicalDeviceComboVisibility(int index);
|
||||
|
||||
ConfigurationShared::CheckState use_hw_renderer;
|
||||
ConfigurationShared::CheckState use_hw_shader;
|
||||
ConfigurationShared::CheckState separable_shader;
|
||||
ConfigurationShared::CheckState shaders_accurate_mul;
|
||||
ConfigurationShared::CheckState use_disk_shader_cache;
|
||||
ConfigurationShared::CheckState use_vsync_new;
|
||||
ConfigurationShared::CheckState async_command_recording;
|
||||
ConfigurationShared::CheckState spirv_shader_gen;
|
||||
std::unique_ptr<Ui::ConfigureGraphics> ui;
|
||||
QColor bg_color;
|
||||
};
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>430</height>
|
||||
<height>513</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@ -20,6 +20,66 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="apiBox">
|
||||
<property name="title">
|
||||
<string>API Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="graphics_api_label">
|
||||
<property name="text">
|
||||
<string>Graphics API</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="graphics_api_combo">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>OpenGL</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>OpenGLES</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Vulkan</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="physical_device_label">
|
||||
<property name="text">
|
||||
<string>Physical device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="physical_device_combo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="spirv_shader_gen">
|
||||
<property name="text">
|
||||
<string>SPIR-V Shader Generation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="rendererBox">
|
||||
<property name="title">
|
||||
@ -118,6 +178,16 @@
|
||||
<string>Advanced</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_async_recording">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Offloads command buffer recording and fragment shader generation to a worker thread. Can improve performance especially on weaker systems. Disable if you notice better performance. If unsure leave it enabled,</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Async Command Recording</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_disk_shader_cache">
|
||||
<property name="toolTip">
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "citra_qt/util/spinbox.h"
|
||||
#include "common/color.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
|
@ -18,12 +18,6 @@
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
#include <fmt/format.h>
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h> // for chdir
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include "citra_qt/aboutdialog.h"
|
||||
#include "citra_qt/applets/mii_selector.h"
|
||||
#include "citra_qt/applets/swkbd.h"
|
||||
@ -65,9 +59,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/literals.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
#include "common/memory_detect.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scm_rev.h"
|
||||
@ -82,8 +74,6 @@
|
||||
#include "core/file_sys/archive_extsavedata.h"
|
||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/loader/loader.h"
|
||||
@ -93,9 +83,15 @@
|
||||
#include "input_common/main.h"
|
||||
#include "network/network_settings.h"
|
||||
#include "ui_main.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h> // for chdir
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
#include "citra_qt/discord_impl.h"
|
||||
#endif
|
||||
@ -298,7 +294,6 @@ void GMainWindow::InitializeWidgets() {
|
||||
// Create status bar
|
||||
message_label = new QLabel();
|
||||
// Configured separately for left alignment
|
||||
message_label->setVisible(false);
|
||||
message_label->setFrameStyle(QFrame::NoFrame);
|
||||
message_label->setContentsMargins(4, 0, 4, 0);
|
||||
message_label->setAlignment(Qt::AlignLeft);
|
||||
@ -323,10 +318,28 @@ void GMainWindow::InitializeWidgets() {
|
||||
label->setVisible(false);
|
||||
label->setFrameStyle(QFrame::NoFrame);
|
||||
label->setContentsMargins(4, 0, 4, 0);
|
||||
statusBar()->addPermanentWidget(label, 0);
|
||||
statusBar()->addPermanentWidget(label);
|
||||
}
|
||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
|
||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
|
||||
|
||||
// Setup Graphics API button
|
||||
graphics_api_button = new QPushButton();
|
||||
graphics_api_button->setObjectName(QStringLiteral("GraphicsAPIStatusBarButton"));
|
||||
graphics_api_button->setFocusPolicy(Qt::NoFocus);
|
||||
UpdateAPIIndicator(false);
|
||||
|
||||
connect(graphics_api_button, &QPushButton::clicked, this, [this] {
|
||||
if (emulation_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateAPIIndicator(true);
|
||||
});
|
||||
|
||||
statusBar()->insertPermanentWidget(0, graphics_api_button);
|
||||
|
||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText());
|
||||
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon());
|
||||
|
||||
statusBar()->setVisible(true);
|
||||
|
||||
// Removes an ugly inner border from the status bar widgets under Linux
|
||||
@ -954,16 +967,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
render_window->InitRenderTarget();
|
||||
secondary_window->InitRenderTarget();
|
||||
|
||||
Frontend::ScopeAcquireContext scope(*render_window);
|
||||
|
||||
const QString below_gl43_title = tr("OpenGL 4.3 Unsupported");
|
||||
const QString below_gl43_message = tr("Your GPU may not support OpenGL 4.3, or you do not "
|
||||
"have the latest graphics driver.");
|
||||
|
||||
if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_4_3_Core>()) {
|
||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
||||
return false;
|
||||
}
|
||||
const auto scope = render_window->Acquire();
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
@ -1017,7 +1021,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
case Core::System::ResultStatus::ErrorVideoCore:
|
||||
QMessageBox::critical(
|
||||
this, tr("Video Core Error"),
|
||||
tr("An error has occurred. Please <a "
|
||||
tr("An error has occurred during intialization of the video backend. Please <a "
|
||||
"href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>see "
|
||||
"the "
|
||||
"log</a> for more details. "
|
||||
@ -1032,10 +1036,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
"proper drivers for your graphics card from the manufacturer's website."));
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL43:
|
||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
||||
break;
|
||||
|
||||
default:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error while loading ROM!"),
|
||||
@ -1268,7 +1268,6 @@ void GMainWindow::ShutdownGame() {
|
||||
|
||||
// Disable status bar updates
|
||||
status_bar_update_timer.stop();
|
||||
message_label->setVisible(false);
|
||||
message_label_used_for_movie = false;
|
||||
emu_speed_label->setVisible(false);
|
||||
game_fps_label->setVisible(false);
|
||||
@ -1937,6 +1936,7 @@ void GMainWindow::OnConfigure() {
|
||||
setMouseTracking(false);
|
||||
}
|
||||
UpdateSecondaryWindowVisibility();
|
||||
UpdateAPIIndicator(false);
|
||||
} else {
|
||||
Settings::values.input_profiles = old_input_profiles;
|
||||
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
|
||||
@ -2248,6 +2248,26 @@ void GMainWindow::ShowMouseCursor() {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::UpdateAPIIndicator(bool override) {
|
||||
static std::array graphics_apis = {QStringLiteral("OPENGL"), QStringLiteral("OPENGLES"),
|
||||
QStringLiteral("VULKAN")};
|
||||
|
||||
static std::array graphics_api_colors = {QStringLiteral("#00ccdd"), QStringLiteral("#ba2a8d"),
|
||||
QStringLiteral("#91242a")};
|
||||
|
||||
u32 api_index = static_cast<u32>(Settings::values.graphics_api.GetValue());
|
||||
if (override) {
|
||||
api_index = (api_index + 1) % graphics_apis.size();
|
||||
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(api_index);
|
||||
}
|
||||
|
||||
const QString style_sheet = QStringLiteral("QPushButton { font-weight: bold; color: %0; }")
|
||||
.arg(graphics_api_colors[api_index]);
|
||||
|
||||
graphics_api_button->setText(graphics_apis[api_index]);
|
||||
graphics_api_button->setStyleSheet(style_sheet);
|
||||
}
|
||||
|
||||
void GMainWindow::OnMouseActivity() {
|
||||
ShowMouseCursor();
|
||||
}
|
||||
@ -2435,8 +2455,16 @@ void GMainWindow::UpdateUITheme() {
|
||||
QStringList theme_paths(default_theme_paths);
|
||||
|
||||
if (is_default_theme || current_theme.isEmpty()) {
|
||||
qApp->setStyleSheet({});
|
||||
setStyleSheet({});
|
||||
const QString theme_uri(QStringLiteral(":default/style.qss"));
|
||||
QFile f(theme_uri);
|
||||
if (f.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QTextStream ts(&f);
|
||||
qApp->setStyleSheet(ts.readAll());
|
||||
setStyleSheet(ts.readAll());
|
||||
} else {
|
||||
qApp->setStyleSheet({});
|
||||
setStyleSheet({});
|
||||
}
|
||||
theme_paths.append(default_icons);
|
||||
QIcon::setThemeName(default_icons);
|
||||
} else {
|
||||
@ -2615,14 +2643,6 @@ int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
||||
|
||||
QSurfaceFormat format;
|
||||
format.setVersion(4, 3);
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
format.setSwapInterval(0);
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
|
||||
#ifdef __APPLE__
|
||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
||||
chdir(bin_path.c_str());
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <QMainWindow>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include "citra_qt/compatibility_list.h"
|
||||
@ -241,6 +242,7 @@ private:
|
||||
void HideMouseCursor();
|
||||
void ShowMouseCursor();
|
||||
void OpenPerGameConfiguration(u64 title_id, const QString& file_name);
|
||||
void UpdateAPIIndicator(bool override);
|
||||
|
||||
std::unique_ptr<Ui::MainWindow> ui;
|
||||
|
||||
@ -256,6 +258,7 @@ private:
|
||||
QLabel* emu_speed_label = nullptr;
|
||||
QLabel* game_fps_label = nullptr;
|
||||
QLabel* emu_frametime_label = nullptr;
|
||||
QPushButton* graphics_api_button = nullptr;
|
||||
QTimer status_bar_update_timer;
|
||||
bool message_label_used_for_movie = false;
|
||||
|
||||
|
@ -59,6 +59,60 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
|
||||
#endif // _MSC_VER ndef
|
||||
|
||||
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
|
||||
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
|
||||
} \
|
||||
constexpr type& operator|=(type& a, type b) noexcept { \
|
||||
a = a | b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator&=(type& a, type b) noexcept { \
|
||||
a = a & b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator^=(type& a, type b) noexcept { \
|
||||
a = a ^ b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator<<=(type& a, type b) noexcept { \
|
||||
a = a << b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator>>=(type& a, type b) noexcept { \
|
||||
a = a >> b; \
|
||||
return a; \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator~(type key) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(~static_cast<T>(key)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr bool True(type key) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<T>(key) != 0; \
|
||||
} \
|
||||
[[nodiscard]] constexpr bool False(type key) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<T>(key) == 0; \
|
||||
}
|
||||
|
||||
// Generic function to get last error message.
|
||||
// Call directly after the command or use the error num.
|
||||
// This function might change the error code.
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "common/cityhash.h"
|
||||
@ -41,6 +42,13 @@ inline u64 HashCombine(std::size_t& seed, const u64 hash) {
|
||||
return seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct IdentityHash {
|
||||
T operator()(const T& value) const {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/// A helper template that ensures the padding in a struct is initialized by memsetting to 0.
|
||||
template <typename T>
|
||||
struct HashableStruct {
|
||||
|
@ -236,6 +236,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
||||
CLS(Render) \
|
||||
SUB(Render, Software) \
|
||||
SUB(Render, OpenGL) \
|
||||
SUB(Render, Vulkan) \
|
||||
CLS(Audio) \
|
||||
SUB(Audio, DSP) \
|
||||
SUB(Audio, Sink) \
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/formatter.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
// trims up to and including the last of ../, ..\, src/, src\ in a string
|
||||
@ -103,6 +104,7 @@ enum class Class : ClassType {
|
||||
Render, ///< Emulator video output and hardware acceleration
|
||||
Render_Software, ///< Software renderer backend
|
||||
Render_OpenGL, ///< OpenGL backend
|
||||
Render_Vulkan, ///< Vulkan backend
|
||||
Audio, ///< Audio emulation
|
||||
Audio_DSP, ///< The HLE and LLE implementations of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
#include <cstdlib>
|
||||
#include <type_traits>
|
||||
|
||||
@ -23,19 +24,34 @@ struct Rectangle {
|
||||
constexpr Rectangle(T left, T top, T right, T bottom)
|
||||
: left(left), top(top), right(right), bottom(bottom) {}
|
||||
|
||||
[[nodiscard]] T GetWidth() const {
|
||||
constexpr auto operator<=>(const Rectangle&) const = default;
|
||||
|
||||
constexpr void operator*=(const T value) {
|
||||
left *= value;
|
||||
top *= value;
|
||||
right *= value;
|
||||
bottom *= value;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Rectangle operator*(const T value) const {
|
||||
return Rectangle{left * value, top * value, right * value, bottom * value};
|
||||
}
|
||||
[[nodiscard]] constexpr Rectangle operator/(const T value) const {
|
||||
return Rectangle{left / value, top / value, right / value, bottom / value};
|
||||
}
|
||||
[[nodiscard]] constexpr T GetWidth() const {
|
||||
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
|
||||
}
|
||||
[[nodiscard]] T GetHeight() const {
|
||||
[[nodiscard]] constexpr T GetHeight() const {
|
||||
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
|
||||
}
|
||||
[[nodiscard]] Rectangle<T> TranslateX(const T x) const {
|
||||
[[nodiscard]] constexpr Rectangle<T> TranslateX(const T x) const {
|
||||
return Rectangle{left + x, top, right + x, bottom};
|
||||
}
|
||||
[[nodiscard]] Rectangle<T> TranslateY(const T y) const {
|
||||
[[nodiscard]] constexpr Rectangle<T> TranslateY(const T y) const {
|
||||
return Rectangle{left, top + y, right, bottom + y};
|
||||
}
|
||||
[[nodiscard]] Rectangle<T> Scale(const float s) const {
|
||||
[[nodiscard]] constexpr Rectangle<T> Scale(const float s) const {
|
||||
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
|
||||
static_cast<T>(top + GetHeight() * s)};
|
||||
}
|
||||
|
@ -3,8 +3,8 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
@ -79,6 +79,7 @@ public:
|
||||
: backing_mem(std::move(backing_mem_)), offset(0) {
|
||||
Init();
|
||||
}
|
||||
|
||||
MemoryRef(std::shared_ptr<BackingMem> backing_mem_, u64 offset_)
|
||||
: backing_mem(std::move(backing_mem_)), offset(offset_) {
|
||||
ASSERT(offset <= backing_mem->GetSize());
|
||||
@ -93,11 +94,11 @@ public:
|
||||
return cptr;
|
||||
}
|
||||
|
||||
u8* GetPtr() {
|
||||
operator const u8*() const {
|
||||
return cptr;
|
||||
}
|
||||
|
||||
operator const u8*() const {
|
||||
u8* GetPtr() {
|
||||
return cptr;
|
||||
}
|
||||
|
||||
@ -105,6 +106,14 @@ public:
|
||||
return cptr;
|
||||
}
|
||||
|
||||
auto GetWriteBytes(std::size_t size) {
|
||||
return std::span{reinterpret_cast<std::byte*>(cptr), size > csize ? csize : size};
|
||||
}
|
||||
|
||||
auto GetReadBytes(std::size_t size) const {
|
||||
return std::span{reinterpret_cast<const std::byte*>(cptr), size > csize ? csize : size};
|
||||
}
|
||||
|
||||
std::size_t GetSize() const {
|
||||
return csize;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/shared_page.h"
|
||||
#include "core/hle/service/cam/cam.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/ir/ir_rst.h"
|
||||
@ -20,6 +19,17 @@
|
||||
|
||||
namespace Settings {
|
||||
|
||||
[[nodiscard]] std::string_view GetAPIName(GraphicsAPI api) {
|
||||
switch (api) {
|
||||
case GraphicsAPI::OpenGL:
|
||||
return "OpenGL";
|
||||
case GraphicsAPI::OpenGLES:
|
||||
return "OpenGLES";
|
||||
case GraphicsAPI::Vulkan:
|
||||
return "Vulkan";
|
||||
}
|
||||
}
|
||||
|
||||
Values values = {};
|
||||
static bool configuring_global = true;
|
||||
|
||||
@ -95,7 +105,8 @@ void LogSettings() {
|
||||
LOG_INFO(Config, "Citra Configuration:");
|
||||
log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue());
|
||||
log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue());
|
||||
log_setting("Renderer_UseGLES", values.use_gles.GetValue());
|
||||
log_setting("Renderer_GraphicsAPI", GetAPIName(values.graphics_api.GetValue()));
|
||||
log_setting("Renderer_AsyncRecording", values.async_command_recording.GetValue());
|
||||
log_setting("Renderer_UseHwRenderer", values.use_hw_renderer.GetValue());
|
||||
log_setting("Renderer_UseHwShader", values.use_hw_shader.GetValue());
|
||||
log_setting("Renderer_SeparableShader", values.separable_shader.GetValue());
|
||||
|
@ -15,6 +15,12 @@
|
||||
|
||||
namespace Settings {
|
||||
|
||||
enum class GraphicsAPI {
|
||||
OpenGL = 0,
|
||||
OpenGLES = 1,
|
||||
Vulkan = 2,
|
||||
};
|
||||
|
||||
enum class InitClock : u32 {
|
||||
SystemTime = 0,
|
||||
FixedTime = 1,
|
||||
@ -440,7 +446,12 @@ struct Values {
|
||||
Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
|
||||
|
||||
// Renderer
|
||||
Setting<bool> use_gles{false, "use_gles"};
|
||||
SwitchableSetting<GraphicsAPI> graphics_api{GraphicsAPI::OpenGL, "graphics_api"};
|
||||
SwitchableSetting<u16> physical_device{0, "physical_device"};
|
||||
SwitchableSetting<bool> spirv_shader_gen{true, "spirv_shader_gen"};
|
||||
Setting<bool> renderer_debug{false, "renderer_debug"};
|
||||
Setting<bool> dump_command_buffers{false, "dump_command_buffers"};
|
||||
SwitchableSetting<bool> async_command_recording{true, "async_command_recording"};
|
||||
SwitchableSetting<bool> use_hw_renderer{true, "use_hw_renderer"};
|
||||
SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
|
||||
SwitchableSetting<bool> separable_shader{false, "use_separable_shader"};
|
||||
|
@ -113,8 +113,6 @@ add_library(core STATIC
|
||||
frontend/input.h
|
||||
frontend/mic.cpp
|
||||
frontend/mic.h
|
||||
frontend/scope_acquire_context.cpp
|
||||
frontend/scope_acquire_context.h
|
||||
gdbstub/gdbstub.cpp
|
||||
gdbstub/gdbstub.h
|
||||
hle/applets/applet.cpp
|
||||
|
@ -431,8 +431,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
switch (result) {
|
||||
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
||||
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
|
||||
case VideoCore::ResultStatus::ErrorBelowGL43:
|
||||
return ResultStatus::ErrorVideoCore_ErrorBelowGL43;
|
||||
default:
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
@ -88,8 +88,6 @@ public:
|
||||
ErrorVideoCore, ///< Error in the video core
|
||||
ErrorVideoCore_ErrorGenericDrivers, ///< Error in the video core due to the user having
|
||||
/// generic drivers installed
|
||||
ErrorVideoCore_ErrorBelowGL43, ///< Error in the video core due to the user not having
|
||||
/// OpenGL 4.3 or higher
|
||||
ErrorSavestate, ///< Error saving or loading
|
||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||
ErrorUnknown ///< Any other error
|
||||
|
@ -14,6 +14,17 @@
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
/// Information for the Graphics Backends signifying what type of screen pointer is in
|
||||
/// WindowInformation
|
||||
enum class WindowSystemType : u8 {
|
||||
Headless,
|
||||
Android,
|
||||
Windows,
|
||||
MacOS,
|
||||
X11,
|
||||
Wayland,
|
||||
};
|
||||
|
||||
struct Frame;
|
||||
/**
|
||||
* For smooth Vsync rendering, we want to always present the latest frame that the core generates,
|
||||
@ -62,11 +73,33 @@ class GraphicsContext {
|
||||
public:
|
||||
virtual ~GraphicsContext();
|
||||
|
||||
/// Inform the driver to swap the front/back buffers and present the current image
|
||||
virtual void SwapBuffers(){};
|
||||
|
||||
/// Makes the graphics context current for the caller thread
|
||||
virtual void MakeCurrent() = 0;
|
||||
virtual void MakeCurrent(){};
|
||||
|
||||
/// Releases (dunno if this is the "right" word) the context from the caller thread
|
||||
virtual void DoneCurrent() = 0;
|
||||
virtual void DoneCurrent(){};
|
||||
|
||||
class Scoped {
|
||||
public:
|
||||
explicit Scoped(GraphicsContext& context_) : context(context_) {
|
||||
context.MakeCurrent();
|
||||
}
|
||||
~Scoped() {
|
||||
context.DoneCurrent();
|
||||
}
|
||||
|
||||
private:
|
||||
GraphicsContext& context;
|
||||
};
|
||||
|
||||
/// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value
|
||||
/// ends
|
||||
[[nodiscard]] Scoped Acquire() {
|
||||
return Scoped{*this};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -100,6 +133,23 @@ public:
|
||||
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight};
|
||||
};
|
||||
|
||||
/// Data describing host window system information
|
||||
struct WindowSystemInfo {
|
||||
// Window system type. Determines which GL context or Vulkan WSI is used.
|
||||
WindowSystemType type = WindowSystemType::Headless;
|
||||
|
||||
// Connection to a display server. This is used on X11 and Wayland platforms.
|
||||
void* display_connection = nullptr;
|
||||
|
||||
// Render surface. This is a pointer to the native window handle, which depends
|
||||
// on the platform. e.g. HWND for Windows, Window for X11. If the surface is
|
||||
// set to nullptr, the video backend will run in headless mode.
|
||||
void* render_surface = nullptr;
|
||||
|
||||
// Scale of the render surface. For hidpi systems, this will be >1.
|
||||
float render_surface_scale = 1.0f;
|
||||
};
|
||||
|
||||
/// Polls window events
|
||||
virtual void PollEvents() = 0;
|
||||
|
||||
@ -163,6 +213,13 @@ public:
|
||||
config = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns system information about the drawing area.
|
||||
*/
|
||||
const WindowSystemInfo& GetWindowInfo() const {
|
||||
return window_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the framebuffer layout (width, height, and screen regions)
|
||||
* @note This method is thread-safe
|
||||
@ -211,6 +268,7 @@ protected:
|
||||
}
|
||||
|
||||
bool is_secondary{};
|
||||
WindowSystemInfo window_info;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -1,17 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
ScopeAcquireContext::ScopeAcquireContext(Frontend::GraphicsContext& context) : context{context} {
|
||||
context.MakeCurrent();
|
||||
}
|
||||
ScopeAcquireContext::~ScopeAcquireContext() {
|
||||
context.DoneCurrent();
|
||||
}
|
||||
|
||||
} // namespace Frontend
|
@ -1,23 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
class GraphicsContext;
|
||||
|
||||
/// Helper class to acquire/release window context within a given scope
|
||||
class ScopeAcquireContext : NonCopyable {
|
||||
public:
|
||||
explicit ScopeAcquireContext(Frontend::GraphicsContext& context);
|
||||
~ScopeAcquireContext();
|
||||
|
||||
private:
|
||||
Frontend::GraphicsContext& context;
|
||||
};
|
||||
|
||||
} // namespace Frontend
|
@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
@ -3,8 +3,6 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cryptopp/osrng.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
@ -1053,7 +1053,7 @@ public:
|
||||
}
|
||||
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
SDL_Event event{};
|
||||
while (state.event_queue.Pop(event)) {
|
||||
if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
|
||||
continue;
|
||||
|
@ -1,3 +1,5 @@
|
||||
add_subdirectory(host_shaders)
|
||||
|
||||
add_library(video_core STATIC
|
||||
command_processor.cpp
|
||||
command_processor.h
|
||||
@ -13,6 +15,8 @@ add_library(video_core STATIC
|
||||
precompiled_headers.h
|
||||
primitive_assembly.cpp
|
||||
primitive_assembly.h
|
||||
rasterizer_accelerated.cpp
|
||||
rasterizer_accelerated.h
|
||||
rasterizer_interface.h
|
||||
regs.cpp
|
||||
regs.h
|
||||
@ -24,21 +28,22 @@ add_library(video_core STATIC
|
||||
regs_texturing.h
|
||||
renderer_base.cpp
|
||||
renderer_base.h
|
||||
rasterizer_cache/cached_surface.cpp
|
||||
rasterizer_cache/cached_surface.h
|
||||
rasterizer_cache/morton_swizzle.h
|
||||
rasterizer_cache/pixel_format.cpp
|
||||
rasterizer_cache/pixel_format.h
|
||||
rasterizer_cache/rasterizer_cache.cpp
|
||||
rasterizer_cache/rasterizer_cache.h
|
||||
rasterizer_cache/rasterizer_cache_types.h
|
||||
rasterizer_cache/rasterizer_cache_utils.cpp
|
||||
rasterizer_cache/rasterizer_cache_utils.h
|
||||
rasterizer_cache/surface_base.h
|
||||
rasterizer_cache/utils.cpp
|
||||
rasterizer_cache/utils.h
|
||||
rasterizer_cache/surface_params.cpp
|
||||
rasterizer_cache/surface_params.h
|
||||
rasterizer_cache/texture_runtime.cpp
|
||||
rasterizer_cache/texture_runtime.h
|
||||
renderer_opengl/frame_dumper_opengl.cpp
|
||||
renderer_opengl/frame_dumper_opengl.h
|
||||
renderer_opengl/gl_driver.cpp
|
||||
renderer_opengl/gl_driver.h
|
||||
renderer_opengl/gl_format_reinterpreter.cpp
|
||||
renderer_opengl/gl_format_reinterpreter.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_resource_manager.cpp
|
||||
@ -57,6 +62,8 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_state.h
|
||||
renderer_opengl/gl_stream_buffer.cpp
|
||||
renderer_opengl/gl_stream_buffer.h
|
||||
renderer_opengl/gl_texture_runtime.cpp
|
||||
renderer_opengl/gl_texture_runtime.h
|
||||
renderer_opengl/gl_vars.cpp
|
||||
renderer_opengl/gl_vars.h
|
||||
renderer_opengl/pica_to_gl.h
|
||||
@ -64,8 +71,6 @@ add_library(video_core STATIC
|
||||
renderer_opengl/post_processing_opengl.h
|
||||
renderer_opengl/renderer_opengl.cpp
|
||||
renderer_opengl/renderer_opengl.h
|
||||
renderer_opengl/texture_downloader_es.cpp
|
||||
renderer_opengl/texture_downloader_es.h
|
||||
renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp
|
||||
renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h
|
||||
renderer_opengl/texture_filters/bicubic/bicubic.cpp
|
||||
@ -79,14 +84,55 @@ add_library(video_core STATIC
|
||||
renderer_opengl/texture_filters/texture_filterer.h
|
||||
renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp
|
||||
renderer_opengl/texture_filters/xbrz/xbrz_freescale.h
|
||||
#temporary, move these back in alphabetical order before merging
|
||||
renderer_opengl/gl_format_reinterpreter.cpp
|
||||
renderer_opengl/gl_format_reinterpreter.h
|
||||
renderer_vulkan/pica_to_vk.h
|
||||
renderer_vulkan/renderer_vulkan.cpp
|
||||
renderer_vulkan/renderer_vulkan.h
|
||||
renderer_vulkan/vk_blit_helper.cpp
|
||||
renderer_vulkan/vk_blit_helper.h
|
||||
renderer_vulkan/vk_common.cpp
|
||||
renderer_vulkan/vk_common.h
|
||||
renderer_vulkan/vk_descriptor_manager.cpp
|
||||
renderer_vulkan/vk_descriptor_manager.h
|
||||
renderer_vulkan/vk_format_reinterpreter.cpp
|
||||
renderer_vulkan/vk_format_reinterpreter.h
|
||||
renderer_vulkan/vk_master_semaphore.cpp
|
||||
renderer_vulkan/vk_master_semaphore.h
|
||||
renderer_vulkan/vk_rasterizer.cpp
|
||||
renderer_vulkan/vk_rasterizer.h
|
||||
renderer_vulkan/vk_scheduler.cpp
|
||||
renderer_vulkan/vk_scheduler.h
|
||||
renderer_vulkan/vk_resource_pool.cpp
|
||||
renderer_vulkan/vk_resource_pool.h
|
||||
renderer_vulkan/vk_instance.cpp
|
||||
renderer_vulkan/vk_instance.h
|
||||
renderer_vulkan/vk_pipeline_cache.cpp
|
||||
renderer_vulkan/vk_pipeline_cache.h
|
||||
renderer_vulkan/vk_platform.cpp
|
||||
renderer_vulkan/vk_platform.h
|
||||
renderer_vulkan/vk_renderpass_cache.cpp
|
||||
renderer_vulkan/vk_renderpass_cache.h
|
||||
renderer_vulkan/vk_shader_decompiler.cpp
|
||||
renderer_vulkan/vk_shader_decompiler.h
|
||||
renderer_vulkan/vk_shader_gen.cpp
|
||||
renderer_vulkan/vk_shader_gen.h
|
||||
renderer_vulkan/vk_shader_gen_spv.cpp
|
||||
renderer_vulkan/vk_shader_gen_spv.h
|
||||
renderer_vulkan/vk_shader_util.cpp
|
||||
renderer_vulkan/vk_shader_util.h
|
||||
renderer_vulkan/vk_stream_buffer.cpp
|
||||
renderer_vulkan/vk_stream_buffer.h
|
||||
renderer_vulkan/vk_swapchain.cpp
|
||||
renderer_vulkan/vk_swapchain.h
|
||||
renderer_vulkan/vk_texture_runtime.cpp
|
||||
renderer_vulkan/vk_texture_runtime.h
|
||||
shader/debug_data.h
|
||||
shader/shader.cpp
|
||||
shader/shader.h
|
||||
shader/shader_cache.h
|
||||
shader/shader_interpreter.cpp
|
||||
shader/shader_interpreter.h
|
||||
shader/shader_uniforms.cpp
|
||||
shader/shader_uniforms.h
|
||||
swrasterizer/clipper.cpp
|
||||
swrasterizer/clipper.h
|
||||
swrasterizer/framebuffer.cpp
|
||||
@ -112,36 +158,6 @@ add_library(video_core STATIC
|
||||
video_core.h
|
||||
)
|
||||
|
||||
set(SHADER_FILES
|
||||
renderer_opengl/depth_to_color.frag
|
||||
renderer_opengl/depth_to_color.vert
|
||||
renderer_opengl/ds_to_color.frag
|
||||
renderer_opengl/texture_filters/anime4k/refine.frag
|
||||
renderer_opengl/texture_filters/anime4k/x_gradient.frag
|
||||
renderer_opengl/texture_filters/anime4k/y_gradient.frag
|
||||
renderer_opengl/texture_filters/bicubic/bicubic.frag
|
||||
renderer_opengl/texture_filters/nearest_neighbor/nearest_neighbor.frag
|
||||
renderer_opengl/texture_filters/scale_force/scale_force.frag
|
||||
renderer_opengl/texture_filters/tex_coord.vert
|
||||
renderer_opengl/texture_filters/xbrz/xbrz_freescale.frag
|
||||
renderer_opengl/texture_filters/xbrz/xbrz_freescale.vert
|
||||
)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/generate_shaders.cmake)
|
||||
|
||||
foreach(shader_file ${SHADER_FILES})
|
||||
get_filename_component(shader_file_name ${shader_file} NAME)
|
||||
GetShaderHeaderFile(${shader_file_name})
|
||||
list(APPEND SHADER_HEADERS ${shader_header_file})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(shaders
|
||||
BYPRODUCTS ${SHADER_HEADERS}
|
||||
COMMAND "${CMAKE_COMMAND}" -P ${CMAKE_CURRENT_SOURCE_DIR}/generate_shaders.cmake
|
||||
SOURCES ${SHADER_FILES}
|
||||
)
|
||||
add_dependencies(video_core shaders)
|
||||
|
||||
target_include_directories(video_core PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
if(ARCHITECTURE_x86_64)
|
||||
@ -157,10 +173,19 @@ endif()
|
||||
|
||||
create_target_directory_groups(video_core)
|
||||
|
||||
# Ignore nullability warnings generated from VMA
|
||||
if (NOT MSVC)
|
||||
target_compile_options(vma INTERFACE -Wno-unused-variable -Wno-nullability-completeness)
|
||||
endif()
|
||||
|
||||
target_link_libraries(video_core PUBLIC common core)
|
||||
target_link_libraries(video_core PRIVATE glad nihstro-headers Boost::serialization)
|
||||
set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||
|
||||
add_dependencies(video_core host_shaders)
|
||||
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
|
||||
target_link_libraries(video_core PRIVATE vulkan-headers vma sirit SPIRV glslang glad)
|
||||
target_link_libraries(video_core PRIVATE nihstro-headers Boost::serialization glm::glm)
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_link_libraries(video_core PUBLIC xbyak)
|
||||
endif()
|
||||
|
100
src/video_core/host_shaders/CMakeLists.txt
Normal file
100
src/video_core/host_shaders/CMakeLists.txt
Normal file
@ -0,0 +1,100 @@
|
||||
# Copyright 2022 Citra Emulator Project
|
||||
# Licensed under GPLv2 or any later version
|
||||
# Refer to the license.txt file included.
|
||||
|
||||
set(SHADER_FILES
|
||||
texture_filtering/bicubic.frag
|
||||
texture_filtering/nearest_neighbor.frag
|
||||
texture_filtering/refine.frag
|
||||
texture_filtering/scale_force.frag
|
||||
texture_filtering/tex_coord.vert
|
||||
texture_filtering/xbrz_freescale.frag
|
||||
texture_filtering/xbrz_freescale.vert
|
||||
texture_filtering/x_gradient.frag
|
||||
texture_filtering/y_gradient.frag
|
||||
opengl_present.frag
|
||||
opengl_present.vert
|
||||
opengl_present_anaglyph.frag
|
||||
opengl_present_interlaced.frag
|
||||
vulkan_present.frag
|
||||
vulkan_present.vert
|
||||
vulkan_present_anaglyph.frag
|
||||
vulkan_present_interlaced.frag
|
||||
)
|
||||
|
||||
find_program(GLSLANGVALIDATOR "glslangValidator")
|
||||
if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
|
||||
message(FATAL_ERROR "Required program `glslangValidator` not found.")
|
||||
endif()
|
||||
|
||||
set(MACROS "-Dgl_VertexID=gl_VertexIndex")
|
||||
set(QUIET_FLAG "--quiet")
|
||||
|
||||
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
|
||||
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
|
||||
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
||||
|
||||
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
|
||||
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
|
||||
|
||||
# Check if `--quiet` is available on host's glslangValidator version
|
||||
# glslangValidator prints to STDERR iff an unrecognized flag is passed to it
|
||||
execute_process(
|
||||
COMMAND
|
||||
${GLSLANGVALIDATOR} ${QUIET_FLAG}
|
||||
ERROR_VARIABLE
|
||||
GLSLANG_ERROR
|
||||
# STDOUT variable defined to silence unnecessary output during CMake configuration
|
||||
OUTPUT_VARIABLE
|
||||
GLSLANG_OUTPUT
|
||||
)
|
||||
|
||||
if (NOT GLSLANG_ERROR STREQUAL "")
|
||||
message(WARNING "Refusing to use unavailable flag `${QUIET_FLAG}` on `${GLSLANGVALIDATOR}`")
|
||||
set(QUIET_FLAG "")
|
||||
endif()
|
||||
|
||||
foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
||||
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
|
||||
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
|
||||
# Skip generating source headers on Vulkan exclusive files
|
||||
if (NOT ${FILENAME} MATCHES "vulkan.*")
|
||||
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${SOURCE_HEADER_FILE}
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE}
|
||||
MAIN_DEPENDENCY
|
||||
${SOURCE_FILE}
|
||||
DEPENDS
|
||||
${INPUT_FILE}
|
||||
# HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified
|
||||
)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
|
||||
endif()
|
||||
# Skip compiling to SPIR-V OpenGL exclusive files
|
||||
if (NOT ${FILENAME} MATCHES "opengl.*")
|
||||
string(TOUPPER ${SHADER_NAME}_SPV SPIRV_VARIABLE_NAME)
|
||||
set(SPIRV_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}_spv.h)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${SPIRV_HEADER_FILE}
|
||||
COMMAND
|
||||
${GLSLANGVALIDATOR} --target-env vulkan1.1 --glsl-version 450 ${QUIET_FLAG} ${MACROS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
|
||||
MAIN_DEPENDENCY
|
||||
${SOURCE_FILE}
|
||||
)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${SPIRV_HEADER_FILE})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(SHADER_SOURCES ${SHADER_FILES})
|
||||
list(APPEND SHADER_SOURCES ${GLSL_INCLUDES})
|
||||
|
||||
add_custom_target(host_shaders
|
||||
DEPENDS
|
||||
${SHADER_HEADERS}
|
||||
SOURCES
|
||||
${SHADER_SOURCES}
|
||||
)
|
36
src/video_core/host_shaders/StringShaderHeader.cmake
Normal file
36
src/video_core/host_shaders/StringShaderHeader.cmake
Normal file
@ -0,0 +1,36 @@
|
||||
# SPDX-FileCopyrightText: 2020 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(SOURCE_FILE ${CMAKE_ARGV3})
|
||||
set(HEADER_FILE ${CMAKE_ARGV4})
|
||||
set(INPUT_FILE ${CMAKE_ARGV5})
|
||||
|
||||
get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME)
|
||||
string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME})
|
||||
string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME)
|
||||
|
||||
FILE(READ ${SOURCE_FILE} line_contents)
|
||||
|
||||
# Replace double quotes with single quotes,
|
||||
# as double quotes will be used to wrap the lines
|
||||
STRING(REGEX REPLACE "\"" "'" line_contents "${line_contents}")
|
||||
|
||||
# CMake separates list elements with semicolons, but semicolons
|
||||
# are used extensively in the shader code.
|
||||
# Replace with a temporary marker, to be reverted later.
|
||||
STRING(REGEX REPLACE ";" "{{SEMICOLON}}" line_contents "${line_contents}")
|
||||
|
||||
# Make every line an individual element in the CMake list.
|
||||
STRING(REGEX REPLACE "\n" ";" line_contents "${line_contents}")
|
||||
|
||||
# Build the shader string, wrapping each line in double quotes.
|
||||
foreach(line IN LISTS line_contents)
|
||||
string(CONCAT CONTENTS "${CONTENTS}" \"${line}\\n\"\n)
|
||||
endforeach()
|
||||
|
||||
# Revert the original semicolons in the source.
|
||||
STRING(REGEX REPLACE "{{SEMICOLON}}" ";" CONTENTS "${CONTENTS}")
|
||||
|
||||
get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
|
||||
make_directory(${OUTPUT_DIR})
|
||||
configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY)
|
18
src/video_core/host_shaders/opengl_present.frag
Normal file
18
src/video_core/host_shaders/opengl_present.frag
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
layout(location = 0) in vec2 frag_tex_coord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
|
||||
uniform vec4 i_resolution;
|
||||
uniform vec4 o_resolution;
|
||||
uniform int layer;
|
||||
|
||||
void main() {
|
||||
color = texture(color_texture, frag_tex_coord);
|
||||
}
|
23
src/video_core/host_shaders/opengl_present.vert
Normal file
23
src/video_core/host_shaders/opengl_present.vert
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
layout(location = 0) in vec2 vert_position;
|
||||
layout(location = 1) in vec2 vert_tex_coord;
|
||||
layout(location = 0) out vec2 frag_tex_coord;
|
||||
|
||||
// This is a truncated 3x3 matrix for 2D transformations:
|
||||
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
|
||||
// The third column performs translation.
|
||||
// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
|
||||
// implicitly be [0, 0, 1]
|
||||
uniform mat3x2 modelview_matrix;
|
||||
|
||||
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(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
|
||||
frag_tex_coord = vert_tex_coord;
|
||||
}
|
32
src/video_core/host_shaders/opengl_present_anaglyph.frag
Normal file
32
src/video_core/host_shaders/opengl_present_anaglyph.frag
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
// Anaglyph Red-Cyan shader based on Dubois algorithm
|
||||
// Constants taken from the paper:
|
||||
// "Conversion of a Stereo Pair to Anaglyph with
|
||||
// the Least-Squares Projection Method"
|
||||
// Eric Dubois, March 2009
|
||||
const mat3 l = mat3( 0.437, 0.449, 0.164,
|
||||
-0.062,-0.062,-0.024,
|
||||
-0.048,-0.050,-0.017);
|
||||
const mat3 r = mat3(-0.011,-0.032,-0.007,
|
||||
0.377, 0.761, 0.009,
|
||||
-0.026,-0.093, 1.234);
|
||||
|
||||
layout(location = 0) in vec2 frag_tex_coord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
layout(binding = 1) uniform sampler2D color_texture_r;
|
||||
|
||||
uniform vec4 resolution;
|
||||
uniform int layer;
|
||||
|
||||
void main() {
|
||||
vec4 color_tex_l = texture(color_texture, frag_tex_coord);
|
||||
vec4 color_tex_r = texture(color_texture_r, frag_tex_coord);
|
||||
color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a);
|
||||
}
|
22
src/video_core/host_shaders/opengl_present_interlaced.frag
Normal file
22
src/video_core/host_shaders/opengl_present_interlaced.frag
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
|
||||
layout(location = 0) in vec2 frag_tex_coord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
layout(binding = 1) uniform sampler2D color_texture_r;
|
||||
|
||||
uniform vec4 o_resolution;
|
||||
uniform int reverse_interlaced;
|
||||
|
||||
void main() {
|
||||
float screen_row = o_resolution.x * frag_tex_coord.x;
|
||||
if (int(screen_row) % 2 == reverse_interlaced)
|
||||
color = texture(color_texture, frag_tex_coord);
|
||||
else
|
||||
color = texture(color_texture_r, frag_tex_coord);
|
||||
}
|
15
src/video_core/host_shaders/source_shader.h.in
Normal file
15
src/video_core/host_shaders/source_shader.h.in
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace HostShaders {
|
||||
|
||||
constexpr std::string_view @CONTENTS_NAME@ = {
|
||||
@CONTENTS@
|
||||
};
|
||||
|
||||
} // namespace HostShaders
|
@ -1,11 +1,14 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 330
|
||||
precision mediump float;
|
||||
|
||||
in vec2 tex_coord;
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform sampler2D input_texture;
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
// from http://www.java-gaming.org/index.php?topic=35123.0
|
||||
vec4 cubic(float v) {
|
||||
@ -18,9 +21,9 @@ vec4 cubic(float v) {
|
||||
return vec4(x, y, z, w) * (1.0 / 6.0);
|
||||
}
|
||||
|
||||
vec4 textureBicubic(sampler2D sampler, vec2 texCoords) {
|
||||
vec4 textureBicubic(sampler2D tex_sampler, vec2 texCoords) {
|
||||
|
||||
vec2 texSize = vec2(textureSize(sampler, 0));
|
||||
vec2 texSize = vec2(textureSize(tex_sampler, 0));
|
||||
vec2 invTexSize = 1.0 / texSize;
|
||||
|
||||
texCoords = texCoords * texSize - 0.5;
|
||||
@ -38,10 +41,10 @@ vec4 textureBicubic(sampler2D sampler, vec2 texCoords) {
|
||||
|
||||
offset *= invTexSize.xxyy;
|
||||
|
||||
vec4 sample0 = texture(sampler, offset.xz);
|
||||
vec4 sample1 = texture(sampler, offset.yz);
|
||||
vec4 sample2 = texture(sampler, offset.xw);
|
||||
vec4 sample3 = texture(sampler, offset.yw);
|
||||
vec4 sample0 = texture(tex_sampler, offset.xz);
|
||||
vec4 sample1 = texture(tex_sampler, offset.yz);
|
||||
vec4 sample2 = texture(tex_sampler, offset.xw);
|
||||
vec4 sample3 = texture(tex_sampler, offset.yw);
|
||||
|
||||
float sx = s.x / (s.x + s.y);
|
||||
float sy = s.z / (s.z + s.w);
|
@ -0,0 +1,15 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
void main() {
|
||||
frag_color = texture(input_texture, tex_coord);
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
//? #version 330
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
in vec2 tex_coord;
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform sampler2D HOOKED;
|
||||
uniform sampler2D LUMAD;
|
||||
layout(binding = 0) uniform sampler2D HOOKED;
|
||||
layout(binding = 1) uniform sampler2D LUMAD;
|
||||
|
||||
const float LINE_DETECT_THRESHOLD = 0.4;
|
||||
const float STRENGTH = 0.6;
|
@ -1,5 +1,3 @@
|
||||
//? #version 320 es
|
||||
|
||||
// from https://github.com/BreadFish64/ScaleFish/tree/master/scale_force
|
||||
|
||||
// MIT License
|
||||
@ -24,13 +22,18 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 320 es
|
||||
|
||||
precision mediump float;
|
||||
|
||||
in vec2 tex_coord;
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform sampler2D input_texture;
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
vec2 tex_size;
|
||||
vec2 inv_tex_size;
|
@ -1,5 +1,9 @@
|
||||
//? #version 330
|
||||
out vec2 tex_coord;
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
layout(location = 0) out vec2 tex_coord;
|
||||
|
||||
const vec2 vertices[4] =
|
||||
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
@ -1,11 +1,14 @@
|
||||
//? #version 330
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
in vec2 tex_coord;
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out vec2 frag_color;
|
||||
|
||||
out vec2 frag_color;
|
||||
|
||||
uniform sampler2D tex_input;
|
||||
layout(binding = 0) uniform sampler2D tex_input;
|
||||
|
||||
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
|
||||
// TODO: improve handling of alpha channel
|
@ -1,14 +1,25 @@
|
||||
//? #version 330
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
in vec2 tex_coord;
|
||||
in vec2 source_size;
|
||||
in vec2 output_size;
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 1) in vec2 source_size;
|
||||
layout(location = 2) in vec2 output_size;
|
||||
|
||||
out vec4 frag_color;
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
uniform sampler2D tex;
|
||||
layout(binding = 0) uniform sampler2D tex;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(push_constant, std140) uniform XbrzInfo {
|
||||
lowp float scale;
|
||||
};
|
||||
#else
|
||||
uniform lowp float scale;
|
||||
#endif
|
||||
|
||||
const int BLEND_NONE = 0;
|
||||
const int BLEND_NORMAL = 1;
|
@ -0,0 +1,28 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
layout(location = 0) out vec2 tex_coord;
|
||||
layout(location = 1) out vec2 source_size;
|
||||
layout(location = 2) out vec2 output_size;
|
||||
|
||||
layout(binding = 0) uniform sampler2D tex;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(push_constant, std140) uniform XbrzInfo {
|
||||
lowp float scale;
|
||||
};
|
||||
#else
|
||||
uniform lowp float scale;
|
||||
#endif
|
||||
|
||||
const vec2 vertices[4] =
|
||||
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
|
||||
tex_coord = (vertices[gl_VertexID] + 1.0) / 2.0;
|
||||
source_size = vec2(textureSize(tex, 0));
|
||||
output_size = source_size * scale;
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
//? #version 330
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
//? #version 430 core
|
||||
precision mediump float;
|
||||
|
||||
in vec2 tex_coord;
|
||||
layout(location = 0) in vec2 tex_coord;
|
||||
layout(location = 0) out float frag_color;
|
||||
|
||||
out float frag_color;
|
||||
|
||||
uniform sampler2D tex_input;
|
||||
layout(binding = 0) uniform sampler2D tex_input;
|
||||
|
||||
void main() {
|
||||
vec2 t = textureLodOffset(tex_input, tex_coord, 0.0, ivec2(0, 1)).xy;
|
26
src/video_core/host_shaders/vulkan_present.frag
Normal file
26
src/video_core/host_shaders/vulkan_present.frag
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#version 450 core
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout (location = 0) in vec2 frag_tex_coord;
|
||||
layout (location = 0) out vec4 color;
|
||||
|
||||
layout (push_constant, std140) uniform DrawInfo {
|
||||
mat4 modelview_matrix;
|
||||
vec4 i_resolution;
|
||||
vec4 o_resolution;
|
||||
int screen_id_l;
|
||||
int screen_id_r;
|
||||
int layer;
|
||||
int reverse_interlaced;
|
||||
};
|
||||
|
||||
layout (set = 0, binding = 0) uniform texture2D screen_textures[3];
|
||||
layout (set = 0, binding = 1) uniform sampler screen_sampler;
|
||||
|
||||
void main() {
|
||||
color = texture(sampler2D(screen_textures[screen_id_l], screen_sampler), frag_tex_coord);
|
||||
}
|
25
src/video_core/host_shaders/vulkan_present.vert
Normal file
25
src/video_core/host_shaders/vulkan_present.vert
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#version 450 core
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout (location = 0) in vec2 vert_position;
|
||||
layout (location = 1) in vec2 vert_tex_coord;
|
||||
layout (location = 0) out vec2 frag_tex_coord;
|
||||
|
||||
layout (push_constant, std140) uniform DrawInfo {
|
||||
mat4 modelview_matrix;
|
||||
vec4 i_resolution;
|
||||
vec4 o_resolution;
|
||||
int screen_id_l;
|
||||
int screen_id_r;
|
||||
int layer;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 position = vec4(vert_position, 0.0, 1.0) * modelview_matrix;
|
||||
gl_Position = vec4(position.x, -position.y, 0.0, 1.0);
|
||||
frag_tex_coord = vert_tex_coord;
|
||||
}
|
40
src/video_core/host_shaders/vulkan_present_anaglyph.frag
Normal file
40
src/video_core/host_shaders/vulkan_present_anaglyph.frag
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#version 450 core
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout (location = 0) in vec2 frag_tex_coord;
|
||||
layout (location = 0) out vec4 color;
|
||||
|
||||
// Anaglyph Red-Cyan shader based on Dubois algorithm
|
||||
// Constants taken from the paper:
|
||||
// "Conversion of a Stereo Pair to Anaglyph with
|
||||
// the Least-Squares Projection Method"
|
||||
// Eric Dubois, March 2009
|
||||
const mat3 l = mat3( 0.437, 0.449, 0.164,
|
||||
-0.062,-0.062,-0.024,
|
||||
-0.048,-0.050,-0.017);
|
||||
const mat3 r = mat3(-0.011,-0.032,-0.007,
|
||||
0.377, 0.761, 0.009,
|
||||
-0.026,-0.093, 1.234);
|
||||
|
||||
layout (push_constant, std140) uniform DrawInfo {
|
||||
mat4 modelview_matrix;
|
||||
vec4 i_resolution;
|
||||
vec4 o_resolution;
|
||||
int screen_id_l;
|
||||
int screen_id_r;
|
||||
int layer;
|
||||
int reverse_interlaced;
|
||||
};
|
||||
|
||||
layout (set = 0, binding = 0) uniform texture2D screen_textures[3];
|
||||
layout (set = 0, binding = 1) uniform sampler screen_sampler;
|
||||
|
||||
void main() {
|
||||
vec4 color_tex_l = texture(sampler2D(screen_textures[screen_id_l], screen_sampler), frag_tex_coord);
|
||||
vec4 color_tex_r = texture(sampler2D(screen_textures[screen_id_r], screen_sampler), frag_tex_coord);
|
||||
color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a);
|
||||
}
|
30
src/video_core/host_shaders/vulkan_present_interlaced.frag
Normal file
30
src/video_core/host_shaders/vulkan_present_interlaced.frag
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#version 450 core
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout (location = 0) in vec2 frag_tex_coord;
|
||||
layout (location = 0) out vec4 color;
|
||||
|
||||
layout (push_constant, std140) uniform DrawInfo {
|
||||
mat4 modelview_matrix;
|
||||
vec4 i_resolution;
|
||||
vec4 o_resolution;
|
||||
int screen_id_l;
|
||||
int screen_id_r;
|
||||
int layer;
|
||||
int reverse_interlaced;
|
||||
};
|
||||
|
||||
layout (set = 0, binding = 0) uniform texture2D screen_textures[3];
|
||||
layout (set = 0, binding = 1) uniform sampler screen_sampler;
|
||||
|
||||
void main() {
|
||||
float screen_row = o_resolution.x * frag_tex_coord.x;
|
||||
if (int(screen_row) % 2 == reverse_interlaced)
|
||||
color = texture(sampler2D(screen_textures[screen_id_l], screen_sampler), frag_tex_coord);
|
||||
else
|
||||
color = texture(sampler2D(screen_textures[screen_id_r], screen_sampler), frag_tex_coord);
|
||||
}
|
@ -40,8 +40,8 @@ void Zero(T& o) {
|
||||
State::State() : geometry_pipeline(*this) {
|
||||
auto SubmitVertex = [this](const Shader::AttributeBuffer& vertex) {
|
||||
using Pica::Shader::OutputVertex;
|
||||
auto AddTriangle = [this](const OutputVertex& v0, const OutputVertex& v1,
|
||||
const OutputVertex& v2) {
|
||||
auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
|
||||
const OutputVertex& v2) {
|
||||
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
|
||||
};
|
||||
primitive_assembler.SubmitVertex(
|
||||
|
840
src/video_core/rasterizer_accelerated.cpp
Normal file
840
src/video_core/rasterizer_accelerated.cpp
Normal file
@ -0,0 +1,840 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <limits>
|
||||
#include "common/alignment.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/rasterizer_accelerated.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
static Common::Vec4f ColorRGBA8(const u32 color) {
|
||||
const auto rgba =
|
||||
Common::Vec4u{color >> 0 & 0xFF, color >> 8 & 0xFF, color >> 16 & 0xFF, color >> 24 & 0xFF};
|
||||
return rgba / 255.0f;
|
||||
}
|
||||
|
||||
static Common::Vec3f LightColor(const Pica::LightingRegs::LightColor& color) {
|
||||
return Common::Vec3u{color.r, color.g, color.b} / 255.0f;
|
||||
}
|
||||
|
||||
RasterizerAccelerated::HardwareVertex::HardwareVertex(const Pica::Shader::OutputVertex& v,
|
||||
bool flip_quaternion) {
|
||||
position[0] = v.pos.x.ToFloat32();
|
||||
position[1] = v.pos.y.ToFloat32();
|
||||
position[2] = v.pos.z.ToFloat32();
|
||||
position[3] = v.pos.w.ToFloat32();
|
||||
color[0] = v.color.x.ToFloat32();
|
||||
color[1] = v.color.y.ToFloat32();
|
||||
color[2] = v.color.z.ToFloat32();
|
||||
color[3] = v.color.w.ToFloat32();
|
||||
tex_coord0[0] = v.tc0.x.ToFloat32();
|
||||
tex_coord0[1] = v.tc0.y.ToFloat32();
|
||||
tex_coord1[0] = v.tc1.x.ToFloat32();
|
||||
tex_coord1[1] = v.tc1.y.ToFloat32();
|
||||
tex_coord2[0] = v.tc2.x.ToFloat32();
|
||||
tex_coord2[1] = v.tc2.y.ToFloat32();
|
||||
tex_coord0_w = v.tc0_w.ToFloat32();
|
||||
normquat[0] = v.quat.x.ToFloat32();
|
||||
normquat[1] = v.quat.y.ToFloat32();
|
||||
normquat[2] = v.quat.z.ToFloat32();
|
||||
normquat[3] = v.quat.w.ToFloat32();
|
||||
view[0] = v.view.x.ToFloat32();
|
||||
view[1] = v.view.y.ToFloat32();
|
||||
view[2] = v.view.z.ToFloat32();
|
||||
|
||||
if (flip_quaternion) {
|
||||
normquat = -normquat;
|
||||
}
|
||||
}
|
||||
|
||||
RasterizerAccelerated::RasterizerAccelerated() {
|
||||
uniform_block_data.lighting_lut_dirty.fill(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a helper function to resolve an issue when interpolating opposite quaternions. See below
|
||||
* for a detailed description of this issue (yuriks):
|
||||
*
|
||||
* For any rotation, there are two quaternions Q, and -Q, that represent the same rotation. If you
|
||||
* interpolate two quaternions that are opposite, instead of going from one rotation to another
|
||||
* using the shortest path, you'll go around the longest path. You can test if two quaternions are
|
||||
* opposite by checking if Dot(Q1, Q2) < 0. In that case, you can flip either of them, therefore
|
||||
* making Dot(Q1, -Q2) positive.
|
||||
*
|
||||
* This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This is
|
||||
* correct for most cases but can still rotate around the long way sometimes. An implementation
|
||||
* which did `lerp(lerp(Q1, Q2), Q3)` (with proper weighting), applying the dot product check
|
||||
* between each step would work for those cases at the cost of being more complex to implement.
|
||||
*
|
||||
* Fortunately however, the 3DS hardware happens to also use this exact same logic to work around
|
||||
* these issues, making this basic implementation actually more accurate to the hardware.
|
||||
*/
|
||||
static bool AreQuaternionsOpposite(Common::Vec4<Pica::float24> qa, Common::Vec4<Pica::float24> qb) {
|
||||
Common::Vec4f a{qa.x.ToFloat32(), qa.y.ToFloat32(), qa.z.ToFloat32(), qa.w.ToFloat32()};
|
||||
Common::Vec4f b{qb.x.ToFloat32(), qb.y.ToFloat32(), qb.z.ToFloat32(), qb.w.ToFloat32()};
|
||||
|
||||
return (Common::Dot(a, b) < 0.f);
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::AddTriangle(const Pica::Shader::OutputVertex& v0,
|
||||
const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) {
|
||||
vertex_batch.emplace_back(v0, false);
|
||||
vertex_batch.emplace_back(v1, AreQuaternionsOpposite(v0.quat, v1.quat));
|
||||
vertex_batch.emplace_back(v2, AreQuaternionsOpposite(v0.quat, v2.quat));
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::UpdatePagesCachedCount(PAddr addr, u32 size, int delta) {
|
||||
const u32 page_start = addr >> Memory::CITRA_PAGE_BITS;
|
||||
const u32 page_end = ((addr + size - 1) >> Memory::CITRA_PAGE_BITS) + 1;
|
||||
|
||||
u32 uncache_start_addr = 0;
|
||||
u32 cache_start_addr = 0;
|
||||
u32 uncache_bytes = 0;
|
||||
u32 cache_bytes = 0;
|
||||
|
||||
for (u32 page = page_start; page != page_end; page++) {
|
||||
auto& count = cached_pages.at(page);
|
||||
|
||||
// Ensure no overflow happens
|
||||
if (delta > 0) {
|
||||
ASSERT_MSG(count < std::numeric_limits<u16>::max(), "Count will overflow!");
|
||||
} else if (delta < 0) {
|
||||
ASSERT_MSG(count > 0, "Count will underflow!");
|
||||
} else {
|
||||
ASSERT_MSG(false, "Delta must be non-zero!");
|
||||
}
|
||||
|
||||
// Adds or subtracts 1, as count is a unsigned 8-bit value
|
||||
count += delta;
|
||||
|
||||
// Assume delta is either -1 or 1
|
||||
if (count == 0) {
|
||||
if (uncache_bytes == 0) {
|
||||
uncache_start_addr = page << Memory::CITRA_PAGE_BITS;
|
||||
}
|
||||
|
||||
uncache_bytes += Memory::CITRA_PAGE_SIZE;
|
||||
} else if (uncache_bytes > 0) {
|
||||
VideoCore::g_memory->RasterizerMarkRegionCached(uncache_start_addr, uncache_bytes,
|
||||
false);
|
||||
uncache_bytes = 0;
|
||||
}
|
||||
|
||||
if (count == 1 && delta > 0) {
|
||||
if (cache_bytes == 0) {
|
||||
cache_start_addr = page << Memory::CITRA_PAGE_BITS;
|
||||
}
|
||||
|
||||
cache_bytes += Memory::CITRA_PAGE_SIZE;
|
||||
} else if (cache_bytes > 0) {
|
||||
VideoCore::g_memory->RasterizerMarkRegionCached(cache_start_addr, cache_bytes, true);
|
||||
|
||||
cache_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (uncache_bytes > 0) {
|
||||
VideoCore::g_memory->RasterizerMarkRegionCached(uncache_start_addr, uncache_bytes, false);
|
||||
}
|
||||
|
||||
if (cache_bytes > 0) {
|
||||
VideoCore::g_memory->RasterizerMarkRegionCached(cache_start_addr, cache_bytes, true);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::ClearAll(bool flush) {
|
||||
// Force flush all surfaces from the cache
|
||||
if (flush) {
|
||||
FlushRegion(0x0, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
u32 uncache_start_addr = 0;
|
||||
u32 uncache_bytes = 0;
|
||||
|
||||
for (u32 page = 0; page != cached_pages.size(); page++) {
|
||||
auto& count = cached_pages.at(page);
|
||||
|
||||
// Assume delta is either -1 or 1
|
||||
if (count != 0) {
|
||||
if (uncache_bytes == 0) {
|
||||
uncache_start_addr = page << Memory::CITRA_PAGE_BITS;
|
||||
}
|
||||
|
||||
uncache_bytes += Memory::CITRA_PAGE_SIZE;
|
||||
} else if (uncache_bytes > 0) {
|
||||
VideoCore::g_memory->RasterizerMarkRegionCached(uncache_start_addr, uncache_bytes,
|
||||
false);
|
||||
uncache_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (uncache_bytes > 0) {
|
||||
VideoCore::g_memory->RasterizerMarkRegionCached(uncache_start_addr, uncache_bytes, false);
|
||||
}
|
||||
|
||||
cached_pages = {};
|
||||
}
|
||||
|
||||
RasterizerAccelerated::VertexArrayInfo RasterizerAccelerated::AnalyzeVertexArray(bool is_indexed) {
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
const auto& vertex_attributes = regs.pipeline.vertex_attributes;
|
||||
|
||||
u32 vertex_min;
|
||||
u32 vertex_max;
|
||||
if (is_indexed) {
|
||||
const auto& index_info = regs.pipeline.index_array;
|
||||
const PAddr address = vertex_attributes.GetPhysicalBaseAddress() + index_info.offset;
|
||||
const u8* index_address_8 = VideoCore::g_memory->GetPhysicalPointer(address);
|
||||
const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8);
|
||||
const bool index_u16 = index_info.format != 0;
|
||||
|
||||
vertex_min = 0xFFFF;
|
||||
vertex_max = 0;
|
||||
const u32 size = regs.pipeline.num_vertices * (index_u16 ? 2 : 1);
|
||||
FlushRegion(address, size);
|
||||
for (u32 index = 0; index < regs.pipeline.num_vertices; ++index) {
|
||||
const u32 vertex = index_u16 ? index_address_16[index] : index_address_8[index];
|
||||
vertex_min = std::min(vertex_min, vertex);
|
||||
vertex_max = std::max(vertex_max, vertex);
|
||||
}
|
||||
} else {
|
||||
vertex_min = regs.pipeline.vertex_offset;
|
||||
vertex_max = regs.pipeline.vertex_offset + regs.pipeline.num_vertices - 1;
|
||||
}
|
||||
|
||||
const u32 vertex_num = vertex_max - vertex_min + 1;
|
||||
u32 vs_input_size = 0;
|
||||
for (const auto& loader : vertex_attributes.attribute_loaders) {
|
||||
if (loader.component_count != 0) {
|
||||
vs_input_size += Common::AlignUp(loader.byte_count * vertex_num, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return {vertex_min, vertex_max, vs_input_size};
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::NotifyPicaRegisterChanged(u32 id) {
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
|
||||
switch (id) {
|
||||
// Depth modifiers
|
||||
case PICA_REG_INDEX(rasterizer.viewport_depth_range):
|
||||
SyncDepthScale();
|
||||
break;
|
||||
case PICA_REG_INDEX(rasterizer.viewport_depth_near_plane):
|
||||
SyncDepthOffset();
|
||||
break;
|
||||
|
||||
// Depth buffering
|
||||
case PICA_REG_INDEX(rasterizer.depthmap_enable):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// Shadow texture
|
||||
case PICA_REG_INDEX(texturing.shadow):
|
||||
SyncShadowTextureBias();
|
||||
break;
|
||||
|
||||
// Fog state
|
||||
case PICA_REG_INDEX(texturing.fog_color):
|
||||
SyncFogColor();
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[0]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[1]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[2]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[3]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[4]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[5]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[6]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[7]):
|
||||
uniform_block_data.fog_lut_dirty = true;
|
||||
break;
|
||||
|
||||
// ProcTex state
|
||||
case PICA_REG_INDEX(texturing.proctex):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_offset):
|
||||
SyncProcTexBias();
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(texturing.proctex_noise_u):
|
||||
case PICA_REG_INDEX(texturing.proctex_noise_v):
|
||||
case PICA_REG_INDEX(texturing.proctex_noise_frequency):
|
||||
SyncProcTexNoise();
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[0]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[1]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[2]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[3]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[4]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[5]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[6]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[7]):
|
||||
using Pica::TexturingRegs;
|
||||
switch (regs.texturing.proctex_lut_config.ref_table.Value()) {
|
||||
case TexturingRegs::ProcTexLutTable::Noise:
|
||||
uniform_block_data.proctex_noise_lut_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::ColorMap:
|
||||
uniform_block_data.proctex_color_map_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::AlphaMap:
|
||||
uniform_block_data.proctex_alpha_map_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::Color:
|
||||
uniform_block_data.proctex_lut_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::ColorDiff:
|
||||
uniform_block_data.proctex_diff_lut_dirty = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
// Alpha test
|
||||
case PICA_REG_INDEX(framebuffer.output_merger.alpha_test):
|
||||
SyncAlphaTest();
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(framebuffer.shadow):
|
||||
SyncShadowBias();
|
||||
break;
|
||||
|
||||
// Scissor test
|
||||
case PICA_REG_INDEX(rasterizer.scissor_test.mode):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(texturing.main_config):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// Texture 0 type
|
||||
case PICA_REG_INDEX(texturing.texture0.type):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// TEV stages
|
||||
// (This also syncs fog_mode and fog_flip which are part of tev_combiner_buffer_input)
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_combiner_buffer_input):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.const_r):
|
||||
SyncTevConstColor(0, regs.texturing.tev_stage0);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.const_r):
|
||||
SyncTevConstColor(1, regs.texturing.tev_stage1);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.const_r):
|
||||
SyncTevConstColor(2, regs.texturing.tev_stage2);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.const_r):
|
||||
SyncTevConstColor(3, regs.texturing.tev_stage3);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.const_r):
|
||||
SyncTevConstColor(4, regs.texturing.tev_stage4);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.const_r):
|
||||
SyncTevConstColor(5, regs.texturing.tev_stage5);
|
||||
break;
|
||||
|
||||
// TEV combiner buffer color
|
||||
case PICA_REG_INDEX(texturing.tev_combiner_buffer_color):
|
||||
SyncCombinerColor();
|
||||
break;
|
||||
|
||||
// Fragment lighting switches
|
||||
case PICA_REG_INDEX(lighting.disable):
|
||||
case PICA_REG_INDEX(lighting.max_light_index):
|
||||
case PICA_REG_INDEX(lighting.config0):
|
||||
case PICA_REG_INDEX(lighting.config1):
|
||||
case PICA_REG_INDEX(lighting.abs_lut_input):
|
||||
case PICA_REG_INDEX(lighting.lut_input):
|
||||
case PICA_REG_INDEX(lighting.lut_scale):
|
||||
case PICA_REG_INDEX(lighting.light_enable):
|
||||
break;
|
||||
|
||||
// Fragment lighting specular 0 color
|
||||
case PICA_REG_INDEX(lighting.light[0].specular_0):
|
||||
SyncLightSpecular0(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].specular_0):
|
||||
SyncLightSpecular0(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].specular_0):
|
||||
SyncLightSpecular0(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].specular_0):
|
||||
SyncLightSpecular0(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].specular_0):
|
||||
SyncLightSpecular0(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].specular_0):
|
||||
SyncLightSpecular0(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].specular_0):
|
||||
SyncLightSpecular0(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].specular_0):
|
||||
SyncLightSpecular0(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting specular 1 color
|
||||
case PICA_REG_INDEX(lighting.light[0].specular_1):
|
||||
SyncLightSpecular1(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].specular_1):
|
||||
SyncLightSpecular1(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].specular_1):
|
||||
SyncLightSpecular1(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].specular_1):
|
||||
SyncLightSpecular1(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].specular_1):
|
||||
SyncLightSpecular1(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].specular_1):
|
||||
SyncLightSpecular1(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].specular_1):
|
||||
SyncLightSpecular1(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].specular_1):
|
||||
SyncLightSpecular1(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting diffuse color
|
||||
case PICA_REG_INDEX(lighting.light[0].diffuse):
|
||||
SyncLightDiffuse(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].diffuse):
|
||||
SyncLightDiffuse(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].diffuse):
|
||||
SyncLightDiffuse(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].diffuse):
|
||||
SyncLightDiffuse(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].diffuse):
|
||||
SyncLightDiffuse(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].diffuse):
|
||||
SyncLightDiffuse(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].diffuse):
|
||||
SyncLightDiffuse(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].diffuse):
|
||||
SyncLightDiffuse(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting ambient color
|
||||
case PICA_REG_INDEX(lighting.light[0].ambient):
|
||||
SyncLightAmbient(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].ambient):
|
||||
SyncLightAmbient(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].ambient):
|
||||
SyncLightAmbient(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].ambient):
|
||||
SyncLightAmbient(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].ambient):
|
||||
SyncLightAmbient(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].ambient):
|
||||
SyncLightAmbient(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].ambient):
|
||||
SyncLightAmbient(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].ambient):
|
||||
SyncLightAmbient(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting position
|
||||
case PICA_REG_INDEX(lighting.light[0].x):
|
||||
case PICA_REG_INDEX(lighting.light[0].z):
|
||||
SyncLightPosition(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].x):
|
||||
case PICA_REG_INDEX(lighting.light[1].z):
|
||||
SyncLightPosition(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].x):
|
||||
case PICA_REG_INDEX(lighting.light[2].z):
|
||||
SyncLightPosition(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].x):
|
||||
case PICA_REG_INDEX(lighting.light[3].z):
|
||||
SyncLightPosition(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].x):
|
||||
case PICA_REG_INDEX(lighting.light[4].z):
|
||||
SyncLightPosition(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].x):
|
||||
case PICA_REG_INDEX(lighting.light[5].z):
|
||||
SyncLightPosition(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].x):
|
||||
case PICA_REG_INDEX(lighting.light[6].z):
|
||||
SyncLightPosition(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].x):
|
||||
case PICA_REG_INDEX(lighting.light[7].z):
|
||||
SyncLightPosition(7);
|
||||
break;
|
||||
|
||||
// Fragment spot lighting direction
|
||||
case PICA_REG_INDEX(lighting.light[0].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[0].spot_z):
|
||||
SyncLightSpotDirection(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[1].spot_z):
|
||||
SyncLightSpotDirection(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[2].spot_z):
|
||||
SyncLightSpotDirection(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[3].spot_z):
|
||||
SyncLightSpotDirection(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[4].spot_z):
|
||||
SyncLightSpotDirection(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[5].spot_z):
|
||||
SyncLightSpotDirection(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[6].spot_z):
|
||||
SyncLightSpotDirection(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[7].spot_z):
|
||||
SyncLightSpotDirection(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting light source config
|
||||
case PICA_REG_INDEX(lighting.light[0].config):
|
||||
case PICA_REG_INDEX(lighting.light[1].config):
|
||||
case PICA_REG_INDEX(lighting.light[2].config):
|
||||
case PICA_REG_INDEX(lighting.light[3].config):
|
||||
case PICA_REG_INDEX(lighting.light[4].config):
|
||||
case PICA_REG_INDEX(lighting.light[5].config):
|
||||
case PICA_REG_INDEX(lighting.light[6].config):
|
||||
case PICA_REG_INDEX(lighting.light[7].config):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// Fragment lighting distance attenuation bias
|
||||
case PICA_REG_INDEX(lighting.light[0].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting distance attenuation scale
|
||||
case PICA_REG_INDEX(lighting.light[0].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting global ambient color (emission + ambient * ambient)
|
||||
case PICA_REG_INDEX(lighting.global_ambient):
|
||||
SyncGlobalAmbient();
|
||||
break;
|
||||
|
||||
// Fragment lighting lookup tables
|
||||
case PICA_REG_INDEX(lighting.lut_data[0]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[1]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[2]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[3]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[4]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[5]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[6]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[7]): {
|
||||
const auto& lut_config = regs.lighting.lut_config;
|
||||
uniform_block_data.lighting_lut_dirty[lut_config.type] = true;
|
||||
uniform_block_data.lighting_lut_dirty_any = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Forward registers that map to fixed function API features to the video backend
|
||||
NotifyFixedFunctionPicaRegisterChanged(id);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncDepthScale() {
|
||||
float depth_scale =
|
||||
Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_range).ToFloat32();
|
||||
|
||||
if (depth_scale != uniform_block_data.data.depth_scale) {
|
||||
uniform_block_data.data.depth_scale = depth_scale;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncDepthOffset() {
|
||||
float depth_offset =
|
||||
Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_near_plane).ToFloat32();
|
||||
|
||||
if (depth_offset != uniform_block_data.data.depth_offset) {
|
||||
uniform_block_data.data.depth_offset = depth_offset;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncFogColor() {
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
uniform_block_data.data.fog_color = {
|
||||
regs.texturing.fog_color.r.Value() / 255.0f,
|
||||
regs.texturing.fog_color.g.Value() / 255.0f,
|
||||
regs.texturing.fog_color.b.Value() / 255.0f,
|
||||
};
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncProcTexNoise() {
|
||||
const auto& regs = Pica::g_state.regs.texturing;
|
||||
uniform_block_data.data.proctex_noise_f = {
|
||||
Pica::float16::FromRaw(regs.proctex_noise_frequency.u).ToFloat32(),
|
||||
Pica::float16::FromRaw(regs.proctex_noise_frequency.v).ToFloat32(),
|
||||
};
|
||||
uniform_block_data.data.proctex_noise_a = {
|
||||
regs.proctex_noise_u.amplitude / 4095.0f,
|
||||
regs.proctex_noise_v.amplitude / 4095.0f,
|
||||
};
|
||||
uniform_block_data.data.proctex_noise_p = {
|
||||
Pica::float16::FromRaw(regs.proctex_noise_u.phase).ToFloat32(),
|
||||
Pica::float16::FromRaw(regs.proctex_noise_v.phase).ToFloat32(),
|
||||
};
|
||||
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncProcTexBias() {
|
||||
const auto& regs = Pica::g_state.regs.texturing;
|
||||
uniform_block_data.data.proctex_bias =
|
||||
Pica::float16::FromRaw(regs.proctex.bias_low | (regs.proctex_lut.bias_high << 8))
|
||||
.ToFloat32();
|
||||
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncAlphaTest() {
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
if (regs.framebuffer.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) {
|
||||
uniform_block_data.data.alphatest_ref = regs.framebuffer.output_merger.alpha_test.ref;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncCombinerColor() {
|
||||
auto combiner_color = ColorRGBA8(Pica::g_state.regs.texturing.tev_combiner_buffer_color.raw);
|
||||
if (combiner_color != uniform_block_data.data.tev_combiner_buffer_color) {
|
||||
uniform_block_data.data.tev_combiner_buffer_color = combiner_color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncTevConstColor(
|
||||
std::size_t stage_index, const Pica::TexturingRegs::TevStageConfig& tev_stage) {
|
||||
const auto const_color = ColorRGBA8(tev_stage.const_color);
|
||||
|
||||
if (const_color == uniform_block_data.data.const_color[stage_index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
uniform_block_data.data.const_color[stage_index] = const_color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncGlobalAmbient() {
|
||||
auto color = LightColor(Pica::g_state.regs.lighting.global_ambient);
|
||||
if (color != uniform_block_data.data.lighting_global_ambient) {
|
||||
uniform_block_data.data.lighting_global_ambient = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightSpecular0(int light_index) {
|
||||
auto color = LightColor(Pica::g_state.regs.lighting.light[light_index].specular_0);
|
||||
if (color != uniform_block_data.data.light_src[light_index].specular_0) {
|
||||
uniform_block_data.data.light_src[light_index].specular_0 = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightSpecular1(int light_index) {
|
||||
auto color = LightColor(Pica::g_state.regs.lighting.light[light_index].specular_1);
|
||||
if (color != uniform_block_data.data.light_src[light_index].specular_1) {
|
||||
uniform_block_data.data.light_src[light_index].specular_1 = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightDiffuse(int light_index) {
|
||||
auto color = LightColor(Pica::g_state.regs.lighting.light[light_index].diffuse);
|
||||
if (color != uniform_block_data.data.light_src[light_index].diffuse) {
|
||||
uniform_block_data.data.light_src[light_index].diffuse = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightAmbient(int light_index) {
|
||||
auto color = LightColor(Pica::g_state.regs.lighting.light[light_index].ambient);
|
||||
if (color != uniform_block_data.data.light_src[light_index].ambient) {
|
||||
uniform_block_data.data.light_src[light_index].ambient = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightPosition(int light_index) {
|
||||
const Common::Vec3f position = {
|
||||
Pica::float16::FromRaw(Pica::g_state.regs.lighting.light[light_index].x).ToFloat32(),
|
||||
Pica::float16::FromRaw(Pica::g_state.regs.lighting.light[light_index].y).ToFloat32(),
|
||||
Pica::float16::FromRaw(Pica::g_state.regs.lighting.light[light_index].z).ToFloat32()};
|
||||
|
||||
if (position != uniform_block_data.data.light_src[light_index].position) {
|
||||
uniform_block_data.data.light_src[light_index].position = position;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightSpotDirection(int light_index) {
|
||||
const auto& light = Pica::g_state.regs.lighting.light[light_index];
|
||||
const auto spot_direction =
|
||||
Common::Vec3f{light.spot_x / 2047.0f, light.spot_y / 2047.0f, light.spot_z / 2047.0f};
|
||||
|
||||
if (spot_direction != uniform_block_data.data.light_src[light_index].spot_direction) {
|
||||
uniform_block_data.data.light_src[light_index].spot_direction = spot_direction;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightDistanceAttenuationBias(int light_index) {
|
||||
float dist_atten_bias =
|
||||
Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_bias)
|
||||
.ToFloat32();
|
||||
|
||||
if (dist_atten_bias != uniform_block_data.data.light_src[light_index].dist_atten_bias) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_bias = dist_atten_bias;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightDistanceAttenuationScale(int light_index) {
|
||||
float dist_atten_scale =
|
||||
Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_scale)
|
||||
.ToFloat32();
|
||||
|
||||
if (dist_atten_scale != uniform_block_data.data.light_src[light_index].dist_atten_scale) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_scale = dist_atten_scale;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncShadowBias() {
|
||||
const auto& shadow = Pica::g_state.regs.framebuffer.shadow;
|
||||
float constant = Pica::float16::FromRaw(shadow.constant).ToFloat32();
|
||||
float linear = Pica::float16::FromRaw(shadow.linear).ToFloat32();
|
||||
|
||||
if (constant != uniform_block_data.data.shadow_bias_constant ||
|
||||
linear != uniform_block_data.data.shadow_bias_linear) {
|
||||
uniform_block_data.data.shadow_bias_constant = constant;
|
||||
uniform_block_data.data.shadow_bias_linear = linear;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncShadowTextureBias() {
|
||||
int bias = Pica::g_state.regs.texturing.shadow.bias << 1;
|
||||
if (bias != uniform_block_data.data.shadow_texture_bias) {
|
||||
uniform_block_data.data.shadow_texture_bias = bias;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
142
src/video_core/rasterizer_accelerated.h
Normal file
142
src/video_core/rasterizer_accelerated.h
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/shader/shader_uniforms.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class RasterizerAccelerated : public RasterizerInterface {
|
||||
public:
|
||||
RasterizerAccelerated();
|
||||
virtual ~RasterizerAccelerated() = default;
|
||||
|
||||
void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) override;
|
||||
|
||||
void UpdatePagesCachedCount(PAddr addr, u32 size, int delta) override;
|
||||
void NotifyPicaRegisterChanged(u32 id) override;
|
||||
void ClearAll(bool flush) override;
|
||||
|
||||
protected:
|
||||
/// Notifies that a fixed function PICA register changed to the video backend
|
||||
virtual void NotifyFixedFunctionPicaRegisterChanged(u32 id) = 0;
|
||||
|
||||
/// Syncs the depth scale to match the PICA register
|
||||
void SyncDepthScale();
|
||||
|
||||
/// Syncs the depth offset to match the PICA register
|
||||
void SyncDepthOffset();
|
||||
|
||||
/// Syncs the fog states to match the PICA register
|
||||
void SyncFogColor();
|
||||
|
||||
/// Sync the procedural texture noise configuration to match the PICA register
|
||||
void SyncProcTexNoise();
|
||||
|
||||
/// Sync the procedural texture bias configuration to match the PICA register
|
||||
void SyncProcTexBias();
|
||||
|
||||
/// Syncs the alpha test states to match the PICA register
|
||||
void SyncAlphaTest();
|
||||
|
||||
/// Syncs the TEV combiner color buffer to match the PICA register
|
||||
void SyncCombinerColor();
|
||||
|
||||
/// Syncs the TEV constant color to match the PICA register
|
||||
void SyncTevConstColor(std::size_t tev_index,
|
||||
const Pica::TexturingRegs::TevStageConfig& tev_stage);
|
||||
|
||||
/// Syncs the lighting global ambient color to match the PICA register
|
||||
void SyncGlobalAmbient();
|
||||
|
||||
/// Syncs the specified light's specular 0 color to match the PICA register
|
||||
void SyncLightSpecular0(int light_index);
|
||||
|
||||
/// Syncs the specified light's specular 1 color to match the PICA register
|
||||
void SyncLightSpecular1(int light_index);
|
||||
|
||||
/// Syncs the specified light's diffuse color to match the PICA register
|
||||
void SyncLightDiffuse(int light_index);
|
||||
|
||||
/// Syncs the specified light's ambient color to match the PICA register
|
||||
void SyncLightAmbient(int light_index);
|
||||
|
||||
/// Syncs the specified light's position to match the PICA register
|
||||
void SyncLightPosition(int light_index);
|
||||
|
||||
/// Syncs the specified spot light direcition to match the PICA register
|
||||
void SyncLightSpotDirection(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation bias to match the PICA register
|
||||
void SyncLightDistanceAttenuationBias(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation scale to match the PICA register
|
||||
void SyncLightDistanceAttenuationScale(int light_index);
|
||||
|
||||
/// Syncs the shadow rendering bias to match the PICA register
|
||||
void SyncShadowBias();
|
||||
|
||||
/// Syncs the shadow texture bias to match the PICA register
|
||||
void SyncShadowTextureBias();
|
||||
|
||||
protected:
|
||||
/// Structure that keeps tracks of the uniform state
|
||||
struct UniformBlockData {
|
||||
Pica::Shader::UniformData data{};
|
||||
std::array<bool, Pica::LightingRegs::NumLightingSampler> lighting_lut_dirty{};
|
||||
bool lighting_lut_dirty_any = true;
|
||||
bool fog_lut_dirty = true;
|
||||
bool proctex_noise_lut_dirty = true;
|
||||
bool proctex_color_map_dirty = true;
|
||||
bool proctex_alpha_map_dirty = true;
|
||||
bool proctex_lut_dirty = true;
|
||||
bool proctex_diff_lut_dirty = true;
|
||||
bool dirty = true;
|
||||
};
|
||||
|
||||
/// Structure that the hardware rendered vertices are composed of
|
||||
struct HardwareVertex {
|
||||
HardwareVertex() = default;
|
||||
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion);
|
||||
|
||||
Common::Vec4f position;
|
||||
Common::Vec4f color;
|
||||
Common::Vec2f tex_coord0;
|
||||
Common::Vec2f tex_coord1;
|
||||
Common::Vec2f tex_coord2;
|
||||
float tex_coord0_w;
|
||||
Common::Vec4f normquat;
|
||||
Common::Vec3f view;
|
||||
};
|
||||
|
||||
struct VertexArrayInfo {
|
||||
u32 vs_input_index_min;
|
||||
u32 vs_input_index_max;
|
||||
u32 vs_input_size;
|
||||
};
|
||||
|
||||
/// Retrieve the range and the size of the input vertex
|
||||
VertexArrayInfo AnalyzeVertexArray(bool is_indexed);
|
||||
|
||||
protected:
|
||||
std::array<u16, 0x30000> cached_pages{};
|
||||
std::vector<HardwareVertex> vertex_batch;
|
||||
bool shader_dirty = true;
|
||||
|
||||
UniformBlockData uniform_block_data{};
|
||||
std::array<std::array<Common::Vec2f, 256>, Pica::LightingRegs::NumLightingSampler>
|
||||
lighting_lut_data{};
|
||||
std::array<Common::Vec2f, 128> fog_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_noise_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_color_map_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_alpha_map_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_lut_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_diff_lut_data{};
|
||||
};
|
||||
} // namespace VideoCore
|
@ -1,480 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/texture.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/rasterizer_cache/cached_surface.h"
|
||||
#include "video_core/rasterizer_cache/morton_swizzle.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/texture_downloader_es.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
static Aspect ToAspect(SurfaceType type) {
|
||||
switch (type) {
|
||||
case SurfaceType::Color:
|
||||
case SurfaceType::Texture:
|
||||
case SurfaceType::Fill:
|
||||
return Aspect::Color;
|
||||
case SurfaceType::Depth:
|
||||
return Aspect::Depth;
|
||||
case SurfaceType::DepthStencil:
|
||||
return Aspect::DepthStencil;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unknown SurfaceType {}", type);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return Aspect::Color;
|
||||
}
|
||||
|
||||
CachedSurface::~CachedSurface() {
|
||||
if (texture.handle) {
|
||||
auto tag = is_custom ? HostTextureTag{GetFormatTuple(PixelFormat::RGBA8),
|
||||
custom_tex_info.width, custom_tex_info.height}
|
||||
: HostTextureTag{GetFormatTuple(pixel_format), GetScaledWidth(),
|
||||
GetScaledHeight()};
|
||||
|
||||
owner.host_texture_recycler.emplace(tag, std::move(texture));
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_SurfaceLoad, "RasterizerCache", "Surface Load",
|
||||
MP_RGB(128, 192, 64));
|
||||
void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
|
||||
ASSERT(type != SurfaceType::Fill);
|
||||
const bool need_swap =
|
||||
GLES && (pixel_format == PixelFormat::RGBA8 || pixel_format == PixelFormat::RGB8);
|
||||
|
||||
const u8* const texture_src_data = VideoCore::g_memory->GetPhysicalPointer(addr);
|
||||
if (texture_src_data == nullptr)
|
||||
return;
|
||||
|
||||
if (gl_buffer.empty()) {
|
||||
gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format));
|
||||
}
|
||||
|
||||
// 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(RasterizerCache_SurfaceLoad);
|
||||
|
||||
ASSERT(load_start >= addr && load_end <= end);
|
||||
const u32 start_offset = load_start - addr;
|
||||
|
||||
if (!is_tiled) {
|
||||
ASSERT(type == SurfaceType::Color);
|
||||
if (need_swap) {
|
||||
// TODO(liushuyu): check if the byteswap here is 100% correct
|
||||
// cannot fully test this
|
||||
if (pixel_format == PixelFormat::RGBA8) {
|
||||
for (std::size_t i = start_offset; i < load_end - addr; i += 4) {
|
||||
gl_buffer[i] = texture_src_data[i + 3];
|
||||
gl_buffer[i + 1] = texture_src_data[i + 2];
|
||||
gl_buffer[i + 2] = texture_src_data[i + 1];
|
||||
gl_buffer[i + 3] = texture_src_data[i];
|
||||
}
|
||||
} else if (pixel_format == PixelFormat::RGB8) {
|
||||
for (std::size_t i = start_offset; i < load_end - addr; i += 3) {
|
||||
gl_buffer[i] = texture_src_data[i + 2];
|
||||
gl_buffer[i + 1] = texture_src_data[i + 1];
|
||||
gl_buffer[i + 2] = texture_src_data[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset,
|
||||
load_end - load_start);
|
||||
}
|
||||
} else {
|
||||
if (type == SurfaceType::Texture) {
|
||||
Pica::Texture::TextureInfo tex_info{};
|
||||
tex_info.width = width;
|
||||
tex_info.height = height;
|
||||
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(pixel_format);
|
||||
tex_info.SetDefaultStride();
|
||||
tex_info.physical_address = addr;
|
||||
|
||||
const SurfaceInterval load_interval(load_start, load_end);
|
||||
const auto rect = GetSubRect(FromInterval(load_interval));
|
||||
ASSERT(FromInterval(load_interval).GetInterval() == load_interval);
|
||||
|
||||
for (unsigned y = rect.bottom; y < rect.top; ++y) {
|
||||
for (unsigned x = rect.left; x < rect.right; ++x) {
|
||||
auto vec4 =
|
||||
Pica::Texture::LookupTexture(texture_src_data, x, height - 1 - y, tex_info);
|
||||
const std::size_t offset = (x + (width * y)) * 4;
|
||||
std::memcpy(&gl_buffer[offset], vec4.AsArray(), 4);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
morton_to_gl_fns[static_cast<std::size_t>(pixel_format)](stride, height, &gl_buffer[0],
|
||||
addr, load_start, load_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_SurfaceFlush, "RasterizerCache", "Surface Flush",
|
||||
MP_RGB(128, 192, 64));
|
||||
void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
|
||||
u8* const dst_buffer = VideoCore::g_memory->GetPhysicalPointer(addr);
|
||||
if (dst_buffer == nullptr)
|
||||
return;
|
||||
|
||||
ASSERT(gl_buffer.size() == width * height * GetBytesPerPixel(pixel_format));
|
||||
|
||||
// TODO: Should probably be done in ::Memory:: and check for other regions too
|
||||
// same as loadglbuffer()
|
||||
if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END)
|
||||
flush_end = Memory::VRAM_VADDR_END;
|
||||
|
||||
if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR)
|
||||
flush_start = Memory::VRAM_VADDR;
|
||||
|
||||
MICROPROFILE_SCOPE(RasterizerCache_SurfaceFlush);
|
||||
|
||||
ASSERT(flush_start >= addr && flush_end <= end);
|
||||
const u32 start_offset = flush_start - addr;
|
||||
const u32 end_offset = flush_end - addr;
|
||||
|
||||
if (type == SurfaceType::Fill) {
|
||||
const u32 coarse_start_offset = start_offset - (start_offset % fill_size);
|
||||
const u32 backup_bytes = start_offset % fill_size;
|
||||
std::array<u8, 4> backup_data;
|
||||
if (backup_bytes)
|
||||
std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes);
|
||||
|
||||
for (u32 offset = coarse_start_offset; offset < end_offset; offset += fill_size) {
|
||||
std::memcpy(&dst_buffer[offset], &fill_data[0],
|
||||
std::min(fill_size, end_offset - offset));
|
||||
}
|
||||
|
||||
if (backup_bytes)
|
||||
std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes);
|
||||
} else if (!is_tiled) {
|
||||
ASSERT(type == SurfaceType::Color);
|
||||
if (pixel_format == PixelFormat::RGBA8 && GLES) {
|
||||
for (std::size_t i = start_offset; i < flush_end - addr; i += 4) {
|
||||
dst_buffer[i] = gl_buffer[i + 3];
|
||||
dst_buffer[i + 1] = gl_buffer[i + 2];
|
||||
dst_buffer[i + 2] = gl_buffer[i + 1];
|
||||
dst_buffer[i + 3] = gl_buffer[i];
|
||||
}
|
||||
} else if (pixel_format == PixelFormat::RGB8 && GLES) {
|
||||
for (std::size_t i = start_offset; i < flush_end - addr; i += 3) {
|
||||
dst_buffer[i] = gl_buffer[i + 2];
|
||||
dst_buffer[i + 1] = gl_buffer[i + 1];
|
||||
dst_buffer[i + 2] = gl_buffer[i];
|
||||
}
|
||||
} else {
|
||||
std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset],
|
||||
flush_end - flush_start);
|
||||
}
|
||||
} else {
|
||||
gl_to_morton_fns[static_cast<std::size_t>(pixel_format)](stride, height, &gl_buffer[0],
|
||||
addr, flush_start, flush_end);
|
||||
}
|
||||
}
|
||||
|
||||
bool CachedSurface::LoadCustomTexture(u64 tex_hash) {
|
||||
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
|
||||
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
|
||||
|
||||
if (custom_tex_cache.IsTextureCached(tex_hash)) {
|
||||
custom_tex_info = custom_tex_cache.LookupTexture(tex_hash);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!custom_tex_cache.CustomTextureExists(tex_hash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& path_info = custom_tex_cache.LookupTexturePathInfo(tex_hash);
|
||||
if (!image_interface->DecodePNG(custom_tex_info.tex, custom_tex_info.width,
|
||||
custom_tex_info.height, path_info.path)) {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::bitset<32> width_bits(custom_tex_info.width);
|
||||
const std::bitset<32> height_bits(custom_tex_info.height);
|
||||
if (width_bits.count() != 1 || height_bits.count() != 1) {
|
||||
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
|
||||
Common::FlipRGBA8Texture(custom_tex_info.tex, custom_tex_info.width, custom_tex_info.height);
|
||||
custom_tex_cache.CacheTexture(tex_hash, custom_tex_info.tex, custom_tex_info.width,
|
||||
custom_tex_info.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CachedSurface::DumpTexture(GLuint target_tex, u64 tex_hash) {
|
||||
// Make sure the texture size is a power of 2
|
||||
// If not, the surface is actually a framebuffer
|
||||
std::bitset<32> width_bits(width);
|
||||
std::bitset<32> height_bits(height);
|
||||
if (width_bits.count() != 1 || height_bits.count() != 1) {
|
||||
LOG_WARNING(Render_OpenGL, "Not dumping {:016X} because size isn't a power of 2 ({}x{})",
|
||||
tex_hash, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dump texture to RGBA8 and encode as PNG
|
||||
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
|
||||
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
|
||||
std::string dump_path =
|
||||
fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id);
|
||||
if (!FileUtil::CreateFullPath(dump_path)) {
|
||||
LOG_ERROR(Render, "Unable to create {}", dump_path);
|
||||
return;
|
||||
}
|
||||
|
||||
dump_path += fmt::format("tex1_{}x{}_{:016X}_{}.png", width, height, tex_hash, pixel_format);
|
||||
if (!custom_tex_cache.IsTextureDumped(tex_hash) && !FileUtil::Exists(dump_path)) {
|
||||
custom_tex_cache.SetTextureDumped(tex_hash);
|
||||
|
||||
LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path);
|
||||
std::vector<u8> decoded_texture;
|
||||
decoded_texture.resize(width * height * 4);
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
GLuint old_texture = state.texture_units[0].texture_2d;
|
||||
state.Apply();
|
||||
/*
|
||||
GetTexImageOES is used even if not using OpenGL ES to work around a small issue that
|
||||
happens if using custom textures with texture dumping at the same.
|
||||
Let's say there's 2 textures that are both 32x32 and one of them gets replaced with a
|
||||
higher quality 256x256 texture. If the 256x256 texture is displayed first and the
|
||||
32x32 texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture
|
||||
will appear in the corner of the 256x256 texture. If texture dumping is enabled and
|
||||
the 32x32 is undumped, Citra will attempt to dump it. Since the underlying OpenGL
|
||||
texture is still 256x256, Citra crashes because it thinks the texture is only 32x32.
|
||||
GetTexImageOES conveniently only dumps the specified region, and works on both
|
||||
desktop and ES.
|
||||
*/
|
||||
owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
height, width, &decoded_texture[0]);
|
||||
state.texture_units[0].texture_2d = old_texture;
|
||||
state.Apply();
|
||||
Common::FlipRGBA8Texture(decoded_texture, width, height);
|
||||
if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height))
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save decoded texture");
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_TextureUL, "RasterizerCache", "Texture Upload",
|
||||
MP_RGB(128, 192, 64));
|
||||
void CachedSurface::UploadGLTexture(Common::Rectangle<u32> rect) {
|
||||
if (type == SurfaceType::Fill) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(RasterizerCache_TextureUL);
|
||||
ASSERT(gl_buffer.size() == width * height * GetBytesPerPixel(pixel_format));
|
||||
|
||||
u64 tex_hash = 0;
|
||||
|
||||
if (Settings::values.dump_textures || Settings::values.custom_textures) {
|
||||
tex_hash = Common::ComputeHash64(gl_buffer.data(), gl_buffer.size());
|
||||
}
|
||||
|
||||
if (Settings::values.custom_textures) {
|
||||
is_custom = LoadCustomTexture(tex_hash);
|
||||
}
|
||||
|
||||
// Load data from memory to the surface
|
||||
GLint x0 = static_cast<GLint>(rect.left);
|
||||
GLint y0 = static_cast<GLint>(rect.bottom);
|
||||
std::size_t buffer_offset = (y0 * stride + x0) * GetBytesPerPixel(pixel_format);
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(pixel_format);
|
||||
GLuint target_tex = texture.handle;
|
||||
|
||||
// If not 1x scale, create 1x texture that we will blit from to replace texture subrect in
|
||||
// surface
|
||||
OGLTexture unscaled_tex;
|
||||
if (res_scale != 1) {
|
||||
x0 = 0;
|
||||
y0 = 0;
|
||||
|
||||
if (is_custom) {
|
||||
const auto& tuple = GetFormatTuple(PixelFormat::RGBA8);
|
||||
unscaled_tex =
|
||||
owner.AllocateSurfaceTexture(tuple, custom_tex_info.width, custom_tex_info.height);
|
||||
} else {
|
||||
unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight());
|
||||
}
|
||||
|
||||
target_tex = unscaled_tex.handle;
|
||||
}
|
||||
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
|
||||
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
||||
cur_state.texture_units[0].texture_2d = target_tex;
|
||||
cur_state.Apply();
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
|
||||
if (is_custom) {
|
||||
if (res_scale == 1) {
|
||||
texture = owner.AllocateSurfaceTexture(GetFormatTuple(PixelFormat::RGBA8),
|
||||
custom_tex_info.width, custom_tex_info.height);
|
||||
cur_state.texture_units[0].texture_2d = texture.handle;
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
// Always going to be using rgba8
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(custom_tex_info.width));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, custom_tex_info.width, custom_tex_info.height,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, custom_tex_info.tex.data());
|
||||
} else {
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
if (Settings::values.dump_textures && !is_custom) {
|
||||
DumpTexture(target_tex, tex_hash);
|
||||
}
|
||||
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
|
||||
if (res_scale != 1) {
|
||||
auto scaled_rect = rect;
|
||||
scaled_rect.left *= res_scale;
|
||||
scaled_rect.top *= res_scale;
|
||||
scaled_rect.right *= res_scale;
|
||||
scaled_rect.bottom *= res_scale;
|
||||
|
||||
const u32 width = is_custom ? custom_tex_info.width : rect.GetWidth();
|
||||
const u32 height = is_custom ? custom_tex_info.height : rect.GetHeight();
|
||||
const Common::Rectangle<u32> from_rect{0, height, width, 0};
|
||||
|
||||
if (is_custom ||
|
||||
!owner.texture_filterer->Filter(unscaled_tex, from_rect, texture, scaled_rect, type)) {
|
||||
const Aspect aspect = ToAspect(type);
|
||||
runtime.BlitTextures(unscaled_tex, {aspect, from_rect}, texture, {aspect, scaled_rect});
|
||||
}
|
||||
}
|
||||
|
||||
InvalidateAllWatcher();
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_TextureDL, "RasterizerCache", "Texture Download",
|
||||
MP_RGB(128, 192, 64));
|
||||
void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect) {
|
||||
if (type == SurfaceType::Fill) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(RasterizerCache_TextureDL);
|
||||
|
||||
if (gl_buffer.empty()) {
|
||||
gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format));
|
||||
}
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
OpenGLState prev_state = state;
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(pixel_format);
|
||||
|
||||
// Ensure no bad interactions with GL_PACK_ALIGNMENT
|
||||
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
|
||||
const std::size_t buffer_offset =
|
||||
(rect.bottom * stride + rect.left) * GetBytesPerPixel(pixel_format);
|
||||
|
||||
// If not 1x scale, blit scaled texture to a new 1x texture and use that to flush
|
||||
const Aspect aspect = ToAspect(type);
|
||||
if (res_scale != 1) {
|
||||
auto scaled_rect = rect;
|
||||
scaled_rect.left *= res_scale;
|
||||
scaled_rect.top *= res_scale;
|
||||
scaled_rect.right *= res_scale;
|
||||
scaled_rect.bottom *= res_scale;
|
||||
|
||||
const Common::Rectangle<u32> unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0};
|
||||
auto unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight());
|
||||
// Blit scaled texture to the unscaled one
|
||||
runtime.BlitTextures(texture, {aspect, scaled_rect}, unscaled_tex,
|
||||
{aspect, unscaled_tex_rect});
|
||||
|
||||
state.texture_units[0].texture_2d = unscaled_tex.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
if (GLES) {
|
||||
owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type,
|
||||
rect.GetHeight(), rect.GetWidth(),
|
||||
&gl_buffer[buffer_offset]);
|
||||
} else {
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]);
|
||||
}
|
||||
} else {
|
||||
runtime.ReadTexture(texture, {aspect, rect}, tuple, gl_buffer.data());
|
||||
}
|
||||
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
bool CachedSurface::CanFill(const SurfaceParams& dest_surface,
|
||||
SurfaceInterval fill_interval) const {
|
||||
if (type == SurfaceType::Fill && IsRegionValid(fill_interval) &&
|
||||
boost::icl::first(fill_interval) >= addr &&
|
||||
boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range
|
||||
dest_surface.FromInterval(fill_interval).GetInterval() ==
|
||||
fill_interval) { // make sure interval is a rectangle in dest surface
|
||||
if (fill_size * 8 != dest_surface.GetFormatBpp()) {
|
||||
// Check if bits repeat for our fill_size
|
||||
const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u);
|
||||
std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel);
|
||||
|
||||
for (u32 i = 0; i < dest_bytes_per_pixel; ++i)
|
||||
std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size);
|
||||
|
||||
for (u32 i = 0; i < fill_size; ++i)
|
||||
if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0],
|
||||
dest_bytes_per_pixel) != 0)
|
||||
return false;
|
||||
|
||||
if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CachedSurface::CanCopy(const SurfaceParams& dest_surface,
|
||||
SurfaceInterval copy_interval) const {
|
||||
SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval);
|
||||
ASSERT(subrect_params.GetInterval() == copy_interval);
|
||||
if (CanSubRect(subrect_params))
|
||||
return true;
|
||||
|
||||
if (CanFill(dest_surface, copy_interval))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
@ -1,137 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <list>
|
||||
#include "common/assert.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
#include "video_core/rasterizer_cache/texture_runtime.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
/**
|
||||
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
|
||||
* surface collection objects, including texture cube and mipmap.
|
||||
*/
|
||||
class SurfaceWatcher {
|
||||
friend class CachedSurface;
|
||||
|
||||
public:
|
||||
explicit SurfaceWatcher(std::weak_ptr<CachedSurface>&& surface) : surface(std::move(surface)) {}
|
||||
|
||||
/// Checks whether the surface has been changed.
|
||||
bool IsValid() const {
|
||||
return !surface.expired() && valid;
|
||||
}
|
||||
|
||||
/// Marks that the content of the referencing surface has been updated to the watcher user.
|
||||
void Validate() {
|
||||
ASSERT(!surface.expired());
|
||||
valid = true;
|
||||
}
|
||||
|
||||
/// Gets the referencing surface. Returns null if the surface has been destroyed
|
||||
Surface Get() const {
|
||||
return surface.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<CachedSurface> surface;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL;
|
||||
|
||||
class CachedSurface : public SurfaceParams, public std::enable_shared_from_this<CachedSurface> {
|
||||
public:
|
||||
CachedSurface(SurfaceParams params, RasterizerCacheOpenGL& owner, TextureRuntime& runtime)
|
||||
: SurfaceParams(params), owner(owner), runtime(runtime) {}
|
||||
~CachedSurface();
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Custom texture loading and dumping
|
||||
bool LoadCustomTexture(u64 tex_hash);
|
||||
void DumpTexture(GLuint target_tex, u64 tex_hash);
|
||||
|
||||
/// Upload/Download data in gl_buffer in/to this surface's texture
|
||||
void UploadGLTexture(Common::Rectangle<u32> rect);
|
||||
void DownloadGLTexture(const Common::Rectangle<u32>& rect);
|
||||
|
||||
bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
|
||||
bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
|
||||
|
||||
bool IsRegionValid(SurfaceInterval interval) const {
|
||||
return (invalid_regions.find(interval) == invalid_regions.end());
|
||||
}
|
||||
|
||||
bool IsSurfaceFullyInvalid() const {
|
||||
auto interval = GetInterval();
|
||||
return *invalid_regions.equal_range(interval).first == interval;
|
||||
}
|
||||
|
||||
std::shared_ptr<SurfaceWatcher> CreateWatcher() {
|
||||
auto watcher = std::make_shared<SurfaceWatcher>(weak_from_this());
|
||||
watchers.push_front(watcher);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
void InvalidateAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnlinkAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
locked->surface.reset();
|
||||
}
|
||||
}
|
||||
|
||||
watchers.clear();
|
||||
}
|
||||
|
||||
public:
|
||||
bool registered = false;
|
||||
SurfaceRegions invalid_regions;
|
||||
std::vector<u8> gl_buffer;
|
||||
|
||||
// Number of bytes to read from fill_data
|
||||
u32 fill_size = 0;
|
||||
std::array<u8, 4> fill_data;
|
||||
OGLTexture texture;
|
||||
|
||||
// level_watchers[i] watches the (i+1)-th level mipmap source surface
|
||||
std::array<std::shared_ptr<SurfaceWatcher>, 7> level_watchers;
|
||||
u32 max_level = 0;
|
||||
|
||||
// Information about custom textures
|
||||
bool is_custom = false;
|
||||
Core::CustomTexInfo custom_tex_info;
|
||||
|
||||
private:
|
||||
RasterizerCacheOpenGL& owner;
|
||||
TextureRuntime& runtime;
|
||||
std::list<std::weak_ptr<SurfaceWatcher>> watchers;
|
||||
};
|
||||
|
||||
struct CachedTextureCube {
|
||||
OGLTexture texture;
|
||||
u16 res_scale = 1;
|
||||
std::shared_ptr<SurfaceWatcher> px;
|
||||
std::shared_ptr<SurfaceWatcher> nx;
|
||||
std::shared_ptr<SurfaceWatcher> py;
|
||||
std::shared_ptr<SurfaceWatcher> ny;
|
||||
std::shared_ptr<SurfaceWatcher> pz;
|
||||
std::shared_ptr<SurfaceWatcher> nz;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
@ -3,151 +3,306 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <span>
|
||||
#include "common/alignment.h"
|
||||
#include "core/memory.h"
|
||||
#include "common/color.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/texture/etc1.h"
|
||||
#include "video_core/utils.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace VideoCore {
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) {
|
||||
template <typename T>
|
||||
inline T MakeInt(const std::byte* bytes) {
|
||||
T integer{};
|
||||
std::memcpy(&integer, bytes, sizeof(T));
|
||||
|
||||
return integer;
|
||||
}
|
||||
|
||||
template <PixelFormat format, bool converted>
|
||||
constexpr void DecodePixel(const std::byte* source, std::byte* dest) {
|
||||
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
|
||||
constexpr u32 aligned_bytes_per_pixel = GetBytesPerPixel(format);
|
||||
for (u32 y = 0; y < 8; ++y) {
|
||||
for (u32 x = 0; x < 8; ++x) {
|
||||
u8* tile_ptr = tile_buffer + VideoCore::MortonInterleave(x, y) * bytes_per_pixel;
|
||||
u8* gl_ptr = gl_buffer + ((7 - y) * stride + x) * aligned_bytes_per_pixel;
|
||||
if constexpr (morton_to_gl) {
|
||||
if constexpr (format == PixelFormat::D24S8) {
|
||||
gl_ptr[0] = tile_ptr[3];
|
||||
std::memcpy(gl_ptr + 1, tile_ptr, 3);
|
||||
} else if (format == PixelFormat::RGBA8 && GLES) {
|
||||
// because GLES does not have ABGR format
|
||||
// so we will do byteswapping here
|
||||
gl_ptr[0] = tile_ptr[3];
|
||||
gl_ptr[1] = tile_ptr[2];
|
||||
gl_ptr[2] = tile_ptr[1];
|
||||
gl_ptr[3] = tile_ptr[0];
|
||||
} else if (format == PixelFormat::RGB8 && GLES) {
|
||||
gl_ptr[0] = tile_ptr[2];
|
||||
gl_ptr[1] = tile_ptr[1];
|
||||
gl_ptr[2] = tile_ptr[0];
|
||||
|
||||
if constexpr (format == PixelFormat::D24S8) {
|
||||
const u32 d24s8 = std::rotl(MakeInt<u32>(source), 8);
|
||||
std::memcpy(dest, &d24s8, sizeof(u32));
|
||||
} else if constexpr (format == PixelFormat::RGBA8 && converted) {
|
||||
const u32 rgba = MakeInt<u32>(source);
|
||||
const u32 abgr = Common::swap32(rgba);
|
||||
std::memcpy(dest, &abgr, 4);
|
||||
} else if constexpr (format == PixelFormat::RGB8 && converted) {
|
||||
u32 rgb{};
|
||||
std::memcpy(&rgb, source, 3);
|
||||
const u32 abgr = Common::swap32(rgb << 8) | 0xFF000000;
|
||||
std::memcpy(dest, &abgr, 4);
|
||||
} else if constexpr (format == PixelFormat::RGB565 && converted) {
|
||||
const auto abgr = Common::Color::DecodeRGB565(reinterpret_cast<const u8*>(source));
|
||||
std::memcpy(dest, abgr.AsArray(), 4);
|
||||
} else if constexpr (format == PixelFormat::RGB5A1 && converted) {
|
||||
const auto abgr = Common::Color::DecodeRGB5A1(reinterpret_cast<const u8*>(source));
|
||||
std::memcpy(dest, abgr.AsArray(), 4);
|
||||
} else if constexpr (format == PixelFormat::RGBA4 && converted) {
|
||||
const auto abgr = Common::Color::DecodeRGBA4(reinterpret_cast<const u8*>(source));
|
||||
std::memcpy(dest, abgr.AsArray(), 4);
|
||||
} else if constexpr (format == PixelFormat::IA8) {
|
||||
std::memset(dest, static_cast<int>(source[1]), 3);
|
||||
dest[3] = source[0];
|
||||
} else if constexpr (format == PixelFormat::RG8) {
|
||||
const auto rgba = Common::Color::DecodeRG8(reinterpret_cast<const u8*>(source));
|
||||
std::memcpy(dest, rgba.AsArray(), 4);
|
||||
} else if constexpr (format == PixelFormat::I8) {
|
||||
std::memset(dest, static_cast<int>(source[0]), 3);
|
||||
dest[3] = std::byte{255};
|
||||
} else if constexpr (format == PixelFormat::A8) {
|
||||
std::memset(dest, 0, 3);
|
||||
dest[3] = source[0];
|
||||
} else if constexpr (format == PixelFormat::IA4) {
|
||||
const u8 ia4 = static_cast<const u8>(source[0]);
|
||||
std::memset(dest, Common::Color::Convert4To8(ia4 >> 4), 3);
|
||||
dest[3] = std::byte{Common::Color::Convert4To8(ia4 & 0xF)};
|
||||
} else {
|
||||
std::memcpy(dest, source, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
template <PixelFormat format>
|
||||
constexpr void DecodePixel4(u32 x, u32 y, const std::byte* source_tile, std::byte* dest_pixel) {
|
||||
const u32 morton_offset = VideoCore::MortonInterleave(x, y);
|
||||
const u8 value = static_cast<const u8>(source_tile[morton_offset >> 1]);
|
||||
const u8 pixel = Common::Color::Convert4To8((morton_offset % 2) ? (value >> 4) : (value & 0xF));
|
||||
|
||||
if constexpr (format == PixelFormat::I4) {
|
||||
std::memset(dest_pixel, static_cast<int>(pixel), 3);
|
||||
dest_pixel[3] = std::byte{255};
|
||||
} else {
|
||||
std::memset(dest_pixel, 0, 3);
|
||||
dest_pixel[3] = std::byte{pixel};
|
||||
}
|
||||
}
|
||||
|
||||
template <PixelFormat format>
|
||||
constexpr void DecodePixelETC1(u32 x, u32 y, const std::byte* source_tile, std::byte* dest_pixel) {
|
||||
constexpr u32 subtile_width = 4;
|
||||
constexpr u32 subtile_height = 4;
|
||||
constexpr bool has_alpha = format == PixelFormat::ETC1A4;
|
||||
constexpr std::size_t subtile_size = has_alpha ? 16 : 8;
|
||||
|
||||
const u32 subtile_index = (x / subtile_width) + 2 * (y / subtile_height);
|
||||
x %= subtile_width;
|
||||
y %= subtile_height;
|
||||
|
||||
const std::byte* subtile_ptr = source_tile + subtile_index * subtile_size;
|
||||
|
||||
u8 alpha = 255;
|
||||
if constexpr (has_alpha) {
|
||||
u64_le packed_alpha;
|
||||
std::memcpy(&packed_alpha, subtile_ptr, sizeof(u64));
|
||||
subtile_ptr += sizeof(u64);
|
||||
|
||||
alpha = Common::Color::Convert4To8((packed_alpha >> (4 * (x * subtile_width + y))) & 0xF);
|
||||
}
|
||||
|
||||
const u64_le subtile_data = MakeInt<u64_le>(subtile_ptr);
|
||||
const auto rgb = Pica::Texture::SampleETC1Subtile(subtile_data, x, y);
|
||||
|
||||
// Copy the uncompressed pixel to the destination
|
||||
std::memcpy(dest_pixel, rgb.AsArray(), 3);
|
||||
dest_pixel[3] = std::byte{alpha};
|
||||
}
|
||||
|
||||
template <PixelFormat format, bool converted>
|
||||
constexpr void EncodePixel(const std::byte* source, std::byte* dest) {
|
||||
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
|
||||
|
||||
if constexpr (format == PixelFormat::D24S8) {
|
||||
const u32 s8d24 = std::rotr(MakeInt<u32>(source), 8);
|
||||
std::memcpy(dest, &s8d24, sizeof(u32));
|
||||
} else if constexpr (format == PixelFormat::RGBA8 && converted) {
|
||||
const u32 abgr = MakeInt<u32>(source);
|
||||
const u32 rgba = Common::swap32(abgr);
|
||||
std::memcpy(dest, &rgba, 4);
|
||||
} else if constexpr (format == PixelFormat::RGB8 && converted) {
|
||||
const u32 abgr = MakeInt<u32>(source);
|
||||
const u32 rgb = Common::swap32(abgr << 8);
|
||||
std::memcpy(dest, &rgb, 3);
|
||||
} else if constexpr (format == PixelFormat::RGB565 && converted) {
|
||||
Common::Vec4<u8> rgba;
|
||||
std::memcpy(rgba.AsArray(), source, 4);
|
||||
Common::Color::EncodeRGB565(rgba, reinterpret_cast<u8*>(dest));
|
||||
} else if constexpr (format == PixelFormat::RGB5A1 && converted) {
|
||||
Common::Vec4<u8> rgba;
|
||||
std::memcpy(rgba.AsArray(), source, 4);
|
||||
Common::Color::EncodeRGB5A1(rgba, reinterpret_cast<u8*>(dest));
|
||||
} else if constexpr (format == PixelFormat::RGBA4 && converted) {
|
||||
Common::Vec4<u8> rgba;
|
||||
std::memcpy(rgba.AsArray(), source, 4);
|
||||
Common::Color::EncodeRGBA4(rgba, reinterpret_cast<u8*>(dest));
|
||||
} else {
|
||||
std::memcpy(dest, source, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool morton_to_linear, PixelFormat format, bool converted>
|
||||
constexpr void MortonCopyTile(u32 stride, std::span<std::byte> tile_buffer,
|
||||
std::span<std::byte> linear_buffer) {
|
||||
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
|
||||
constexpr u32 linear_bytes_per_pixel = converted ? 4 : GetBytesPerPixel(format);
|
||||
constexpr bool is_compressed = format == PixelFormat::ETC1 || format == PixelFormat::ETC1A4;
|
||||
constexpr bool is_4bit = format == PixelFormat::I4 || format == PixelFormat::A4;
|
||||
|
||||
for (u32 y = 0; y < 8; y++) {
|
||||
for (u32 x = 0; x < 8; x++) {
|
||||
const auto tiled_pixel = tile_buffer.subspan(
|
||||
VideoCore::MortonInterleave(x, y) * bytes_per_pixel, bytes_per_pixel);
|
||||
const auto linear_pixel = linear_buffer.subspan(
|
||||
((7 - y) * stride + x) * linear_bytes_per_pixel, linear_bytes_per_pixel);
|
||||
if constexpr (morton_to_linear) {
|
||||
if constexpr (is_compressed) {
|
||||
DecodePixelETC1<format>(x, y, tile_buffer.data(), linear_pixel.data());
|
||||
} else if constexpr (is_4bit) {
|
||||
DecodePixel4<format>(x, y, tile_buffer.data(), linear_pixel.data());
|
||||
} else {
|
||||
std::memcpy(gl_ptr, tile_ptr, bytes_per_pixel);
|
||||
DecodePixel<format, converted>(tiled_pixel.data(), linear_pixel.data());
|
||||
}
|
||||
} else {
|
||||
if constexpr (format == PixelFormat::D24S8) {
|
||||
std::memcpy(tile_ptr, gl_ptr + 1, 3);
|
||||
tile_ptr[3] = gl_ptr[0];
|
||||
} else if (format == PixelFormat::RGBA8 && GLES) {
|
||||
// because GLES does not have ABGR format
|
||||
// so we will do byteswapping here
|
||||
tile_ptr[0] = gl_ptr[3];
|
||||
tile_ptr[1] = gl_ptr[2];
|
||||
tile_ptr[2] = gl_ptr[1];
|
||||
tile_ptr[3] = gl_ptr[0];
|
||||
} else if (format == PixelFormat::RGB8 && GLES) {
|
||||
tile_ptr[0] = gl_ptr[2];
|
||||
tile_ptr[1] = gl_ptr[1];
|
||||
tile_ptr[2] = gl_ptr[0];
|
||||
} else {
|
||||
std::memcpy(tile_ptr, gl_ptr, bytes_per_pixel);
|
||||
}
|
||||
EncodePixel<format, converted>(linear_pixel.data(), tiled_pixel.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, PAddr base, PAddr start, PAddr end) {
|
||||
/**
|
||||
* @brief Performs morton to/from linear convertions on the provided pixel data
|
||||
* @param converted If true performs RGBA8 to/from convertion to all color formats
|
||||
* @param width, height The dimentions of the rectangular region of pixels in linear_buffer
|
||||
* @param start_offset The number of bytes from the start of the first tile to the start of
|
||||
* tiled_buffer
|
||||
* @param end_offset The number of bytes from the start of the first tile to the end of tiled_buffer
|
||||
* @param linear_buffer The linear pixel data
|
||||
* @param tiled_buffer The tiled pixel data
|
||||
*
|
||||
* The MortonCopy is at the heart of the PICA texture implementation, as it's responsible for
|
||||
* converting between linear and morton tiled layouts. The function handles both convertions but
|
||||
* there are slightly different paths and inputs for each:
|
||||
*
|
||||
* Morton to Linear:
|
||||
* During uploads, tiled_buffer is always aligned to the tile or scanline boundary depending if the
|
||||
* linear rectangle spans multiple vertical tiles. linear_buffer does not reference the entire
|
||||
* texture area, but rather the specific rectangle affected by the upload.
|
||||
*
|
||||
* Linear to Morton:
|
||||
* This is similar to the other convertion but with some differences. In this case tiled_buffer is
|
||||
* not required to be aligned to any specific boundary which requires special care.
|
||||
* start_offset/end_offset are useful here as they tell us exactly where the data should be placed
|
||||
* in the linear_buffer.
|
||||
*/
|
||||
template <bool morton_to_linear, PixelFormat format, bool converted = false>
|
||||
static constexpr void MortonCopy(u32 width, u32 height, u32 start_offset, u32 end_offset,
|
||||
std::span<std::byte> linear_buffer,
|
||||
std::span<std::byte> tiled_buffer) {
|
||||
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
|
||||
constexpr u32 tile_size = bytes_per_pixel * 64;
|
||||
|
||||
constexpr u32 aligned_bytes_per_pixel = GetBytesPerPixel(format);
|
||||
constexpr u32 aligned_bytes_per_pixel = converted ? 4 : GetBytesPerPixel(format);
|
||||
constexpr u32 tile_size = GetFormatBpp(format) * 64 / 8;
|
||||
static_assert(aligned_bytes_per_pixel >= bytes_per_pixel, "");
|
||||
gl_buffer += aligned_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);
|
||||
const u32 linear_tile_stride = (7 * width + 8) * aligned_bytes_per_pixel;
|
||||
const u32 aligned_down_start_offset = Common::AlignDown(start_offset, tile_size);
|
||||
const u32 aligned_start_offset = Common::AlignUp(start_offset, tile_size);
|
||||
const u32 aligned_end_offset = Common::AlignDown(end_offset, tile_size);
|
||||
|
||||
ASSERT(!morton_to_gl || (aligned_start == start && aligned_end == end));
|
||||
ASSERT(!morton_to_linear ||
|
||||
(aligned_start_offset == start_offset && aligned_end_offset == end_offset));
|
||||
|
||||
const u32 begin_pixel_index = (aligned_down_start - base) / bytes_per_pixel;
|
||||
u32 x = (begin_pixel_index % (stride * 8)) / 8;
|
||||
u32 y = (begin_pixel_index / (stride * 8)) * 8;
|
||||
// In OpenGL the texture origin is in the bottom left corner as opposed to other
|
||||
// APIs that have it at the top left. To avoid flipping texture coordinates in
|
||||
// the shader we read/write the linear buffer from the bottom up
|
||||
u32 linear_offset = ((height - 8) * width) * aligned_bytes_per_pixel;
|
||||
u32 tiled_offset = 0;
|
||||
u32 x = 0;
|
||||
u32 y = 0;
|
||||
|
||||
gl_buffer += ((height - 8 - y) * stride + x) * aligned_bytes_per_pixel;
|
||||
|
||||
auto glbuf_next_tile = [&] {
|
||||
x = (x + 8) % stride;
|
||||
gl_buffer += 8 * aligned_bytes_per_pixel;
|
||||
const auto LinearNextTile = [&] {
|
||||
x = (x + 8) % width;
|
||||
linear_offset += 8 * aligned_bytes_per_pixel;
|
||||
if (!x) {
|
||||
y += 8;
|
||||
gl_buffer -= stride * 9 * aligned_bytes_per_pixel;
|
||||
y = (y + 8) % height;
|
||||
if (!y) {
|
||||
return;
|
||||
}
|
||||
|
||||
linear_offset -= width * 9 * aligned_bytes_per_pixel;
|
||||
}
|
||||
};
|
||||
|
||||
u8* tile_buffer = VideoCore::g_memory->GetPhysicalPointer(start);
|
||||
// If during a texture download the start coordinate is not tile aligned, swizzle
|
||||
// the tile affected to a temporary buffer and copy the part we are interested in
|
||||
if (start_offset < aligned_start_offset && !morton_to_linear) {
|
||||
std::array<std::byte, tile_size> tmp_buf;
|
||||
auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
|
||||
MortonCopyTile<morton_to_linear, format, converted>(width, tmp_buf, linear_data);
|
||||
|
||||
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);
|
||||
std::memcpy(tiled_buffer.data(), tmp_buf.data() + start_offset - aligned_down_start_offset,
|
||||
std::min(aligned_start_offset, end_offset) - start_offset);
|
||||
|
||||
tile_buffer += aligned_start - start;
|
||||
glbuf_next_tile();
|
||||
tiled_offset += aligned_start_offset - start_offset;
|
||||
LinearNextTile();
|
||||
}
|
||||
|
||||
const u8* const buffer_end = tile_buffer + aligned_end - aligned_start;
|
||||
PAddr current_paddr = aligned_start;
|
||||
while (tile_buffer < buffer_end) {
|
||||
// Pokemon Super Mystery Dungeon will try to use textures that go beyond
|
||||
// the end address of VRAM. Stop reading if reaches invalid address
|
||||
if (!VideoCore::g_memory->IsValidPhysicalAddress(current_paddr) ||
|
||||
!VideoCore::g_memory->IsValidPhysicalAddress(current_paddr + tile_size)) {
|
||||
LOG_ERROR(Render_OpenGL, "Out of bound texture");
|
||||
break;
|
||||
}
|
||||
MortonCopyTile<morton_to_gl, format>(stride, tile_buffer, gl_buffer);
|
||||
tile_buffer += tile_size;
|
||||
current_paddr += tile_size;
|
||||
glbuf_next_tile();
|
||||
const u32 buffer_end = tiled_offset + aligned_end_offset - aligned_start_offset;
|
||||
while (tiled_offset < buffer_end) {
|
||||
auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
|
||||
auto tiled_data = tiled_buffer.subspan(tiled_offset, tile_size);
|
||||
MortonCopyTile<morton_to_linear, format, converted>(width, tiled_data, linear_data);
|
||||
tiled_offset += tile_size;
|
||||
LinearNextTile();
|
||||
}
|
||||
|
||||
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);
|
||||
// If during a texture download the end coordinate is not tile aligned, swizzle
|
||||
// the tile affected to a temporary buffer and copy the part we are interested in
|
||||
if (end_offset > std::max(aligned_start_offset, aligned_end_offset) && !morton_to_linear) {
|
||||
std::array<std::byte, tile_size> tmp_buf;
|
||||
auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
|
||||
MortonCopyTile<morton_to_linear, format, converted>(width, tmp_buf, linear_data);
|
||||
std::memcpy(tiled_buffer.data() + tiled_offset, tmp_buf.data(),
|
||||
end_offset - aligned_end_offset);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> morton_to_gl_fns = {
|
||||
using MortonFunc = void (*)(u32, u32, u32, u32, std::span<std::byte>, std::span<std::byte>);
|
||||
|
||||
static constexpr std::array<MortonFunc, 18> UNSWIZZLE_TABLE = {
|
||||
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
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr, // 5 - 13
|
||||
MortonCopy<true, PixelFormat::D16>, // 14
|
||||
nullptr, // 15
|
||||
MortonCopy<true, PixelFormat::D24>, // 16
|
||||
MortonCopy<true, PixelFormat::D24S8> // 17
|
||||
MortonCopy<true, PixelFormat::IA8>, // 5
|
||||
MortonCopy<true, PixelFormat::RG8>, // 6
|
||||
MortonCopy<true, PixelFormat::I8>, // 7
|
||||
MortonCopy<true, PixelFormat::A8>, // 8
|
||||
MortonCopy<true, PixelFormat::IA4>, // 9
|
||||
MortonCopy<true, PixelFormat::I4>, // 10
|
||||
MortonCopy<true, PixelFormat::A4>, // 11
|
||||
MortonCopy<true, PixelFormat::ETC1>, // 12
|
||||
MortonCopy<true, PixelFormat::ETC1A4>, // 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 = {
|
||||
static constexpr std::array<MortonFunc, 18> UNSWIZZLE_TABLE_CONVERTED = {
|
||||
MortonCopy<true, PixelFormat::RGBA8, true>, // 0
|
||||
MortonCopy<true, PixelFormat::RGB8, true>, // 1
|
||||
MortonCopy<true, PixelFormat::RGB5A1, true>, // 2
|
||||
MortonCopy<true, PixelFormat::RGB565, true>, // 3
|
||||
MortonCopy<true, PixelFormat::RGBA4, true> // 4
|
||||
};
|
||||
|
||||
static constexpr std::array<MortonFunc, 18> SWIZZLE_TABLE = {
|
||||
MortonCopy<false, PixelFormat::RGBA8>, // 0
|
||||
MortonCopy<false, PixelFormat::RGB8>, // 1
|
||||
MortonCopy<false, PixelFormat::RGB5A1>, // 2
|
||||
@ -168,4 +323,12 @@ static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl
|
||||
MortonCopy<false, PixelFormat::D24S8> // 17
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
static constexpr std::array<MortonFunc, 18> SWIZZLE_TABLE_CONVERTED = {
|
||||
MortonCopy<false, PixelFormat::RGBA8, true>, // 0
|
||||
MortonCopy<false, PixelFormat::RGB8, true>, // 1
|
||||
MortonCopy<false, PixelFormat::RGB5A1, true>, // 2
|
||||
MortonCopy<false, PixelFormat::RGB565, true>, // 3
|
||||
MortonCopy<false, PixelFormat::RGBA4, true> // 4
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
||||
|
98
src/video_core/rasterizer_cache/pixel_format.cpp
Normal file
98
src/video_core/rasterizer_cache/pixel_format.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
std::string_view PixelFormatAsString(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
return "RGBA8";
|
||||
case PixelFormat::RGB8:
|
||||
return "RGB8";
|
||||
case PixelFormat::RGB5A1:
|
||||
return "RGB5A1";
|
||||
case PixelFormat::RGB565:
|
||||
return "RGB565";
|
||||
case PixelFormat::RGBA4:
|
||||
return "RGBA4";
|
||||
case PixelFormat::IA8:
|
||||
return "IA8";
|
||||
case PixelFormat::RG8:
|
||||
return "RG8";
|
||||
case PixelFormat::I8:
|
||||
return "I8";
|
||||
case PixelFormat::A8:
|
||||
return "A8";
|
||||
case PixelFormat::IA4:
|
||||
return "IA4";
|
||||
case PixelFormat::I4:
|
||||
return "I4";
|
||||
case PixelFormat::A4:
|
||||
return "A4";
|
||||
case PixelFormat::ETC1:
|
||||
return "ETC1";
|
||||
case PixelFormat::ETC1A4:
|
||||
return "ETC1A4";
|
||||
case PixelFormat::D16:
|
||||
return "D16";
|
||||
case PixelFormat::D24:
|
||||
return "D24";
|
||||
case PixelFormat::D24S8:
|
||||
return "D24S8";
|
||||
default:
|
||||
return "NotReal";
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
|
||||
SurfaceType source_type = GetFormatType(source_format);
|
||||
SurfaceType dest_type = GetFormatType(dest_format);
|
||||
|
||||
if ((source_type == SurfaceType::Color || source_type == SurfaceType::Texture) &&
|
||||
(dest_type == SurfaceType::Color || dest_type == SurfaceType::Texture)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source_type == SurfaceType::Depth && dest_type == SurfaceType::Depth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source_type == SurfaceType::DepthStencil && dest_type == SurfaceType::DepthStencil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
return (format_index < 14) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
return (format_index < 4) ? static_cast<PixelFormat>(format_index + 14) : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
switch (format) {
|
||||
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return PixelFormat::RGB565;
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return PixelFormat::RGB5A1;
|
||||
default:
|
||||
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
@ -3,23 +3,22 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace VideoCore {
|
||||
|
||||
constexpr u32 PIXEL_FORMAT_COUNT = 18;
|
||||
constexpr std::size_t PIXEL_FORMAT_COUNT = 18;
|
||||
|
||||
enum class PixelFormat : u8 {
|
||||
// First 5 formats are shared between textures and color buffers
|
||||
enum class PixelFormat : u32 {
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGB5A1 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
// Texture-only formats
|
||||
IA8 = 5,
|
||||
RG8 = 6,
|
||||
I8 = 7,
|
||||
@ -29,163 +28,81 @@ enum class PixelFormat : u8 {
|
||||
A4 = 11,
|
||||
ETC1 = 12,
|
||||
ETC1A4 = 13,
|
||||
// Depth buffer-only formats
|
||||
D16 = 14,
|
||||
D24 = 16,
|
||||
D24S8 = 17,
|
||||
Max = 18,
|
||||
Invalid = 255,
|
||||
};
|
||||
|
||||
enum class SurfaceType {
|
||||
enum class SurfaceType : u32 {
|
||||
Color = 0,
|
||||
Texture = 1,
|
||||
Depth = 2,
|
||||
DepthStencil = 3,
|
||||
Fill = 4,
|
||||
Invalid = 5
|
||||
Invalid = 5,
|
||||
};
|
||||
|
||||
constexpr std::string_view PixelFormatAsString(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
return "RGBA8";
|
||||
case PixelFormat::RGB8:
|
||||
return "RGB8";
|
||||
case PixelFormat::RGB5A1:
|
||||
return "RGB5A1";
|
||||
case PixelFormat::RGB565:
|
||||
return "RGB565";
|
||||
case PixelFormat::RGBA4:
|
||||
return "RGBA4";
|
||||
case PixelFormat::IA8:
|
||||
return "IA8";
|
||||
case PixelFormat::RG8:
|
||||
return "RG8";
|
||||
case PixelFormat::I8:
|
||||
return "I8";
|
||||
case PixelFormat::A8:
|
||||
return "A8";
|
||||
case PixelFormat::IA4:
|
||||
return "IA4";
|
||||
case PixelFormat::I4:
|
||||
return "I4";
|
||||
case PixelFormat::A4:
|
||||
return "A4";
|
||||
case PixelFormat::ETC1:
|
||||
return "ETC1";
|
||||
case PixelFormat::ETC1A4:
|
||||
return "ETC1A4";
|
||||
case PixelFormat::D16:
|
||||
return "D16";
|
||||
case PixelFormat::D24:
|
||||
return "D24";
|
||||
case PixelFormat::D24S8:
|
||||
return "D24S8";
|
||||
default:
|
||||
return "NotReal";
|
||||
}
|
||||
}
|
||||
enum class TextureType : u32 {
|
||||
Texture2D = 0,
|
||||
CubeMap = 1,
|
||||
};
|
||||
|
||||
constexpr PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
return (format_index < 14) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
constexpr PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
constexpr PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
return (format_index < 4) ? static_cast<PixelFormat>(format_index + 14) : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
constexpr PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
switch (format) {
|
||||
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return PixelFormat::RGB565;
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return PixelFormat::RGB5A1;
|
||||
default:
|
||||
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
|
||||
const u32 format_index = static_cast<u32>(pixel_format);
|
||||
if (format_index < 5) {
|
||||
return SurfaceType::Color;
|
||||
}
|
||||
|
||||
if (format_index < 14) {
|
||||
return SurfaceType::Texture;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) {
|
||||
return SurfaceType::Depth;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D24S8) {
|
||||
return SurfaceType::DepthStencil;
|
||||
}
|
||||
|
||||
return SurfaceType::Invalid;
|
||||
}
|
||||
|
||||
constexpr bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
|
||||
SurfaceType source_type = GetFormatType(source_format);
|
||||
SurfaceType dest_type = GetFormatType(dest_format);
|
||||
|
||||
if ((source_type == SurfaceType::Color || source_type == SurfaceType::Texture) &&
|
||||
(dest_type == SurfaceType::Color || dest_type == SurfaceType::Texture)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source_type == SurfaceType::Depth && dest_type == SurfaceType::Depth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source_type == SurfaceType::DepthStencil && dest_type == SurfaceType::DepthStencil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
constexpr std::array<u8, PIXEL_FORMAT_COUNT> BITS_PER_BLOCK_TABLE = {{
|
||||
32, // RGBA8
|
||||
24, // RGB8
|
||||
16, // RGB5A1
|
||||
16, // RGB565
|
||||
16, // RGBA4
|
||||
16, // IA8
|
||||
16, // RG8
|
||||
8, // I8
|
||||
8, // A8
|
||||
8, // IA4
|
||||
4, // I4
|
||||
4, // A4
|
||||
4, // ETC1
|
||||
8, // ETC1A4
|
||||
16, // D16
|
||||
0,
|
||||
24, // D24
|
||||
32, // D24S8
|
||||
}};
|
||||
|
||||
constexpr u32 GetFormatBpp(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
case PixelFormat::D24S8:
|
||||
return 32;
|
||||
case PixelFormat::RGB8:
|
||||
case PixelFormat::D24:
|
||||
return 24;
|
||||
case PixelFormat::RGB5A1:
|
||||
case PixelFormat::RGB565:
|
||||
case PixelFormat::RGBA4:
|
||||
case PixelFormat::IA8:
|
||||
case PixelFormat::RG8:
|
||||
case PixelFormat::D16:
|
||||
return 16;
|
||||
case PixelFormat::I8:
|
||||
case PixelFormat::A8:
|
||||
case PixelFormat::IA4:
|
||||
case PixelFormat::ETC1A4:
|
||||
return 8;
|
||||
case PixelFormat::I4:
|
||||
case PixelFormat::A4:
|
||||
case PixelFormat::ETC1:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
ASSERT(static_cast<std::size_t>(format) < BITS_PER_BLOCK_TABLE.size());
|
||||
return BITS_PER_BLOCK_TABLE[static_cast<std::size_t>(format)];
|
||||
}
|
||||
|
||||
constexpr std::array<SurfaceType, PIXEL_FORMAT_COUNT> FORMAT_TYPE_TABLE = {{
|
||||
SurfaceType::Color, // RGBA8
|
||||
SurfaceType::Color, // RGB8
|
||||
SurfaceType::Color, // RGB5A1
|
||||
SurfaceType::Color, // RGB565
|
||||
SurfaceType::Color, // RGBA4
|
||||
SurfaceType::Texture, // IA8
|
||||
SurfaceType::Texture, // RG8
|
||||
SurfaceType::Texture, // I8
|
||||
SurfaceType::Texture, // A8
|
||||
SurfaceType::Texture, // IA4
|
||||
SurfaceType::Texture, // I4
|
||||
SurfaceType::Texture, // A4
|
||||
SurfaceType::Texture, // ETC1
|
||||
SurfaceType::Texture, // ETC1A4
|
||||
SurfaceType::Depth, // D16
|
||||
SurfaceType::Invalid,
|
||||
SurfaceType::Depth, // D24
|
||||
SurfaceType::DepthStencil, // D24S8
|
||||
}};
|
||||
|
||||
constexpr SurfaceType GetFormatType(PixelFormat format) {
|
||||
ASSERT(static_cast<std::size_t>(format) < FORMAT_TYPE_TABLE.size());
|
||||
return FORMAT_TYPE_TABLE[static_cast<std::size_t>(format)];
|
||||
}
|
||||
|
||||
constexpr u32 GetBytesPerPixel(PixelFormat format) {
|
||||
// OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
|
||||
// Modern GPUs need 4 bpp alignment for D24
|
||||
if (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture) {
|
||||
return 4;
|
||||
}
|
||||
@ -193,4 +110,16 @@ constexpr u32 GetBytesPerPixel(PixelFormat format) {
|
||||
return GetFormatBpp(format) / 8;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
std::string_view PixelFormatAsString(PixelFormat format);
|
||||
|
||||
bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format);
|
||||
|
||||
PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format);
|
||||
|
||||
PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format);
|
||||
|
||||
PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format);
|
||||
|
||||
PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format);
|
||||
|
||||
} // namespace VideoCore
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
|
||||
// Declare rasterizer interval types
|
||||
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
|
||||
using SurfaceSet = std::set<Surface>;
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
using SurfaceMap =
|
||||
boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
using SurfaceCache =
|
||||
boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
|
||||
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
|
||||
"Incorrect interval types");
|
||||
|
||||
using SurfaceRect_Tuple = std::tuple<Surface, Common::Rectangle<u32>>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
|
||||
using PageMap = boost::icl::interval_map<u32, int>;
|
||||
|
||||
} // namespace OpenGL
|
@ -1,56 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <glad/glad.h>
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||
|
||||
static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
|
||||
{},
|
||||
{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
|
||||
}};
|
||||
|
||||
static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
|
||||
{GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
|
||||
}};
|
||||
|
||||
// Same as above, with minor changes for OpenGL ES. Replaced
|
||||
// GL_UNSIGNED_INT_8_8_8_8 with GL_UNSIGNED_BYTE and
|
||||
// GL_BGR with GL_RGB
|
||||
static constexpr std::array<FormatTuple, 5> fb_format_tuples_oes = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}, // RGB8
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
|
||||
}};
|
||||
|
||||
const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
|
||||
const SurfaceType type = GetFormatType(pixel_format);
|
||||
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
|
||||
|
||||
if (type == SurfaceType::Color) {
|
||||
ASSERT(format_index < fb_format_tuples.size());
|
||||
return (GLES ? fb_format_tuples_oes : fb_format_tuples)[format_index];
|
||||
} else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
|
||||
const std::size_t tuple_idx = format_index - 14;
|
||||
ASSERT(tuple_idx < depth_format_tuples.size());
|
||||
return depth_format_tuples[tuple_idx];
|
||||
}
|
||||
|
||||
return tex_tuple;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
@ -1,73 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include "common/hash.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct FormatTuple {
|
||||
int internal_format;
|
||||
u32 format;
|
||||
u32 type;
|
||||
};
|
||||
|
||||
const FormatTuple& GetFormatTuple(PixelFormat pixel_format);
|
||||
|
||||
struct HostTextureTag {
|
||||
FormatTuple format_tuple{};
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
|
||||
bool operator==(const HostTextureTag& rhs) const noexcept {
|
||||
return std::memcmp(this, &rhs, sizeof(HostTextureTag)) == 0;
|
||||
};
|
||||
|
||||
const u64 Hash() const {
|
||||
return Common::ComputeHash64(this, sizeof(HostTextureTag));
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureCubeConfig {
|
||||
PAddr px;
|
||||
PAddr nx;
|
||||
PAddr py;
|
||||
PAddr ny;
|
||||
PAddr pz;
|
||||
PAddr nz;
|
||||
u32 width;
|
||||
Pica::TexturingRegs::TextureFormat format;
|
||||
|
||||
bool operator==(const TextureCubeConfig& rhs) const {
|
||||
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) == 0;
|
||||
}
|
||||
|
||||
bool operator!=(const TextureCubeConfig& rhs) const {
|
||||
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) != 0;
|
||||
}
|
||||
|
||||
const u64 Hash() const {
|
||||
return Common::ComputeHash64(this, sizeof(TextureCubeConfig));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<OpenGL::HostTextureTag> {
|
||||
std::size_t operator()(const OpenGL::HostTextureTag& tag) const noexcept {
|
||||
return tag.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::TextureCubeConfig> {
|
||||
std::size_t operator()(const OpenGL::TextureCubeConfig& config) const noexcept {
|
||||
return config.Hash();
|
||||
}
|
||||
};
|
||||
} // namespace std
|
217
src/video_core/rasterizer_cache/surface_base.h
Normal file
217
src/video_core/rasterizer_cache/surface_base.h
Normal file
@ -0,0 +1,217 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
|
||||
/**
|
||||
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
|
||||
* surface collection objects, including texture cube and mipmap.
|
||||
*/
|
||||
template <class S>
|
||||
class SurfaceWatcher {
|
||||
public:
|
||||
explicit SurfaceWatcher(std::weak_ptr<S>&& surface) : surface(std::move(surface)) {}
|
||||
|
||||
/// Checks whether the surface has been changed.
|
||||
bool IsValid() const {
|
||||
return !surface.expired() && valid;
|
||||
}
|
||||
|
||||
/// Marks that the content of the referencing surface has been updated to the watcher user.
|
||||
void Validate() {
|
||||
ASSERT(!surface.expired());
|
||||
valid = true;
|
||||
}
|
||||
|
||||
/// Gets the referencing surface. Returns null if the surface has been destroyed
|
||||
std::shared_ptr<S> Get() const {
|
||||
return surface.lock();
|
||||
}
|
||||
|
||||
public:
|
||||
std::weak_ptr<S> surface;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
template <class S>
|
||||
class SurfaceBase : public SurfaceParams, public std::enable_shared_from_this<S> {
|
||||
using Watcher = SurfaceWatcher<S>;
|
||||
|
||||
public:
|
||||
SurfaceBase() = default;
|
||||
SurfaceBase(const SurfaceParams& params) : SurfaceParams{params} {}
|
||||
virtual ~SurfaceBase() = default;
|
||||
|
||||
/// Returns true when this surface can be used to fill the fill_interval of dest_surface
|
||||
bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
|
||||
|
||||
/// Returns true when copy_interval of dest_surface can be validated by copying from this
|
||||
/// surface
|
||||
bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
|
||||
|
||||
/// Returns the region of the biggest valid rectange within interval
|
||||
SurfaceInterval GetCopyableInterval(const SurfaceParams& params) const;
|
||||
|
||||
/// Creates a surface watcher linked to this surface
|
||||
std::shared_ptr<Watcher> CreateWatcher();
|
||||
|
||||
/// Invalidates all watchers linked to this surface
|
||||
void InvalidateAllWatcher();
|
||||
|
||||
/// Removes any linked watchers from this surface
|
||||
void UnlinkAllWatcher();
|
||||
|
||||
/// Returns true when the region denoted by interval is valid
|
||||
bool IsRegionValid(SurfaceInterval interval) const {
|
||||
return (invalid_regions.find(interval) == invalid_regions.end());
|
||||
}
|
||||
|
||||
/// Returns true when the entire surface is invalid
|
||||
bool IsSurfaceFullyInvalid() const {
|
||||
auto interval = GetInterval();
|
||||
return *invalid_regions.equal_range(interval).first == interval;
|
||||
}
|
||||
|
||||
public:
|
||||
bool registered = false;
|
||||
SurfaceRegions invalid_regions;
|
||||
std::array<std::shared_ptr<Watcher>, 7> level_watchers;
|
||||
u32 max_level = 0;
|
||||
std::array<u8, 4> fill_data;
|
||||
u32 fill_size = 0;
|
||||
|
||||
public:
|
||||
std::vector<std::weak_ptr<Watcher>> watchers;
|
||||
};
|
||||
|
||||
template <class S>
|
||||
bool SurfaceBase<S>::CanFill(const SurfaceParams& dest_surface,
|
||||
SurfaceInterval fill_interval) const {
|
||||
if (type == SurfaceType::Fill && IsRegionValid(fill_interval) &&
|
||||
boost::icl::first(fill_interval) >= addr &&
|
||||
boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range
|
||||
dest_surface.FromInterval(fill_interval).GetInterval() ==
|
||||
fill_interval) { // make sure interval is a rectangle in dest surface
|
||||
|
||||
if (fill_size * 8 != dest_surface.GetFormatBpp()) {
|
||||
// Check if bits repeat for our fill_size
|
||||
const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u);
|
||||
std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel);
|
||||
|
||||
for (u32 i = 0; i < dest_bytes_per_pixel; ++i)
|
||||
std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size);
|
||||
|
||||
for (u32 i = 0; i < fill_size; ++i)
|
||||
if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0],
|
||||
dest_bytes_per_pixel) != 0)
|
||||
return false;
|
||||
|
||||
if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
bool SurfaceBase<S>::CanCopy(const SurfaceParams& dest_surface,
|
||||
SurfaceInterval copy_interval) const {
|
||||
SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval);
|
||||
ASSERT(subrect_params.GetInterval() == copy_interval);
|
||||
if (CanSubRect(subrect_params))
|
||||
return true;
|
||||
|
||||
if (CanFill(dest_surface, copy_interval))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
SurfaceInterval SurfaceBase<S>::GetCopyableInterval(const SurfaceParams& params) const {
|
||||
SurfaceInterval result{};
|
||||
const u32 tile_align = params.BytesInPixels(params.is_tiled ? 8 * 8 : 1);
|
||||
const auto valid_regions =
|
||||
SurfaceRegions{params.GetInterval() & GetInterval()} - invalid_regions;
|
||||
|
||||
for (auto& valid_interval : valid_regions) {
|
||||
const SurfaceInterval aligned_interval{
|
||||
params.addr +
|
||||
Common::AlignUp(boost::icl::first(valid_interval) - params.addr, tile_align),
|
||||
params.addr +
|
||||
Common::AlignDown(boost::icl::last_next(valid_interval) - params.addr, tile_align)};
|
||||
|
||||
if (params.BytesInPixels(tile_align) > boost::icl::length(valid_interval) ||
|
||||
boost::icl::length(aligned_interval) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the rectangle within aligned_interval
|
||||
const u32 stride_bytes = params.BytesInPixels(params.stride) * (params.is_tiled ? 8 : 1);
|
||||
SurfaceInterval rect_interval{
|
||||
params.addr +
|
||||
Common::AlignUp(boost::icl::first(aligned_interval) - params.addr, stride_bytes),
|
||||
params.addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - params.addr,
|
||||
stride_bytes),
|
||||
};
|
||||
|
||||
if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
|
||||
// 1 row
|
||||
rect_interval = aligned_interval;
|
||||
} else if (boost::icl::length(rect_interval) == 0) {
|
||||
// 2 rows that do not make a rectangle, return the larger one
|
||||
const SurfaceInterval row1{boost::icl::first(aligned_interval),
|
||||
boost::icl::first(rect_interval)};
|
||||
const SurfaceInterval row2{boost::icl::first(rect_interval),
|
||||
boost::icl::last_next(aligned_interval)};
|
||||
rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
|
||||
}
|
||||
|
||||
if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
|
||||
result = rect_interval;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
auto SurfaceBase<S>::CreateWatcher() -> std::shared_ptr<Watcher> {
|
||||
auto weak_ptr = reinterpret_cast<S*>(this)->weak_from_this();
|
||||
auto watcher = std::make_shared<Watcher>(std::move(weak_ptr));
|
||||
watchers.push_back(watcher);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
void SurfaceBase<S>::InvalidateAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class S>
|
||||
void SurfaceBase<S>::UnlinkAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
locked->surface.reset();
|
||||
}
|
||||
}
|
||||
|
||||
watchers.clear();
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user