Compare commits
336 Commits
gl-present
...
vulkan-2
Author | SHA1 | Date | |
---|---|---|---|
c9942b35bb | |||
ffe6904502 | |||
fcf9d29a62 | |||
9d7e68b7fc | |||
0a976f30c1 | |||
8e05af4389 | |||
d2b719ac5a | |||
4accbeba0d | |||
ee26b5b82f | |||
12a5265db1 | |||
eb8b463ca8 | |||
72c1785bf0 | |||
a2aca3dde6 | |||
c5f2267306 | |||
911fe5610c | |||
b3e0078041 | |||
eeb1ff7965 | |||
8721456944 | |||
a23fbae391 | |||
0a8f11ca63 | |||
8f194b5fff | |||
3b050668bb | |||
d054eea0c4 | |||
b9021ea469 | |||
223627c381 | |||
ad4339464a | |||
32cb44d2b9 | |||
39edca2cf7 | |||
0a3acc25d2 | |||
3f0bcf5913 | |||
2384c8f811 | |||
c26cb68a0c | |||
c34bc45bf1 | |||
19617f32c8 | |||
8396ce0b47 | |||
07f85cf639 | |||
dfd8ded206 | |||
06caa535d6 | |||
42bed30b98 | |||
74e75f1996 | |||
f04a6a4d83 | |||
33be1b744b | |||
2c9e0ec723 | |||
d1ac33b18b | |||
c00bdc4214 | |||
74be64b60a | |||
11ca327951 | |||
631da59392 | |||
e861c456c9 | |||
c3ab060576 | |||
94ee7c68fc | |||
8e8097e7c0 | |||
52683adedd | |||
e47c47245f | |||
004b83c978 | |||
4bff44c9a9 | |||
18ff007ff2 | |||
e41ceb3c88 | |||
14880862bc | |||
e651bb41bc | |||
8e93039ef9 | |||
65b02f35c7 | |||
d42ccb387a | |||
ee0a7476b3 | |||
45ffc56733 | |||
be8ee3d706 | |||
ab688170f0 | |||
274f6093f3 | |||
9662425337 | |||
6fde9b3354 | |||
e5907f0979 | |||
2855d30815 | |||
546c7b3bfd | |||
41c10cd5a7 | |||
bc77e16653 | |||
6ba83db6dd | |||
96effa46e4 | |||
e7a1318773 | |||
ffc9e34281 | |||
20fc09df13 | |||
6c78abb015 | |||
0d3434734a | |||
42a6f7a42e | |||
95e6428d33 | |||
d9ed4600ca | |||
612647f94f | |||
6a16a8f60d | |||
1ffd9f08af | |||
c855d84492 | |||
d461778296 | |||
3fe0130fdb | |||
b0880f0ef8 | |||
0bb2e8c618 | |||
84ccb45c5c | |||
f8b853d001 | |||
61e0725d9d | |||
2b9ab33af3 | |||
09350f71e8 | |||
582c438441 | |||
bc10681156 | |||
faefc5cfe1 | |||
fd09f1471e | |||
c1c68a3487 | |||
5849d29fc1 | |||
b31b6e35a2 | |||
79a53c8003 | |||
fd9525acc2 | |||
938ec204f5 | |||
7c8bfbb078 | |||
d7bf139e85 | |||
df7f1b13cb | |||
e08e644e73 | |||
ccf36b5e25 | |||
b6427d0ee0 | |||
5c401b8ea0 | |||
bd3571db5a | |||
b18710e4df | |||
69b66cb41d | |||
a5f86e9813 | |||
920492925c | |||
0381081c5d | |||
382b64d3c0 | |||
a629aa0dde | |||
b29f263e1b | |||
48d3093fc8 | |||
f77491196c | |||
0fce3e556f | |||
12e69913c2 | |||
89217f8c4b | |||
247b6900c7 | |||
1e42a40640 | |||
7eab7b4151 | |||
e9ccd51286 | |||
9f385ddeb7 | |||
ab73566acc | |||
1e3971038f | |||
306943532c | |||
27698b0c93 | |||
d6cab3ab40 | |||
cbd9a6ffe3 | |||
4752818920 | |||
131129062b | |||
fad1ee7140 | |||
b9ca7bef3f | |||
0e0771ad74 | |||
489248e77f | |||
f593268476 | |||
8fcc3d2121 | |||
a3a8964d46 | |||
c8d614139c | |||
25fe723fa6 | |||
2335b47f4b | |||
10a7b60b12 | |||
98ab3c9610 | |||
0ca25b64e1 | |||
694e49b857 | |||
381db8452b | |||
e2076f2385 | |||
9c206db630 | |||
447f29285f | |||
26a9002d97 | |||
a12748d79e | |||
5abfdff66a | |||
8a315d0c8f | |||
4ee36e05b6 | |||
11061f36e6 | |||
6d286d5f8c | |||
4619ed086c | |||
3843122cf8 | |||
d320eef663 | |||
a7a9b94a30 | |||
a9f2a69487 | |||
78965ba18d | |||
52c41d185b | |||
c5c041de89 | |||
e46a88f24a | |||
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 |
@ -5,6 +5,7 @@ pip3 install macpack
|
||||
|
||||
export FFMPEG_VER=5.1
|
||||
export QT_VER=5.15.8
|
||||
export VULKAN_SDK_VER=1.3.236.0
|
||||
|
||||
mkdir tmp
|
||||
cd tmp/
|
||||
@ -18,3 +19,9 @@ cp -rv $(pwd)/ffmpeg-${FFMPEG_VER}/* /
|
||||
wget https://github.com/SachinVin/ext-macos-bin/raw/main/qt/qt-${QT_VER}.7z
|
||||
7z x qt-${QT_VER}.7z
|
||||
sudo cp -rv $(pwd)/qt-${QT_VER}/* /usr/local/
|
||||
|
||||
# install Vulkan SDK
|
||||
wget https://sdk.lunarg.com/sdk/download/1.3.236.0/mac/vulkansdk-macos-${VULKAN_SDK_VER}.dmg
|
||||
hdiutil attach vulkansdk-macos-${VULKAN_SDK_VER}.dmg
|
||||
sudo /Volumes/vulkansdk-macos-${VULKAN_SDK_VER}/InstallVulkan.app/Contents/MacOS/InstallVulkan install --accept-licenses --confirm-command --default-answer com.lunarg.vulkan.core com.lunarg.vulkan.usr
|
||||
hdiutil detach /Volumes/vulkansdk-macos-${VULKAN_SDK_VER}
|
||||
|
@ -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
|
||||
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_FRAMEWORKS_PATH="$BUNDLE_CONTENTS_PATH/Frameworks"
|
||||
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
|
||||
macdeployqt $BUNDLE_PATH -executable=$BUNDLE_EXECUTABLE_PATH
|
||||
|
||||
# move libs into folder for deployment
|
||||
macpack $CITRA_STANDALONE_PATH -d "libs"
|
||||
|
||||
# bundle MoltenVK
|
||||
VULKAN_SDK_PATH=~/VulkanSDK/*/macOS
|
||||
cp $VULKAN_SDK_PATH/lib/libvulkan.dylib $BUNDLE_FRAMEWORKS_PATH
|
||||
cp $VULKAN_SDK_PATH/lib/libMoltenVK.dylib $BUNDLE_FRAMEWORKS_PATH
|
||||
cp $VULKAN_SDK_PATH/lib/libVkLayer_*.dylib $BUNDLE_FRAMEWORKS_PATH
|
||||
cp -r $VULKAN_SDK_PATH/share/vulkan $BUNDLE_RESOURCES_PATH
|
||||
find $BUNDLE_RESOURCES_PATH/vulkan -type f -name "*.json" -exec sed -i'' -e 's/..\/..\/..\/lib/..\/..\/..\/Frameworks/g' {} \;
|
||||
install_name_tool -add_rpath "@loader_path/../Frameworks/" $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 {} \;
|
||||
|
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@ -130,11 +130,11 @@ jobs:
|
||||
run: ./.ci/macos/universal.sh
|
||||
env:
|
||||
ARTIFACTS: macos-x86_64 macos-arm64
|
||||
# - name: Upload
|
||||
# uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: macos
|
||||
# path: artifacts/
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: artifacts/
|
||||
- name: Delete intermediate artifacts
|
||||
uses: geekyeggo/delete-artifact@v2
|
||||
with:
|
||||
@ -162,6 +162,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
|
||||
@ -197,6 +205,9 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ccache apksigner -y
|
||||
sudo add-apt-repository -y ppa:savoury1/ffmpeg4
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install --no-install-recommends -y glslang-dev glslang-tools
|
||||
- name: Build
|
||||
run: ./.ci/android/build.sh
|
||||
- name: Copy and sign artifacts
|
||||
|
30
.gitmodules
vendored
30
.gitmodules
vendored
@ -43,9 +43,6 @@
|
||||
[submodule "teakra"]
|
||||
path = externals/teakra
|
||||
url = https://github.com/wwylele/teakra.git
|
||||
[submodule "lodepng"]
|
||||
path = externals/lodepng/lodepng
|
||||
url = https://github.com/lvandeve/lodepng.git
|
||||
[submodule "zstd"]
|
||||
path = externals/zstd
|
||||
url = https://github.com/facebook/zstd.git
|
||||
@ -53,11 +50,26 @@
|
||||
path = externals/libyuv
|
||||
url = https://github.com/lemenkov/libyuv.git
|
||||
[submodule "sdl2"]
|
||||
path = externals/sdl2/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
path = externals/sdl2/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "cryptopp-cmake"]
|
||||
path = externals/cryptopp-cmake
|
||||
url = https://github.com/abdes/cryptopp-cmake.git
|
||||
path = externals/cryptopp-cmake
|
||||
url = https://github.com/abdes/cryptopp-cmake.git
|
||||
[submodule "cryptopp"]
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/weidai11/cryptopp.git
|
||||
path = externals/cryptopp
|
||||
url = https://github.com/weidai11/cryptopp.git
|
||||
[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
|
||||
[submodule "zlib-ng"]
|
||||
path = externals/zlib-ng/zlib-ng
|
||||
url = https://github.com/zlib-ng/zlib-ng
|
||||
|
@ -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)
|
||||
|
14
dist/qt_themes/default/style.qss
vendored
14
dist/qt_themes/default/style.qss
vendored
@ -1,3 +1,17 @@
|
||||
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;
|
||||
}
|
||||
|
||||
QPushButton#3DOptionStatusBarButton {
|
||||
color: #A5A5A5;
|
||||
font-weight: bold;
|
||||
|
14
dist/qt_themes/qdarkstyle/style.qss
vendored
14
dist/qt_themes/qdarkstyle/style.qss
vendored
@ -1237,6 +1237,20 @@ 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;
|
||||
}
|
||||
|
||||
QPushButton#3DOptionStatusBarButton {
|
||||
color: #A5A5A5;
|
||||
font-weight: bold;
|
||||
|
29
externals/CMakeLists.txt
vendored
29
externals/CMakeLists.txt
vendored
@ -89,6 +89,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)
|
||||
|
||||
@ -116,6 +129,9 @@ if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
|
||||
add_subdirectory(sdl2)
|
||||
endif()
|
||||
|
||||
# Zlib
|
||||
add_subdirectory(zlib-ng)
|
||||
|
||||
# Zstandard
|
||||
set(ZSTD_LEGACY_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
@ -174,8 +190,8 @@ if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
||||
endif()
|
||||
|
||||
# lodepng
|
||||
add_subdirectory(lodepng)
|
||||
# libspng
|
||||
add_subdirectory(libspng)
|
||||
|
||||
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
|
||||
if(ANDROID)
|
||||
@ -183,3 +199,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)
|
||||
|
||||
|
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 9af4b970d3...a179e2f2bb
1627
externals/glad/include/glad/glad.h
vendored
1627
externals/glad/include/glad/glad.h
vendored
File diff suppressed because it is too large
Load Diff
867
externals/glad/src/glad.c
vendored
867
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
14
externals/libspng/CMakeLists.txt
vendored
Normal file
14
externals/libspng/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
add_library(spng STATIC spng.h spng.c)
|
||||
target_compile_definitions(spng PUBLIC SPNG_STATIC)
|
||||
target_include_directories(spng PUBLIC ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_link_libraries(spng PRIVATE ZLIB::ZLIB)
|
||||
|
||||
# Enable SSE4.1 on x64
|
||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
target_compile_definitions(spng PRIVATE SPNG_SSE=4)
|
||||
if (NOT MSVC)
|
||||
target_compile_options(spng PRIVATE -msse4.1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(spng::spng ALIAS spng)
|
6979
externals/libspng/spng.c
vendored
Normal file
6979
externals/libspng/spng.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
537
externals/libspng/spng.h
vendored
Normal file
537
externals/libspng/spng.h
vendored
Normal file
@ -0,0 +1,537 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
#ifndef SPNG_H
|
||||
#define SPNG_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(SPNG_STATIC)
|
||||
#if defined(SPNG__BUILD)
|
||||
#define SPNG_API __declspec(dllexport)
|
||||
#else
|
||||
#define SPNG_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define SPNG_API
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define SPNG_CDECL __cdecl
|
||||
#else
|
||||
#define SPNG_CDECL
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define SPNG_VERSION_MAJOR 0
|
||||
#define SPNG_VERSION_MINOR 7
|
||||
#define SPNG_VERSION_PATCH 3
|
||||
|
||||
enum spng_errno
|
||||
{
|
||||
SPNG_IO_ERROR = -2,
|
||||
SPNG_IO_EOF = -1,
|
||||
SPNG_OK = 0,
|
||||
SPNG_EINVAL,
|
||||
SPNG_EMEM,
|
||||
SPNG_EOVERFLOW,
|
||||
SPNG_ESIGNATURE,
|
||||
SPNG_EWIDTH,
|
||||
SPNG_EHEIGHT,
|
||||
SPNG_EUSER_WIDTH,
|
||||
SPNG_EUSER_HEIGHT,
|
||||
SPNG_EBIT_DEPTH,
|
||||
SPNG_ECOLOR_TYPE,
|
||||
SPNG_ECOMPRESSION_METHOD,
|
||||
SPNG_EFILTER_METHOD,
|
||||
SPNG_EINTERLACE_METHOD,
|
||||
SPNG_EIHDR_SIZE,
|
||||
SPNG_ENOIHDR,
|
||||
SPNG_ECHUNK_POS,
|
||||
SPNG_ECHUNK_SIZE,
|
||||
SPNG_ECHUNK_CRC,
|
||||
SPNG_ECHUNK_TYPE,
|
||||
SPNG_ECHUNK_UNKNOWN_CRITICAL,
|
||||
SPNG_EDUP_PLTE,
|
||||
SPNG_EDUP_CHRM,
|
||||
SPNG_EDUP_GAMA,
|
||||
SPNG_EDUP_ICCP,
|
||||
SPNG_EDUP_SBIT,
|
||||
SPNG_EDUP_SRGB,
|
||||
SPNG_EDUP_BKGD,
|
||||
SPNG_EDUP_HIST,
|
||||
SPNG_EDUP_TRNS,
|
||||
SPNG_EDUP_PHYS,
|
||||
SPNG_EDUP_TIME,
|
||||
SPNG_EDUP_OFFS,
|
||||
SPNG_EDUP_EXIF,
|
||||
SPNG_ECHRM,
|
||||
SPNG_EPLTE_IDX,
|
||||
SPNG_ETRNS_COLOR_TYPE,
|
||||
SPNG_ETRNS_NO_PLTE,
|
||||
SPNG_EGAMA,
|
||||
SPNG_EICCP_NAME,
|
||||
SPNG_EICCP_COMPRESSION_METHOD,
|
||||
SPNG_ESBIT,
|
||||
SPNG_ESRGB,
|
||||
SPNG_ETEXT,
|
||||
SPNG_ETEXT_KEYWORD,
|
||||
SPNG_EZTXT,
|
||||
SPNG_EZTXT_COMPRESSION_METHOD,
|
||||
SPNG_EITXT,
|
||||
SPNG_EITXT_COMPRESSION_FLAG,
|
||||
SPNG_EITXT_COMPRESSION_METHOD,
|
||||
SPNG_EITXT_LANG_TAG,
|
||||
SPNG_EITXT_TRANSLATED_KEY,
|
||||
SPNG_EBKGD_NO_PLTE,
|
||||
SPNG_EBKGD_PLTE_IDX,
|
||||
SPNG_EHIST_NO_PLTE,
|
||||
SPNG_EPHYS,
|
||||
SPNG_ESPLT_NAME,
|
||||
SPNG_ESPLT_DUP_NAME,
|
||||
SPNG_ESPLT_DEPTH,
|
||||
SPNG_ETIME,
|
||||
SPNG_EOFFS,
|
||||
SPNG_EEXIF,
|
||||
SPNG_EIDAT_TOO_SHORT,
|
||||
SPNG_EIDAT_STREAM,
|
||||
SPNG_EZLIB,
|
||||
SPNG_EFILTER,
|
||||
SPNG_EBUFSIZ,
|
||||
SPNG_EIO,
|
||||
SPNG_EOF,
|
||||
SPNG_EBUF_SET,
|
||||
SPNG_EBADSTATE,
|
||||
SPNG_EFMT,
|
||||
SPNG_EFLAGS,
|
||||
SPNG_ECHUNKAVAIL,
|
||||
SPNG_ENCODE_ONLY,
|
||||
SPNG_EOI,
|
||||
SPNG_ENOPLTE,
|
||||
SPNG_ECHUNK_LIMITS,
|
||||
SPNG_EZLIB_INIT,
|
||||
SPNG_ECHUNK_STDLEN,
|
||||
SPNG_EINTERNAL,
|
||||
SPNG_ECTXTYPE,
|
||||
SPNG_ENOSRC,
|
||||
SPNG_ENODST,
|
||||
SPNG_EOPSTATE,
|
||||
SPNG_ENOTFINAL,
|
||||
};
|
||||
|
||||
enum spng_text_type
|
||||
{
|
||||
SPNG_TEXT = 1,
|
||||
SPNG_ZTXT = 2,
|
||||
SPNG_ITXT = 3
|
||||
};
|
||||
|
||||
enum spng_color_type
|
||||
{
|
||||
SPNG_COLOR_TYPE_GRAYSCALE = 0,
|
||||
SPNG_COLOR_TYPE_TRUECOLOR = 2,
|
||||
SPNG_COLOR_TYPE_INDEXED = 3,
|
||||
SPNG_COLOR_TYPE_GRAYSCALE_ALPHA = 4,
|
||||
SPNG_COLOR_TYPE_TRUECOLOR_ALPHA = 6
|
||||
};
|
||||
|
||||
enum spng_filter
|
||||
{
|
||||
SPNG_FILTER_NONE = 0,
|
||||
SPNG_FILTER_SUB = 1,
|
||||
SPNG_FILTER_UP = 2,
|
||||
SPNG_FILTER_AVERAGE = 3,
|
||||
SPNG_FILTER_PAETH = 4
|
||||
};
|
||||
|
||||
enum spng_filter_choice
|
||||
{
|
||||
SPNG_DISABLE_FILTERING = 0,
|
||||
SPNG_FILTER_CHOICE_NONE = 8,
|
||||
SPNG_FILTER_CHOICE_SUB = 16,
|
||||
SPNG_FILTER_CHOICE_UP = 32,
|
||||
SPNG_FILTER_CHOICE_AVG = 64,
|
||||
SPNG_FILTER_CHOICE_PAETH = 128,
|
||||
SPNG_FILTER_CHOICE_ALL = (8|16|32|64|128)
|
||||
};
|
||||
|
||||
enum spng_interlace_method
|
||||
{
|
||||
SPNG_INTERLACE_NONE = 0,
|
||||
SPNG_INTERLACE_ADAM7 = 1
|
||||
};
|
||||
|
||||
/* Channels are always in byte-order */
|
||||
enum spng_format
|
||||
{
|
||||
SPNG_FMT_RGBA8 = 1,
|
||||
SPNG_FMT_RGBA16 = 2,
|
||||
SPNG_FMT_RGB8 = 4,
|
||||
|
||||
/* Partially implemented, see documentation */
|
||||
SPNG_FMT_GA8 = 16,
|
||||
SPNG_FMT_GA16 = 32,
|
||||
SPNG_FMT_G8 = 64,
|
||||
|
||||
/* No conversion or scaling */
|
||||
SPNG_FMT_PNG = 256,
|
||||
SPNG_FMT_RAW = 512 /* big-endian (everything else is host-endian) */
|
||||
};
|
||||
|
||||
enum spng_ctx_flags
|
||||
{
|
||||
SPNG_CTX_IGNORE_ADLER32 = 1, /* Ignore checksum in DEFLATE streams */
|
||||
SPNG_CTX_ENCODER = 2 /* Create an encoder context */
|
||||
};
|
||||
|
||||
enum spng_decode_flags
|
||||
{
|
||||
SPNG_DECODE_USE_TRNS = 1, /* Deprecated */
|
||||
SPNG_DECODE_USE_GAMA = 2, /* Deprecated */
|
||||
SPNG_DECODE_USE_SBIT = 8, /* Undocumented */
|
||||
|
||||
SPNG_DECODE_TRNS = 1, /* Apply transparency */
|
||||
SPNG_DECODE_GAMMA = 2, /* Apply gamma correction */
|
||||
SPNG_DECODE_PROGRESSIVE = 256 /* Initialize for progressive reads */
|
||||
};
|
||||
|
||||
enum spng_crc_action
|
||||
{
|
||||
/* Default for critical chunks */
|
||||
SPNG_CRC_ERROR = 0,
|
||||
|
||||
/* Discard chunk, invalid for critical chunks.
|
||||
Since v0.6.2: default for ancillary chunks */
|
||||
SPNG_CRC_DISCARD = 1,
|
||||
|
||||
/* Ignore and don't calculate checksum.
|
||||
Since v0.6.2: also ignores checksums in DEFLATE streams */
|
||||
SPNG_CRC_USE = 2
|
||||
};
|
||||
|
||||
enum spng_encode_flags
|
||||
{
|
||||
SPNG_ENCODE_PROGRESSIVE = 1, /* Initialize for progressive writes */
|
||||
SPNG_ENCODE_FINALIZE = 2, /* Finalize PNG after encoding image */
|
||||
};
|
||||
|
||||
struct spng_ihdr
|
||||
{
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint8_t bit_depth;
|
||||
uint8_t color_type;
|
||||
uint8_t compression_method;
|
||||
uint8_t filter_method;
|
||||
uint8_t interlace_method;
|
||||
};
|
||||
|
||||
struct spng_plte_entry
|
||||
{
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
|
||||
uint8_t alpha; /* Reserved for internal use */
|
||||
};
|
||||
|
||||
struct spng_plte
|
||||
{
|
||||
uint32_t n_entries;
|
||||
struct spng_plte_entry entries[256];
|
||||
};
|
||||
|
||||
struct spng_trns
|
||||
{
|
||||
uint16_t gray;
|
||||
|
||||
uint16_t red;
|
||||
uint16_t green;
|
||||
uint16_t blue;
|
||||
|
||||
uint32_t n_type3_entries;
|
||||
uint8_t type3_alpha[256];
|
||||
};
|
||||
|
||||
struct spng_chrm_int
|
||||
{
|
||||
uint32_t white_point_x;
|
||||
uint32_t white_point_y;
|
||||
uint32_t red_x;
|
||||
uint32_t red_y;
|
||||
uint32_t green_x;
|
||||
uint32_t green_y;
|
||||
uint32_t blue_x;
|
||||
uint32_t blue_y;
|
||||
};
|
||||
|
||||
struct spng_chrm
|
||||
{
|
||||
double white_point_x;
|
||||
double white_point_y;
|
||||
double red_x;
|
||||
double red_y;
|
||||
double green_x;
|
||||
double green_y;
|
||||
double blue_x;
|
||||
double blue_y;
|
||||
};
|
||||
|
||||
struct spng_iccp
|
||||
{
|
||||
char profile_name[80];
|
||||
size_t profile_len;
|
||||
char *profile;
|
||||
};
|
||||
|
||||
struct spng_sbit
|
||||
{
|
||||
uint8_t grayscale_bits;
|
||||
uint8_t red_bits;
|
||||
uint8_t green_bits;
|
||||
uint8_t blue_bits;
|
||||
uint8_t alpha_bits;
|
||||
};
|
||||
|
||||
struct spng_text
|
||||
{
|
||||
char keyword[80];
|
||||
int type;
|
||||
|
||||
size_t length;
|
||||
char *text;
|
||||
|
||||
uint8_t compression_flag; /* iTXt only */
|
||||
uint8_t compression_method; /* iTXt, ztXt only */
|
||||
char *language_tag; /* iTXt only */
|
||||
char *translated_keyword; /* iTXt only */
|
||||
};
|
||||
|
||||
struct spng_bkgd
|
||||
{
|
||||
uint16_t gray; /* Only for gray/gray alpha */
|
||||
uint16_t red;
|
||||
uint16_t green;
|
||||
uint16_t blue;
|
||||
uint16_t plte_index; /* Only for indexed color */
|
||||
};
|
||||
|
||||
struct spng_hist
|
||||
{
|
||||
uint16_t frequency[256];
|
||||
};
|
||||
|
||||
struct spng_phys
|
||||
{
|
||||
uint32_t ppu_x, ppu_y;
|
||||
uint8_t unit_specifier;
|
||||
};
|
||||
|
||||
struct spng_splt_entry
|
||||
{
|
||||
uint16_t red;
|
||||
uint16_t green;
|
||||
uint16_t blue;
|
||||
uint16_t alpha;
|
||||
uint16_t frequency;
|
||||
};
|
||||
|
||||
struct spng_splt
|
||||
{
|
||||
char name[80];
|
||||
uint8_t sample_depth;
|
||||
uint32_t n_entries;
|
||||
struct spng_splt_entry *entries;
|
||||
};
|
||||
|
||||
struct spng_time
|
||||
{
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
};
|
||||
|
||||
struct spng_offs
|
||||
{
|
||||
int32_t x, y;
|
||||
uint8_t unit_specifier;
|
||||
};
|
||||
|
||||
struct spng_exif
|
||||
{
|
||||
size_t length;
|
||||
char *data;
|
||||
};
|
||||
|
||||
struct spng_chunk
|
||||
{
|
||||
size_t offset;
|
||||
uint32_t length;
|
||||
uint8_t type[4];
|
||||
uint32_t crc;
|
||||
};
|
||||
|
||||
enum spng_location
|
||||
{
|
||||
SPNG_AFTER_IHDR = 1,
|
||||
SPNG_AFTER_PLTE = 2,
|
||||
SPNG_AFTER_IDAT = 8,
|
||||
};
|
||||
|
||||
struct spng_unknown_chunk
|
||||
{
|
||||
uint8_t type[4];
|
||||
size_t length;
|
||||
void *data;
|
||||
enum spng_location location;
|
||||
};
|
||||
|
||||
enum spng_option
|
||||
{
|
||||
SPNG_KEEP_UNKNOWN_CHUNKS = 1,
|
||||
|
||||
SPNG_IMG_COMPRESSION_LEVEL,
|
||||
SPNG_IMG_WINDOW_BITS,
|
||||
SPNG_IMG_MEM_LEVEL,
|
||||
SPNG_IMG_COMPRESSION_STRATEGY,
|
||||
|
||||
SPNG_TEXT_COMPRESSION_LEVEL,
|
||||
SPNG_TEXT_WINDOW_BITS,
|
||||
SPNG_TEXT_MEM_LEVEL,
|
||||
SPNG_TEXT_COMPRESSION_STRATEGY,
|
||||
|
||||
SPNG_FILTER_CHOICE,
|
||||
SPNG_CHUNK_COUNT_LIMIT,
|
||||
SPNG_ENCODE_TO_BUFFER,
|
||||
};
|
||||
|
||||
typedef void* SPNG_CDECL spng_malloc_fn(size_t size);
|
||||
typedef void* SPNG_CDECL spng_realloc_fn(void* ptr, size_t size);
|
||||
typedef void* SPNG_CDECL spng_calloc_fn(size_t count, size_t size);
|
||||
typedef void SPNG_CDECL spng_free_fn(void* ptr);
|
||||
|
||||
struct spng_alloc
|
||||
{
|
||||
spng_malloc_fn *malloc_fn;
|
||||
spng_realloc_fn *realloc_fn;
|
||||
spng_calloc_fn *calloc_fn;
|
||||
spng_free_fn *free_fn;
|
||||
};
|
||||
|
||||
struct spng_row_info
|
||||
{
|
||||
uint32_t scanline_idx;
|
||||
uint32_t row_num; /* deinterlaced row index */
|
||||
int pass;
|
||||
uint8_t filter;
|
||||
};
|
||||
|
||||
typedef struct spng_ctx spng_ctx;
|
||||
|
||||
typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length);
|
||||
typedef int spng_write_fn(spng_ctx *ctx, void *user, void *src, size_t length);
|
||||
|
||||
typedef int spng_rw_fn(spng_ctx *ctx, void *user, void *dst_src, size_t length);
|
||||
|
||||
SPNG_API spng_ctx *spng_ctx_new(int flags);
|
||||
SPNG_API spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags);
|
||||
SPNG_API void spng_ctx_free(spng_ctx *ctx);
|
||||
|
||||
SPNG_API int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size);
|
||||
SPNG_API int spng_set_png_stream(spng_ctx *ctx, spng_rw_fn *rw_func, void *user);
|
||||
SPNG_API int spng_set_png_file(spng_ctx *ctx, FILE *file);
|
||||
|
||||
SPNG_API void *spng_get_png_buffer(spng_ctx *ctx, size_t *len, int *error);
|
||||
|
||||
SPNG_API int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height);
|
||||
SPNG_API int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height);
|
||||
|
||||
SPNG_API int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_size);
|
||||
SPNG_API int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_size);
|
||||
|
||||
SPNG_API int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary);
|
||||
|
||||
SPNG_API int spng_set_option(spng_ctx *ctx, enum spng_option option, int value);
|
||||
SPNG_API int spng_get_option(spng_ctx *ctx, enum spng_option option, int *value);
|
||||
|
||||
SPNG_API int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len);
|
||||
|
||||
/* Decode */
|
||||
SPNG_API int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags);
|
||||
|
||||
/* Progressive decode */
|
||||
SPNG_API int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len);
|
||||
SPNG_API int spng_decode_row(spng_ctx *ctx, void *out, size_t len);
|
||||
SPNG_API int spng_decode_chunks(spng_ctx *ctx);
|
||||
|
||||
/* Encode/decode */
|
||||
SPNG_API int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info);
|
||||
|
||||
/* Encode */
|
||||
SPNG_API int spng_encode_image(spng_ctx *ctx, const void *img, size_t len, int fmt, int flags);
|
||||
|
||||
/* Progressive encode */
|
||||
SPNG_API int spng_encode_scanline(spng_ctx *ctx, const void *scanline, size_t len);
|
||||
SPNG_API int spng_encode_row(spng_ctx *ctx, const void *row, size_t len);
|
||||
SPNG_API int spng_encode_chunks(spng_ctx *ctx);
|
||||
|
||||
SPNG_API int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr);
|
||||
SPNG_API int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte);
|
||||
SPNG_API int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns);
|
||||
SPNG_API int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm);
|
||||
SPNG_API int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int);
|
||||
SPNG_API int spng_get_gama(spng_ctx *ctx, double *gamma);
|
||||
SPNG_API int spng_get_gama_int(spng_ctx *ctx, uint32_t *gama_int);
|
||||
SPNG_API int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp);
|
||||
SPNG_API int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit);
|
||||
SPNG_API int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent);
|
||||
SPNG_API int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text);
|
||||
SPNG_API int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd);
|
||||
SPNG_API int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist);
|
||||
SPNG_API int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys);
|
||||
SPNG_API int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt);
|
||||
SPNG_API int spng_get_time(spng_ctx *ctx, struct spng_time *time);
|
||||
SPNG_API int spng_get_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t *n_chunks);
|
||||
|
||||
/* Official extensions */
|
||||
SPNG_API int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs);
|
||||
SPNG_API int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif);
|
||||
|
||||
|
||||
SPNG_API int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr);
|
||||
SPNG_API int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte);
|
||||
SPNG_API int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns);
|
||||
SPNG_API int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm);
|
||||
SPNG_API int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int);
|
||||
SPNG_API int spng_set_gama(spng_ctx *ctx, double gamma);
|
||||
SPNG_API int spng_set_gama_int(spng_ctx *ctx, uint32_t gamma);
|
||||
SPNG_API int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp);
|
||||
SPNG_API int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit);
|
||||
SPNG_API int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent);
|
||||
SPNG_API int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text);
|
||||
SPNG_API int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd);
|
||||
SPNG_API int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist);
|
||||
SPNG_API int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys);
|
||||
SPNG_API int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt);
|
||||
SPNG_API int spng_set_time(spng_ctx *ctx, struct spng_time *time);
|
||||
SPNG_API int spng_set_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t n_chunks);
|
||||
|
||||
/* Official extensions */
|
||||
SPNG_API int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs);
|
||||
SPNG_API int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif);
|
||||
|
||||
|
||||
SPNG_API const char *spng_strerror(int err);
|
||||
SPNG_API const char *spng_version_string(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SPNG_H */
|
7
externals/lodepng/CMakeLists.txt
vendored
7
externals/lodepng/CMakeLists.txt
vendored
@ -1,7 +0,0 @@
|
||||
add_library(lodepng
|
||||
lodepng/lodepng.cpp
|
||||
lodepng/lodepng.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(lodepng)
|
||||
target_include_directories(lodepng INTERFACE lodepng)
|
1
externals/lodepng/lodepng
vendored
1
externals/lodepng/lodepng
vendored
Submodule externals/lodepng/lodepng deleted from 18964554bc
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 a49166a89a
17
externals/zlib-ng/CMakeLists.txt
vendored
Normal file
17
externals/zlib-ng/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
set(ZLIB_ENABLE_TESTS OFF)
|
||||
set(ZLIBNG_ENABLE_TESTS OFF)
|
||||
set(WITH_GZFILEOP OFF)
|
||||
set(WITH_GTEST OFF)
|
||||
set(ZLIB_COMPAT ON)
|
||||
set(SKIP_INSTALL_ALL ON)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build shared library" OFF)
|
||||
|
||||
add_subdirectory(zlib-ng)
|
||||
|
||||
# Set ZLIB variables for find_package used by other projects
|
||||
set(ZLIB_INCLUDE_DIR ${CMAKE_BINARY_DIR}/zlib-ng CACHE STRING "Path to zlib include directory")
|
||||
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "Path to zlib library")
|
||||
|
||||
# Setup zlib alias project so FindZLIB doesn't recreate it
|
||||
add_library(ZLIB::ZLIB ALIAS zlib)
|
1
externals/zlib-ng/zlib-ng
vendored
Submodule
1
externals/zlib-ng/zlib-ng
vendored
Submodule
Submodule externals/zlib-ng/zlib-ng added at c970422caa
@ -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()
|
||||
|
@ -18,15 +18,6 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
// This is important as it will run lint but not abort on error
|
||||
// Lint has some overly obnoxious "errors" that should really be warnings
|
||||
abortOnError false
|
||||
|
||||
//Uncomment disable lines for test builds...
|
||||
//disable 'MissingTranslation'bin
|
||||
//disable 'ExtraTranslation'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO If this is ever modified, change application_id in strings.xml
|
||||
@ -100,6 +91,10 @@ android {
|
||||
path "../../../CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
namespace 'org.citra.citra_emu'
|
||||
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.citra.citra_emu">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false"/>
|
||||
@ -39,7 +38,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"
|
||||
|
@ -355,6 +355,9 @@ public final class SettingsFragmentPresenter {
|
||||
mView.getActivity().setTitle(R.string.preferences_graphics);
|
||||
|
||||
SettingSection rendererSection = mSettings.getSection(Settings.SECTION_RENDERER);
|
||||
Setting graphicsApi = rendererSection.getSetting(SettingsFile.KEY_GRAPHICS_API);
|
||||
Setting spvShaderGen = rendererSection.getSetting(SettingsFile.KEY_SPIRV_SHADER_GEN);
|
||||
Setting asyncShaders = rendererSection.getSetting(SettingsFile.KEY_ASYNC_SHADERS);
|
||||
Setting resolutionFactor = rendererSection.getSetting(SettingsFile.KEY_RESOLUTION_FACTOR);
|
||||
Setting filterMode = rendererSection.getSetting(SettingsFile.KEY_FILTER_MODE);
|
||||
Setting shadersAccurateMul = rendererSection.getSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL);
|
||||
@ -371,6 +374,9 @@ public final class SettingsFragmentPresenter {
|
||||
//Setting preloadTextures = utilitySection.getSetting(SettingsFile.KEY_PRELOAD_TEXTURES);
|
||||
|
||||
sl.add(new HeaderSetting(null, null, R.string.renderer, 0));
|
||||
sl.add(new SingleChoiceSetting(SettingsFile.KEY_GRAPHICS_API, Settings.SECTION_RENDERER, R.string.graphics_api, 0, R.array.graphicsApiNames, R.array.graphicsApiValues, 2, graphicsApi));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_SPIRV_SHADER_GEN, Settings.SECTION_RENDERER, R.string.spirv_shader_gen, R.string.spirv_shader_gen_description, true, spvShaderGen));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_ASYNC_SHADERS, Settings.SECTION_RENDERER, R.string.async_shaders, R.string.async_shaders_description, false, asyncShaders));
|
||||
sl.add(new SliderSetting(SettingsFile.KEY_RESOLUTION_FACTOR, Settings.SECTION_RENDERER, R.string.internal_resolution, R.string.internal_resolution_description, 1, 4, "x", 1, resolutionFactor));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_FILTER_MODE, Settings.SECTION_RENDERER, R.string.linear_filtering, R.string.linear_filtering_description, true, filterMode));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL, Settings.SECTION_RENDERER, R.string.shaders_accurate_mul, R.string.shaders_accurate_mul_description, false, shadersAccurateMul));
|
||||
@ -412,11 +418,13 @@ public final class SettingsFragmentPresenter {
|
||||
Setting hardwareRenderer = rendererSection.getSetting(SettingsFile.KEY_HW_RENDERER);
|
||||
Setting hardwareShader = rendererSection.getSetting(SettingsFile.KEY_HW_SHADER);
|
||||
Setting vsyncEnable = rendererSection.getSetting(SettingsFile.KEY_USE_VSYNC);
|
||||
Setting rendererDebug = rendererSection.getSetting(SettingsFile.KEY_RENDERER_DEBUG);
|
||||
|
||||
sl.add(new HeaderSetting(null, null, R.string.debug_warning, 0));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_CPU_JIT, Settings.SECTION_CORE, R.string.cpu_jit, R.string.cpu_jit_description, true, useCpuJit, true, mView));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_RENDERER, Settings.SECTION_RENDERER, R.string.hw_renderer, R.string.hw_renderer_description, true, hardwareRenderer, true, mView));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_SHADER, Settings.SECTION_RENDERER, R.string.hw_shaders, R.string.hw_shaders_description, true, hardwareShader, true, mView));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_VSYNC, Settings.SECTION_RENDERER, R.string.vsync, R.string.vsync_description, true, vsyncEnable));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_RENDERER_DEBUG, Settings.SECTION_RENDERER, R.string.renderer_debug, R.string.renderer_debug_description, false, rendererDebug));
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ public final class SettingsFile {
|
||||
|
||||
public static final String KEY_PREMIUM = "premium";
|
||||
|
||||
public static final String KEY_GRAPHICS_API = "graphics_api";
|
||||
public static final String KEY_SPIRV_SHADER_GEN = "spirv_shader_gen";
|
||||
public static final String KEY_RENDERER_DEBUG = "renderer_debug";
|
||||
public static final String KEY_ASYNC_SHADERS = "async_shader_compilation";
|
||||
public static final String KEY_HW_RENDERER = "use_hw_renderer";
|
||||
public static final String KEY_HW_SHADER = "use_hw_shader";
|
||||
public static final String KEY_SHADERS_ACCURATE_MUL = "shaders_accurate_mul";
|
||||
|
@ -19,14 +19,16 @@ add_library(citra-android SHARED
|
||||
default_ini.h
|
||||
emu_window/emu_window.cpp
|
||||
emu_window/emu_window.h
|
||||
emu_window/emu_window_gl.cpp
|
||||
emu_window/emu_window_gl.h
|
||||
emu_window/emu_window_vk.cpp
|
||||
emu_window/emu_window_vk.h
|
||||
game_info.cpp
|
||||
game_info.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
id_cache.cpp
|
||||
id_cache.h
|
||||
lodepng_image_interface.cpp
|
||||
lodepng_image_interface.h
|
||||
mic.cpp
|
||||
mic.h
|
||||
native.cpp
|
||||
@ -36,6 +38,6 @@ add_library(citra-android SHARED
|
||||
)
|
||||
|
||||
target_link_libraries(citra-android PRIVATE audio_core common core input_common network)
|
||||
target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics lodepng log mediandk yuv)
|
||||
target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics log mediandk yuv)
|
||||
|
||||
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} citra-android)
|
||||
|
@ -121,7 +121,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.renderer_debug = sdl2_config->GetBoolean("Renderer", "renderer_debug", false);
|
||||
Settings::values.async_shader_compilation =
|
||||
sdl2_config->GetBoolean("Renderer", "async_shader_compilation", true);
|
||||
Settings::values.spirv_shader_gen =
|
||||
sdl2_config->GetBoolean("Renderer", "spirv_shader_gen", true);
|
||||
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 =
|
||||
|
@ -98,9 +98,21 @@ use_cpu_jit =
|
||||
cpu_clock_percentage =
|
||||
|
||||
[Renderer]
|
||||
# Whether to render using GLES or OpenGL
|
||||
# 0: OpenGL, 1 (default): GLES
|
||||
use_gles =
|
||||
# Whether to render using OpenGL or Vulkan
|
||||
# 1: OpenGL, 2 (default): Vulkan
|
||||
graphics_api =
|
||||
|
||||
# Whether to emit PICA fragment shader using SPIRV or GLSL
|
||||
# 1: SPIR-V (default), 0: GLSL
|
||||
spirv_shader_gen =
|
||||
|
||||
# Whether to use a worker thread for vulkan command buffer recording
|
||||
# 1: On (default), 0: Off
|
||||
async_command_recording =
|
||||
|
||||
# Whether to enable additional debugging information during emulation
|
||||
# 1: On, 0 (default): Off
|
||||
renderer_debug =
|
||||
|
||||
# Whether to use software or hardware rendering.
|
||||
# 0: Software, 1 (default): Hardware
|
||||
|
@ -6,10 +6,7 @@
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/main.h"
|
||||
@ -20,52 +17,6 @@
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
static constexpr std::array<EGLint, 15> egl_attribs{EGL_SURFACE_TYPE,
|
||||
EGL_WINDOW_BIT,
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_ES3_BIT_KHR,
|
||||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_GREEN_SIZE,
|
||||
8,
|
||||
EGL_RED_SIZE,
|
||||
8,
|
||||
EGL_DEPTH_SIZE,
|
||||
0,
|
||||
EGL_STENCIL_SIZE,
|
||||
0,
|
||||
EGL_NONE};
|
||||
static constexpr std::array<EGLint, 5> egl_empty_attribs{EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
|
||||
static constexpr std::array<EGLint, 4> egl_context_attribs{EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
|
||||
|
||||
SharedContext_Android::SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config,
|
||||
EGLContext egl_share_context)
|
||||
: egl_display{egl_display}, egl_surface{eglCreatePbufferSurface(egl_display, egl_config,
|
||||
egl_empty_attribs.data())},
|
||||
egl_context{eglCreateContext(egl_display, egl_config, egl_share_context,
|
||||
egl_context_attribs.data())} {
|
||||
ASSERT_MSG(egl_surface, "eglCreatePbufferSurface() failed!");
|
||||
ASSERT_MSG(egl_context, "eglCreateContext() failed!");
|
||||
}
|
||||
|
||||
SharedContext_Android::~SharedContext_Android() {
|
||||
if (!eglDestroySurface(egl_display, egl_surface)) {
|
||||
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
|
||||
}
|
||||
|
||||
if (!eglDestroyContext(egl_display, egl_context)) {
|
||||
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
|
||||
}
|
||||
}
|
||||
|
||||
void SharedContext_Android::MakeCurrent() {
|
||||
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
||||
}
|
||||
|
||||
void SharedContext_Android::DoneCurrent() {
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
|
||||
static bool IsPortraitMode() {
|
||||
return JNI_FALSE != IDCache::GetEnvForThread()->CallStaticBooleanMethod(
|
||||
IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode());
|
||||
@ -79,6 +30,10 @@ static void UpdateLandscapeScreenLayout() {
|
||||
|
||||
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
render_window = surface;
|
||||
|
||||
window_info.type = Frontend::WindowSystemType::Android;
|
||||
window_info.render_surface = surface;
|
||||
|
||||
StopPresenting();
|
||||
}
|
||||
|
||||
@ -98,6 +53,7 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) {
|
||||
void EmuWindow_Android::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) {
|
||||
@ -107,7 +63,7 @@ void EmuWindow_Android::OnFramebufferSizeChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) {
|
||||
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) : host_window{surface} {
|
||||
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android");
|
||||
|
||||
if (!surface) {
|
||||
@ -115,108 +71,10 @@ EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
window_width = ANativeWindow_getWidth(surface);
|
||||
window_height = ANativeWindow_getHeight(surface);
|
||||
|
||||
Network::Init();
|
||||
|
||||
host_window = surface;
|
||||
|
||||
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
|
||||
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
|
||||
return;
|
||||
}
|
||||
if (eglInitialize(egl_display, 0, 0) != EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglInitialize() failed");
|
||||
return;
|
||||
}
|
||||
if (EGLint egl_num_configs{}; eglChooseConfig(egl_display, egl_attribs.data(), &egl_config, 1,
|
||||
&egl_num_configs) != EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglChooseConfig() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
CreateWindowSurface();
|
||||
|
||||
if (eglQuerySurface(egl_display, egl_surface, EGL_WIDTH, &window_width) != EGL_TRUE) {
|
||||
return;
|
||||
}
|
||||
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
|
||||
egl_context == EGL_NO_CONTEXT) {
|
||||
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
|
||||
return;
|
||||
}
|
||||
if (eglSurfaceAttrib(egl_display, egl_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) !=
|
||||
EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglSurfaceAttrib() failed");
|
||||
return;
|
||||
}
|
||||
if (core_context = CreateSharedContext(); !core_context) {
|
||||
LOG_CRITICAL(Frontend, "CreateSharedContext() failed");
|
||||
return;
|
||||
}
|
||||
if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) != EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglMakeCurrent() failed");
|
||||
return;
|
||||
}
|
||||
if (!gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress)) {
|
||||
LOG_CRITICAL(Frontend, "gladLoadGLES2Loader() failed");
|
||||
return;
|
||||
}
|
||||
if (!eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0)) {
|
||||
LOG_CRITICAL(Frontend, "eglSwapInterval() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
OnFramebufferSizeChanged();
|
||||
}
|
||||
|
||||
bool EmuWindow_Android::CreateWindowSurface() {
|
||||
if (!host_window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLint format{};
|
||||
eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &format);
|
||||
ANativeWindow_setBuffersGeometry(host_window, 0, 0, format);
|
||||
|
||||
if (egl_surface = eglCreateWindowSurface(egl_display, egl_config, host_window, 0);
|
||||
egl_surface == EGL_NO_SURFACE) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return !!egl_surface;
|
||||
}
|
||||
|
||||
void EmuWindow_Android::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::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::~EmuWindow_Android() {
|
||||
@ -224,34 +82,6 @@ EmuWindow_Android::~EmuWindow_Android() {
|
||||
DestroyContext();
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android::CreateSharedContext() const {
|
||||
return std::make_unique<SharedContext_Android>(egl_display, egl_config, egl_context);
|
||||
}
|
||||
|
||||
void EmuWindow_Android::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::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::PollEvents() {
|
||||
if (!render_window) {
|
||||
return;
|
||||
|
@ -1,42 +1,17 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "core/frontend/emu_window.h"
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
class SharedContext_Android : public Frontend::GraphicsContext {
|
||||
public:
|
||||
SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config,
|
||||
EGLContext egl_share_context);
|
||||
|
||||
~SharedContext_Android() override;
|
||||
|
||||
void MakeCurrent() override;
|
||||
|
||||
void DoneCurrent() override;
|
||||
|
||||
private:
|
||||
EGLDisplay egl_display{};
|
||||
EGLSurface egl_surface{};
|
||||
EGLContext egl_context{};
|
||||
};
|
||||
|
||||
class EmuWindow_Android : public Frontend::EmuWindow {
|
||||
public:
|
||||
EmuWindow_Android(ANativeWindow* surface);
|
||||
~EmuWindow_Android();
|
||||
|
||||
void Present();
|
||||
|
||||
/// Called by the onSurfaceChanges() method to change the surface
|
||||
void OnSurfaceChanged(ANativeWindow* surface);
|
||||
|
||||
@ -47,31 +22,34 @@ public:
|
||||
void OnTouchMoved(int x, int y);
|
||||
|
||||
void PollEvents() override;
|
||||
|
||||
void MakeCurrent() override;
|
||||
|
||||
void DoneCurrent() override;
|
||||
|
||||
void TryPresenting();
|
||||
void StopPresenting();
|
||||
virtual void TryPresenting() = 0;
|
||||
|
||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||
virtual void StopPresenting() = 0;
|
||||
|
||||
private:
|
||||
protected:
|
||||
void OnFramebufferSizeChanged();
|
||||
bool CreateWindowSurface();
|
||||
void DestroyWindowSurface();
|
||||
void DestroyContext();
|
||||
|
||||
/// Creates the API specific window surface
|
||||
virtual bool CreateWindowSurface() {}
|
||||
|
||||
/// Destroys the API specific window surface
|
||||
virtual void DestroyWindowSurface() {}
|
||||
|
||||
/// Destroys the graphics context
|
||||
virtual void DestroyContext() {}
|
||||
|
||||
protected:
|
||||
ANativeWindow* render_window{};
|
||||
ANativeWindow* host_window{};
|
||||
|
||||
int window_width{};
|
||||
int window_height{};
|
||||
|
||||
EGLConfig egl_config;
|
||||
EGLSurface egl_surface{};
|
||||
EGLContext egl_context{};
|
||||
EGLDisplay egl_display{};
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> core_context;
|
||||
|
||||
enum class PresentingState {
|
||||
|
205
src/android/app/src/main/jni/emu_window/emu_window_gl.cpp
Normal file
205
src/android/app/src/main/jni/emu_window/emu_window_gl.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
// 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 <glad/glad.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/main.h"
|
||||
#include "jni/emu_window/emu_window_gl.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 constexpr std::array<EGLint, 15> egl_attribs{EGL_SURFACE_TYPE,
|
||||
EGL_WINDOW_BIT,
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_ES3_BIT_KHR,
|
||||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_GREEN_SIZE,
|
||||
8,
|
||||
EGL_RED_SIZE,
|
||||
8,
|
||||
EGL_DEPTH_SIZE,
|
||||
0,
|
||||
EGL_STENCIL_SIZE,
|
||||
0,
|
||||
EGL_NONE};
|
||||
static constexpr std::array<EGLint, 5> egl_empty_attribs{EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
|
||||
static constexpr std::array<EGLint, 4> egl_context_attribs{EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
|
||||
|
||||
class SharedContext_Android : public Frontend::GraphicsContext {
|
||||
public:
|
||||
SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config,
|
||||
EGLContext egl_share_context)
|
||||
: egl_display{egl_display}, egl_surface{eglCreatePbufferSurface(egl_display, egl_config,
|
||||
egl_empty_attribs.data())},
|
||||
egl_context{eglCreateContext(egl_display, egl_config, egl_share_context,
|
||||
egl_context_attribs.data())} {
|
||||
ASSERT_MSG(egl_surface, "eglCreatePbufferSurface() failed!");
|
||||
ASSERT_MSG(egl_context, "eglCreateContext() failed!");
|
||||
}
|
||||
|
||||
~SharedContext_Android() override {
|
||||
if (!eglDestroySurface(egl_display, egl_surface)) {
|
||||
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
|
||||
}
|
||||
|
||||
if (!eglDestroyContext(egl_display, egl_context)) {
|
||||
LOG_CRITICAL(Frontend, "eglDestroySurface() failed");
|
||||
}
|
||||
}
|
||||
|
||||
void MakeCurrent() {
|
||||
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
||||
}
|
||||
|
||||
void DoneCurrent() {
|
||||
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
|
||||
private:
|
||||
EGLDisplay egl_display{};
|
||||
EGLSurface egl_surface{};
|
||||
EGLContext egl_context{};
|
||||
};
|
||||
|
||||
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(ANativeWindow* surface)
|
||||
: EmuWindow_Android{surface} {
|
||||
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
|
||||
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
|
||||
return;
|
||||
}
|
||||
if (eglInitialize(egl_display, 0, 0) != EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglInitialize() failed");
|
||||
return;
|
||||
}
|
||||
if (EGLint egl_num_configs{}; eglChooseConfig(egl_display, egl_attribs.data(), &egl_config, 1,
|
||||
&egl_num_configs) != EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglChooseConfig() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
CreateWindowSurface();
|
||||
|
||||
if (eglQuerySurface(egl_display, egl_surface, EGL_WIDTH, &window_width) != EGL_TRUE) {
|
||||
return;
|
||||
}
|
||||
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
|
||||
egl_context == EGL_NO_CONTEXT) {
|
||||
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
|
||||
return;
|
||||
}
|
||||
if (eglSurfaceAttrib(egl_display, egl_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) !=
|
||||
EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglSurfaceAttrib() failed");
|
||||
return;
|
||||
}
|
||||
if (core_context = CreateSharedContext(); !core_context) {
|
||||
LOG_CRITICAL(Frontend, "CreateSharedContext() failed");
|
||||
return;
|
||||
}
|
||||
if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) != EGL_TRUE) {
|
||||
LOG_CRITICAL(Frontend, "eglMakeCurrent() failed");
|
||||
return;
|
||||
}
|
||||
if (!gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress)) {
|
||||
LOG_CRITICAL(Frontend, "gladLoadGLES2Loader() failed");
|
||||
return;
|
||||
}
|
||||
if (!eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0)) {
|
||||
LOG_CRITICAL(Frontend, "eglSwapInterval() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
OnFramebufferSizeChanged();
|
||||
}
|
||||
|
||||
bool EmuWindow_Android_OpenGL::CreateWindowSurface() {
|
||||
if (!host_window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLint format{};
|
||||
eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &format);
|
||||
ANativeWindow_setBuffersGeometry(host_window, 0, 0, format);
|
||||
|
||||
if (egl_surface = eglCreateWindowSurface(egl_display, egl_config, host_window, 0);
|
||||
egl_surface == EGL_NO_SURFACE) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return !!egl_surface;
|
||||
}
|
||||
|
||||
void EmuWindow_Android_OpenGL::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_OpenGL::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;
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_OpenGL::CreateSharedContext() const {
|
||||
return std::make_unique<SharedContext_Android>(egl_display, egl_config, egl_context);
|
||||
}
|
||||
|
||||
void EmuWindow_Android_OpenGL::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_OpenGL::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);
|
||||
}
|
||||
}
|
36
src/android/app/src/main/jni/emu_window/emu_window_gl.h
Normal file
36
src/android/app/src/main/jni/emu_window/emu_window_gl.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "jni/emu_window/emu_window.h"
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
class EmuWindow_Android_OpenGL : public EmuWindow_Android {
|
||||
public:
|
||||
EmuWindow_Android_OpenGL(ANativeWindow* surface);
|
||||
~EmuWindow_Android_OpenGL() override = default;
|
||||
|
||||
void TryPresenting() override;
|
||||
void StopPresenting() override;
|
||||
|
||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
private:
|
||||
bool CreateWindowSurface() override;
|
||||
void DestroyWindowSurface() override;
|
||||
void DestroyContext() override;
|
||||
|
||||
private:
|
||||
EGLConfig egl_config;
|
||||
EGLSurface egl_surface{};
|
||||
EGLContext egl_context{};
|
||||
EGLDisplay egl_display{};
|
||||
};
|
65
src/android/app/src/main/jni/emu_window/emu_window_vk.cpp
Normal file
65
src/android/app/src/main/jni/emu_window/emu_window_vk.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
// 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"
|
||||
|
||||
class SharedContext_Android : public Frontend::GraphicsContext {};
|
||||
|
||||
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(ANativeWindow* surface)
|
||||
: EmuWindow_Android{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;
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_Vulkan::CreateSharedContext() const {
|
||||
return std::make_unique<SharedContext_Android>();
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::StopPresenting() {
|
||||
presenting_state = PresentingState::Stopped;
|
||||
}
|
||||
|
||||
void EmuWindow_Android_Vulkan::TryPresenting() {
|
||||
if (presenting_state != PresentingState::Running) {
|
||||
if (presenting_state == PresentingState::Initial) {
|
||||
presenting_state = PresentingState::Running;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (VideoCore::g_renderer) {
|
||||
VideoCore::g_renderer->TryPresent(0);
|
||||
}
|
||||
}
|
23
src/android/app/src/main/jni/emu_window/emu_window_vk.h
Normal file
23
src/android/app/src/main/jni/emu_window/emu_window_vk.h
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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jni/emu_window/emu_window.h"
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
class EmuWindow_Android_Vulkan : public EmuWindow_Android {
|
||||
public:
|
||||
EmuWindow_Android_Vulkan(ANativeWindow* surface);
|
||||
~EmuWindow_Android_Vulkan() override = default;
|
||||
|
||||
void TryPresenting() override;
|
||||
void StopPresenting() override;
|
||||
|
||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
private:
|
||||
bool CreateWindowSurface() override;
|
||||
};
|
@ -1,44 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <lodepng.h>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "jni/lodepng_image_interface.h"
|
||||
|
||||
bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
|
||||
const std::string& path) {
|
||||
FileUtil::IOFile file(path, "rb");
|
||||
size_t read_size = file.GetSize();
|
||||
std::vector<u8> in(read_size);
|
||||
if (file.ReadBytes(&in[0], read_size) != read_size) {
|
||||
LOG_CRITICAL(Frontend, "Failed to decode {}", path);
|
||||
}
|
||||
u32 lodepng_ret = lodepng::decode(dst, width, height, in);
|
||||
if (lodepng_ret) {
|
||||
LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path,
|
||||
lodepng_error_text(lodepng_ret));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src,
|
||||
u32 width, u32 height) {
|
||||
std::vector<u8> out;
|
||||
u32 lodepng_ret = lodepng::encode(out, src, width, height);
|
||||
if (lodepng_ret) {
|
||||
LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path,
|
||||
lodepng_error_text(lodepng_ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(path, "wb");
|
||||
if (file.WriteBytes(&out[0], out.size()) != out.size()) {
|
||||
LOG_CRITICAL(Frontend, "Failed to save encode to path={}", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/frontend/image_interface.h"
|
||||
|
||||
class LodePNGImageInterface final : public Frontend::ImageInterface {
|
||||
public:
|
||||
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
|
||||
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||
u32 height) override;
|
||||
};
|
@ -25,7 +25,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"
|
||||
@ -35,17 +34,18 @@
|
||||
#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_gl.h"
|
||||
#include "jni/emu_window/emu_window_vk.h"
|
||||
#include "jni/game_info.h"
|
||||
#include "jni/game_settings.h"
|
||||
#include "jni/id_cache.h"
|
||||
#include "jni/input_manager.h"
|
||||
#include "jni/lodepng_image_interface.h"
|
||||
#include "jni/mic.h"
|
||||
#include "jni/native.h"
|
||||
#include "jni/ndk_motion.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -149,7 +149,17 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||
return Core::System::ResultStatus::ErrorLoader;
|
||||
}
|
||||
|
||||
window = std::make_unique<EmuWindow_Android>(s_surf);
|
||||
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
|
||||
switch (graphics_api) {
|
||||
case Settings::GraphicsAPI::OpenGLES:
|
||||
window = std::make_unique<EmuWindow_Android_OpenGL>(s_surf);
|
||||
break;
|
||||
case Settings::GraphicsAPI::Vulkan:
|
||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown graphics API {}", graphics_api);
|
||||
}
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
@ -177,9 +187,6 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||
system.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>());
|
||||
system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>());
|
||||
|
||||
// Register generic image interface
|
||||
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
|
||||
|
||||
// Register real Mic factory
|
||||
Frontend::Mic::RegisterRealMicFactory(std::make_unique<Mic::AndroidFactory>());
|
||||
|
||||
@ -263,6 +270,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env,
|
||||
if (window) {
|
||||
window->OnSurfaceChanged(s_surf);
|
||||
}
|
||||
if (VideoCore::g_renderer) {
|
||||
VideoCore::g_renderer->NotifySurfaceChanged();
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend, "Surface changed");
|
||||
}
|
||||
|
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.
@ -171,4 +171,14 @@
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="graphicsApiNames">
|
||||
<item>OpenGL</item>
|
||||
<item>Vulkan</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="graphicsApiValues">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
</resources>
|
||||
|
@ -73,6 +73,13 @@
|
||||
<!-- Graphics settings strings -->
|
||||
<string name="renderer">Renderer</string>
|
||||
<string name="vsync">Enable V-Sync</string>
|
||||
<string name="graphics_api">Graphics API</string>
|
||||
<string name="spirv_shader_gen">Enable SPIR-V shader generation</string>
|
||||
<string name="spirv_shader_gen_description">Emits the fragment shader used to emulate PICA using SPIR-V instead of GLSL</string>
|
||||
<string name="async_shaders">Enable asynchronous shader compilation</string>
|
||||
<string name="async_shaders_description">Compiles shaders in the background to reduce stuttering during gameplay. When enabled expect temporary graphical glitches</string>
|
||||
<string name="renderer_debug">Enable debug renderer</string>
|
||||
<string name="renderer_debug_description">Log additional graphics related debug information. When enabled, game performance will be significantly reduced</string>
|
||||
<string name="vsync_description">Synchronizes the game frame rate to the refresh rate of your device.</string>
|
||||
<string name="linear_filtering">Enable linear filtering</string>
|
||||
<string name="linear_filtering_description">Enables linear filtering, which causes game visuals to appear smoother.</string>
|
||||
|
@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -8,8 +8,6 @@ add_executable(citra
|
||||
default_ini.h
|
||||
emu_window/emu_window_sdl2.cpp
|
||||
emu_window/emu_window_sdl2.h
|
||||
lodepng_image_interface.cpp
|
||||
lodepng_image_interface.h
|
||||
precompiled_headers.h
|
||||
resource.h
|
||||
)
|
||||
@ -17,7 +15,7 @@ add_executable(citra
|
||||
create_target_directory_groups(citra)
|
||||
|
||||
target_link_libraries(citra PRIVATE common core input_common network)
|
||||
target_link_libraries(citra PRIVATE inih glad lodepng)
|
||||
target_link_libraries(citra PRIVATE inih glad)
|
||||
if (MSVC)
|
||||
target_link_libraries(citra PRIVATE getopt)
|
||||
endif()
|
||||
|
@ -7,37 +7,30 @@
|
||||
#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"
|
||||
#include "common/common_paths.h"
|
||||
#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"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#undef _UNICODE
|
||||
#include <getopt.h>
|
||||
@ -357,9 +350,6 @@ int main(int argc, char** argv) {
|
||||
// Register frontend applets
|
||||
Frontend::RegisterDefaultApplets();
|
||||
|
||||
// Register generic image interface
|
||||
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
|
||||
|
||||
EmuWindow_SDL2::InitializeSDL2();
|
||||
|
||||
const auto emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen, false)};
|
||||
@ -368,7 +358,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());
|
||||
|
@ -1,29 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <lodepng.h>
|
||||
#include "citra/lodepng_image_interface.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
|
||||
const std::string& path) {
|
||||
u32 lodepng_ret = lodepng::decode(dst, width, height, path);
|
||||
if (lodepng_ret) {
|
||||
LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path,
|
||||
lodepng_error_text(lodepng_ret));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src,
|
||||
u32 width, u32 height) {
|
||||
u32 lodepng_ret = lodepng::encode(path, src, width, height);
|
||||
if (lodepng_ret) {
|
||||
LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path,
|
||||
lodepng_error_text(lodepng_ret));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/frontend/image_interface.h"
|
||||
|
||||
class LodePNGImageInterface final : public Frontend::ImageInterface {
|
||||
public:
|
||||
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
|
||||
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||
u32 height) override;
|
||||
};
|
@ -167,8 +167,6 @@ add_executable(citra-qt
|
||||
precompiled_headers.h
|
||||
uisettings.cpp
|
||||
uisettings.h
|
||||
qt_image_interface.cpp
|
||||
qt_image_interface.h
|
||||
updater/updater.cpp
|
||||
updater/updater.h
|
||||
updater/updater_p.h
|
||||
@ -266,9 +264,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 Qt5::Concurrent)
|
||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia Qt5::Concurrent)
|
||||
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
if (NOT WIN32)
|
||||
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(citra-qt PRIVATE Qt5::DBus)
|
||||
endif()
|
||||
|
@ -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,21 @@
|
||||
#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 <objc/message.h>
|
||||
#include <objc/objc.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 +50,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 +61,7 @@ void EmuThread::run() {
|
||||
});
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||
emit HideLoadingScreen();
|
||||
|
||||
core_context.MakeCurrent();
|
||||
|
||||
@ -113,88 +120,246 @@ 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();
|
||||
|
||||
if (window) {
|
||||
#if defined(WIN32)
|
||||
// Our Win32 Qt external doesn't have the private API.
|
||||
wsi.render_surface = reinterpret_cast<void*>(window->winId());
|
||||
#elif defined(__APPLE__)
|
||||
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
|
||||
reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
wsi.display_connection = pni->nativeResourceForWindow("display", window);
|
||||
if (wsi.type == Frontend::WindowSystemType::Wayland)
|
||||
wsi.render_surface = pni->nativeResourceForWindow("surface", window);
|
||||
else
|
||||
wsi.render_surface = reinterpret_cast<void*>(window->winId());
|
||||
#endif
|
||||
wsi.render_surface_scale = static_cast<float>(window->devicePixelRatio());
|
||||
} else {
|
||||
wsi.render_surface = nullptr;
|
||||
wsi.render_surface_scale = 1.0f;
|
||||
}
|
||||
|
||||
return wsi;
|
||||
}
|
||||
|
||||
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_secondary_)
|
||||
@ -218,11 +383,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,42 +558,80 @@ 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) {
|
||||
if (res_scale == 0)
|
||||
if (res_scale == 0) {
|
||||
res_scale = VideoCore::GetResolutionScaleFactor();
|
||||
}
|
||||
|
||||
const auto layout{Layout::FrameLayoutFromResolutionScale(res_scale, is_secondary)};
|
||||
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
||||
VideoCore::RequestScreenshot(
|
||||
VideoCore::g_renderer->RequestScreenshot(
|
||||
screenshot_image.bits(),
|
||||
[this, screenshot_path] {
|
||||
const std::string std_screenshot_path = screenshot_path.toStdString();
|
||||
@ -445,6 +648,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 +682,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;
|
||||
|
||||
|
@ -482,6 +482,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) {
|
||||
@ -625,6 +627,10 @@ void Config::ReadPathValues() {
|
||||
void Config::ReadRendererValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Renderer"));
|
||||
|
||||
ReadGlobalSetting(Settings::values.physical_device);
|
||||
ReadGlobalSetting(Settings::values.async_shader_compilation);
|
||||
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__
|
||||
@ -992,6 +998,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) {
|
||||
@ -1103,6 +1111,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_shader_compilation);
|
||||
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/configuration_shared.h"
|
||||
#include "citra_qt/configuration/configure_debug.h"
|
||||
@ -12,7 +13,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"
|
||||
|
||||
// The QSlider doesn't have an easy way to set a custom step amount,
|
||||
// so we can just convert from the sliders range (0 - 79) to the expected
|
||||
@ -35,8 +38,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);
|
||||
|
||||
// Set a minimum width for the label to prevent the slider from changing size.
|
||||
// This scales across DPIs. (This value should be enough for "xxx%")
|
||||
@ -62,6 +97,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());
|
||||
|
||||
if (!Settings::IsConfiguringGlobal()) {
|
||||
if (Settings::values.cpu_clock_percentage.UsingGlobal()) {
|
||||
@ -91,6 +128,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();
|
||||
|
||||
ConfigurationShared::ApplyPerGameSetting(
|
||||
&Settings::values.cpu_clock_percentage, ui->clock_speed_combo,
|
||||
|
@ -23,5 +23,6 @@ public:
|
||||
void SetConfiguration();
|
||||
void SetupPerGameUI();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::ConfigureDebug> ui;
|
||||
};
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>443</width>
|
||||
<height>358</height>
|
||||
<width>523</width>
|
||||
<height>491</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -112,11 +112,23 @@
|
||||
<string>CPU</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QWidget" name="clock_speed_widget" native="true">
|
||||
<layout class="QHBoxLayout" name="clock_speed_layout">
|
||||
<property name="spacing">
|
||||
<number>7</number>
|
||||
<number>11</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="clock_speed_combo">
|
||||
@ -180,7 +192,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="toggle_cpu_jit">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes</p></body></html></string>
|
||||
@ -190,6 +202,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="toggle_dump_command_buffers">
|
||||
<property name="text">
|
||||
<string>Dump command buffers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="toggle_renderer_debug">
|
||||
<property name="text">
|
||||
<string>Enable debug renderer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -11,17 +11,25 @@
|
||||
#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->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);
|
||||
ui->toggle_async_shaders->setEnabled(not_running);
|
||||
ui->graphics_api_combo->setCurrentIndex(-1);
|
||||
|
||||
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
|
||||
const bool checked = ui->toggle_hw_renderer->isChecked();
|
||||
@ -60,17 +68,40 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
// TODO(B3N30): Hide this for macs with none Intel GPUs, too.
|
||||
ui->toggle_separable_shader->setVisible(false);
|
||||
#endif
|
||||
|
||||
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureGraphics::SetPhysicalDeviceComboVisibility);
|
||||
|
||||
SetConfiguration();
|
||||
}
|
||||
|
||||
ConfigureGraphics::~ConfigureGraphics() = default;
|
||||
|
||||
void ConfigureGraphics::SetConfiguration() {
|
||||
if (!Settings::IsConfiguringGlobal()) {
|
||||
ConfigurationShared::SetHighlight(ui->physical_device_group,
|
||||
!Settings::values.physical_device.UsingGlobal());
|
||||
ConfigurationShared::SetPerGameSetting(ui->physical_device_combo,
|
||||
&Settings::values.physical_device);
|
||||
ConfigurationShared::SetHighlight(ui->graphics_api_group,
|
||||
!Settings::values.graphics_api.UsingGlobal());
|
||||
ConfigurationShared::SetPerGameSetting(ui->graphics_api_combo,
|
||||
&Settings::values.graphics_api);
|
||||
} else {
|
||||
ui->physical_device_combo->setCurrentIndex(
|
||||
static_cast<int>(Settings::values.physical_device.GetValue()));
|
||||
ui->graphics_api_combo->setCurrentIndex(
|
||||
static_cast<int>(Settings::values.graphics_api.GetValue()));
|
||||
}
|
||||
|
||||
ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer.GetValue());
|
||||
ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader.GetValue());
|
||||
ui->toggle_separable_shader->setChecked(Settings::values.separable_shader.GetValue());
|
||||
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->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue());
|
||||
ui->toggle_async_shaders->setChecked(Settings::values.async_shader_compilation.GetValue());
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue());
|
||||
@ -90,6 +121,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_shader_compilation,
|
||||
ui->toggle_async_shaders, async_shader_compilation);
|
||||
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();
|
||||
@ -110,11 +149,23 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||
ui->toggle_disk_shader_cache->setEnabled(
|
||||
Settings::values.use_disk_shader_cache.UsingGlobal());
|
||||
ui->toggle_vsync_new->setEnabled(Settings::values.use_vsync_new.UsingGlobal());
|
||||
ui->toggle_async_shaders->setEnabled(
|
||||
Settings::values.async_shader_compilation.UsingGlobal());
|
||||
ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal());
|
||||
ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal());
|
||||
return;
|
||||
}
|
||||
|
||||
ui->toggle_shader_jit->setVisible(false);
|
||||
|
||||
ConfigurationShared::SetColoredComboBox(
|
||||
ui->graphics_api_combo, ui->graphics_api_group,
|
||||
static_cast<u32>(Settings::values.graphics_api.GetValue(true)));
|
||||
|
||||
ConfigurationShared::SetColoredComboBox(
|
||||
ui->physical_device_combo, ui->physical_device_group,
|
||||
static_cast<u32>(Settings::values.physical_device.GetValue(true)));
|
||||
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_hw_renderer,
|
||||
Settings::values.use_hw_renderer, use_hw_renderer);
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_hw_shader, Settings::values.use_hw_shader,
|
||||
@ -128,4 +179,48 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||
use_disk_shader_cache);
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_vsync_new, Settings::values.use_vsync_new,
|
||||
use_vsync_new);
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_async_shaders,
|
||||
Settings::values.async_shader_compilation,
|
||||
async_shader_compilation);
|
||||
ConfigurationShared::SetColoredTristate(ui->spirv_shader_gen, Settings::values.spirv_shader_gen,
|
||||
spirv_shader_gen);
|
||||
}
|
||||
|
||||
void ConfigureGraphics::DiscoverPhysicalDevices() {
|
||||
if (physical_devices_discovered) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vulkan::Instance instance{};
|
||||
const auto physical_devices = instance.GetPhysicalDevices();
|
||||
|
||||
for (const vk::PhysicalDevice& physical_device : physical_devices) {
|
||||
const QString name = QString::fromLocal8Bit(physical_device.getProperties().deviceName);
|
||||
ui->physical_device_combo->addItem(name);
|
||||
}
|
||||
|
||||
physical_devices_discovered = true;
|
||||
}
|
||||
|
||||
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
|
||||
bool is_visible{false};
|
||||
|
||||
// When configuring per-game the physical device combo should be
|
||||
// shown either when the global api is used and that is vulkan or
|
||||
// vulkan is set as the per-game api.
|
||||
if (!Settings::IsConfiguringGlobal()) {
|
||||
const auto global_graphics_api = Settings::values.graphics_api.GetValue(true);
|
||||
const bool using_global = index == 0;
|
||||
if (!using_global) {
|
||||
index -= ConfigurationShared::USE_GLOBAL_OFFSET;
|
||||
}
|
||||
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
|
||||
is_visible = (using_global && global_graphics_api == Settings::GraphicsAPI::Vulkan) ||
|
||||
graphics_api == Settings::GraphicsAPI::Vulkan;
|
||||
} else {
|
||||
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
|
||||
is_visible = graphics_api == Settings::GraphicsAPI::Vulkan;
|
||||
}
|
||||
ui->physical_device_group->setVisible(is_visible);
|
||||
ui->spirv_shader_gen->setVisible(is_visible);
|
||||
}
|
||||
|
@ -30,12 +30,19 @@ 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_shader_compilation;
|
||||
ConfigurationShared::CheckState spirv_shader_gen;
|
||||
std::unique_ptr<Ui::ConfigureGraphics> ui;
|
||||
QColor bg_color;
|
||||
bool physical_devices_discovered = false;
|
||||
};
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>430</height>
|
||||
<width>454</width>
|
||||
<height>579</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@ -20,6 +20,97 @@
|
||||
<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>
|
||||
<widget class="QWidget" name="graphics_api_group" native="true">
|
||||
<layout class="QHBoxLayout" name="graphics_api_group_2">
|
||||
<property name="spacing">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<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>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="physical_device_group" native="true">
|
||||
<layout class="QHBoxLayout" name="physical_device_group_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<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>
|
||||
</widget>
|
||||
</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 +209,13 @@
|
||||
<string>Advanced</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_async_shaders">
|
||||
<property name="text">
|
||||
<string>Async Shader Compilation</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"
|
||||
|
@ -90,4 +90,4 @@ bool CheckAuthorizationForMicrophone() {
|
||||
return authorized_microphone;
|
||||
}
|
||||
|
||||
} // AppleAuthorization
|
||||
} // namespace AppleAuthorization
|
||||
|
@ -18,17 +18,6 @@
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
#include <fmt/format.h>
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h> // for chdir
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#ifdef __unix__
|
||||
#include <QVariant>
|
||||
#include <QtDBus/QDBusInterface>
|
||||
#include <QtDBus/QtDBus>
|
||||
#endif
|
||||
#include "citra_qt/aboutdialog.h"
|
||||
#include "citra_qt/applets/mii_selector.h"
|
||||
#include "citra_qt/applets/swkbd.h"
|
||||
@ -61,7 +50,6 @@
|
||||
#include "citra_qt/movie/movie_play_dialog.h"
|
||||
#include "citra_qt/movie/movie_record_dialog.h"
|
||||
#include "citra_qt/multiplayer/state.h"
|
||||
#include "citra_qt/qt_image_interface.h"
|
||||
#include "citra_qt/uisettings.h"
|
||||
#include "citra_qt/updater/updater.h"
|
||||
#include "citra_qt/util/clickable_label.h"
|
||||
@ -71,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"
|
||||
@ -88,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/cfg/cfg.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
@ -100,9 +84,20 @@
|
||||
#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 __unix__
|
||||
#include <QVariant>
|
||||
#include <QtDBus/QDBusInterface>
|
||||
#include <QtDBus/QtDBus>
|
||||
#endif
|
||||
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
#include "citra_qt/discord_impl.h"
|
||||
#endif
|
||||
@ -356,6 +351,22 @@ void GMainWindow::InitializeWidgets() {
|
||||
statusBar()->addPermanentWidget(label);
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
@ -579,6 +590,8 @@ void GMainWindow::InitializeHotkeys() {
|
||||
});
|
||||
connect_shortcut(QStringLiteral("Toggle Texture Dumping"),
|
||||
[&] { Settings::values.dump_textures = !Settings::values.dump_textures; });
|
||||
connect_shortcut(QStringLiteral("Toggle Custom Textures"),
|
||||
[&] { Settings::values.custom_textures = !Settings::values.custom_textures; });
|
||||
// We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes
|
||||
// the variable hold a garbage value after this function exits
|
||||
static constexpr u16 SPEED_LIMIT_STEP = 5;
|
||||
@ -1026,16 +1039,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()};
|
||||
|
||||
@ -1094,7 +1098,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. "
|
||||
@ -1109,10 +1113,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!"),
|
||||
@ -1170,6 +1170,9 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
|
||||
LOG_INFO(Frontend, "Using per game config file for title id {}", config_file_name);
|
||||
Settings::LogSettings();
|
||||
|
||||
// Update API indicator to reflect the per-game graphics API, if set.
|
||||
UpdateAPIIndicator(false);
|
||||
}
|
||||
|
||||
// Save configurations
|
||||
@ -1255,10 +1258,6 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
// show and hide the render_window to create the context
|
||||
render_window->show();
|
||||
render_window->hide();
|
||||
|
||||
loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
|
||||
loading_screen->show();
|
||||
|
||||
@ -1808,6 +1807,9 @@ void GMainWindow::OnPauseContinueGame() {
|
||||
void GMainWindow::OnStopGame() {
|
||||
ShutdownGame();
|
||||
Settings::RestoreGlobalState(false);
|
||||
|
||||
// If a per-game graphics API was set we should reset to the global option
|
||||
UpdateAPIIndicator(false);
|
||||
}
|
||||
|
||||
void GMainWindow::OnLoadComplete() {
|
||||
@ -2030,6 +2032,7 @@ void GMainWindow::OnConfigure() {
|
||||
setMouseTracking(false);
|
||||
}
|
||||
UpdateSecondaryWindowVisibility();
|
||||
UpdateAPIIndicator(false);
|
||||
UpdateBootHomeMenuState();
|
||||
} else {
|
||||
Settings::values.input_profiles = old_input_profiles;
|
||||
@ -2352,6 +2355,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();
|
||||
}
|
||||
@ -2786,14 +2809,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);
|
||||
|
||||
SetHighDPIAttributes();
|
||||
|
||||
#ifdef __APPLE__
|
||||
@ -2826,9 +2841,6 @@ int main(int argc, char* argv[]) {
|
||||
system.RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window));
|
||||
system.RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));
|
||||
|
||||
// Register Qt image interface
|
||||
system.RegisterImageInterface(std::make_shared<QtImageInterface>());
|
||||
|
||||
main_window.show();
|
||||
|
||||
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <QMainWindow>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include "citra_qt/compatibility_list.h"
|
||||
@ -252,6 +253,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;
|
||||
|
||||
@ -267,6 +269,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;
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QImage>
|
||||
#include <QString>
|
||||
#include "citra_qt/qt_image_interface.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height,
|
||||
const std::string& path) {
|
||||
QImage image(QString::fromStdString(path));
|
||||
|
||||
if (image.isNull()) {
|
||||
LOG_ERROR(Frontend, "Failed to open {} for decoding", path);
|
||||
return false;
|
||||
}
|
||||
width = image.width();
|
||||
height = image.height();
|
||||
|
||||
image = image.convertToFormat(QImage::Format_RGBA8888);
|
||||
|
||||
// Write RGBA8 to vector
|
||||
dst = std::vector<u8>(image.constBits(), image.constBits() + (width * height * 4));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QtImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||
u32 height) {
|
||||
QImage image(src.data(), width, height, QImage::Format_RGBA8888);
|
||||
|
||||
if (!image.save(QString::fromStdString(path), "PNG")) {
|
||||
LOG_ERROR(Frontend, "Failed to save {}", path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/frontend/image_interface.h"
|
||||
|
||||
class QtImageInterface final : public Frontend::ImageInterface {
|
||||
public:
|
||||
bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override;
|
||||
bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width,
|
||||
u32 height) override;
|
||||
};
|
@ -63,11 +63,13 @@ add_library(common STATIC
|
||||
arch.h
|
||||
archives.h
|
||||
assert.h
|
||||
async_handle.h
|
||||
atomic_ops.h
|
||||
detached_tasks.cpp
|
||||
detached_tasks.h
|
||||
bit_field.h
|
||||
bit_set.h
|
||||
bit_util.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
color.h
|
||||
@ -76,9 +78,14 @@ add_library(common STATIC
|
||||
common_precompiled_headers.h
|
||||
common_types.h
|
||||
construct.h
|
||||
dds-ktx.h
|
||||
error.cpp
|
||||
error.h
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
hash.h
|
||||
image_util.cpp
|
||||
image_util.h
|
||||
linear_disk_cache.h
|
||||
literals.h
|
||||
logging/backend.cpp
|
||||
@ -100,12 +107,14 @@ add_library(common STATIC
|
||||
misc.cpp
|
||||
param_package.cpp
|
||||
param_package.h
|
||||
polyfill_thread.h
|
||||
precompiled_headers.h
|
||||
quaternion.h
|
||||
ring_buffer.h
|
||||
scm_rev.cpp
|
||||
scm_rev.h
|
||||
scope_exit.h
|
||||
scratch_buffer.h
|
||||
settings.cpp
|
||||
settings.h
|
||||
serialization/atomic.h
|
||||
@ -124,9 +133,11 @@ add_library(common STATIC
|
||||
thread.cpp
|
||||
thread.h
|
||||
thread_queue_list.h
|
||||
thread_worker.h
|
||||
threadsafe_queue.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
unique_function.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
x64/cpu_detect.cpp
|
||||
@ -140,7 +151,7 @@ add_library(common STATIC
|
||||
create_target_directory_groups(common)
|
||||
|
||||
target_link_libraries(common PUBLIC fmt::fmt microprofile Boost::boost Boost::serialization Boost::iostreams)
|
||||
target_link_libraries(common PRIVATE libzstd_static)
|
||||
target_link_libraries(common PRIVATE libzstd_static spng::spng)
|
||||
set_target_properties(common PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||
|
||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
|
39
src/common/async_handle.h
Normal file
39
src/common/async_handle.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct AsyncHandle {
|
||||
public:
|
||||
AsyncHandle(bool is_done_ = false) : is_done{is_done_} {}
|
||||
|
||||
[[nodiscard]] bool IsDone() noexcept {
|
||||
return is_done.load(std::memory_order::relaxed);
|
||||
}
|
||||
|
||||
void WaitDone() noexcept {
|
||||
std::unique_lock lock{mutex};
|
||||
condvar.wait(lock, [this] { return is_done.load(std::memory_order::relaxed); });
|
||||
}
|
||||
|
||||
void MarkDone(bool done = true) noexcept {
|
||||
std::scoped_lock lock{mutex};
|
||||
is_done = done;
|
||||
condvar.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::condition_variable condvar;
|
||||
std::mutex mutex;
|
||||
std::atomic_bool is_done{false};
|
||||
};
|
||||
|
||||
} // namespace Common
|
66
src/common/bit_util.h
Normal file
66
src/common/bit_util.h
Normal file
@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
/// Gets the size of a specified type T in bits.
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr std::size_t BitSize() {
|
||||
return sizeof(T) * CHAR_BIT;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u32 MostSignificantBit32(const u32 value) {
|
||||
return 31U - static_cast<u32>(std::countl_zero(value));
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u32 MostSignificantBit64(const u64 value) {
|
||||
return 63U - static_cast<u32>(std::countl_zero(value));
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u32 Log2Floor32(const u32 value) {
|
||||
return MostSignificantBit32(value);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u32 Log2Floor64(const u64 value) {
|
||||
return MostSignificantBit64(value);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u32 Log2Ceil32(const u32 value) {
|
||||
const u32 log2_f = Log2Floor32(value);
|
||||
return log2_f + static_cast<u32>((value ^ (1U << log2_f)) != 0U);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u32 Log2Ceil64(const u64 value) {
|
||||
const u64 log2_f = Log2Floor64(value);
|
||||
return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires std::is_unsigned_v<T>
|
||||
[[nodiscard]] constexpr bool IsPow2(T value) {
|
||||
return value != 0 && (value & (value - 1)) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires std::is_integral_v<T>
|
||||
[[nodiscard]] T NextPow2(T value) {
|
||||
return static_cast<T>(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U)));
|
||||
}
|
||||
|
||||
template <size_t bit_index, typename T>
|
||||
requires std::is_integral_v<T>
|
||||
[[nodiscard]] constexpr bool Bit(const T value) {
|
||||
static_assert(bit_index < BitSize<T>(), "bit_index must be smaller than size of T");
|
||||
return ((value >> bit_index) & T(1)) == T(1);
|
||||
}
|
||||
|
||||
} // namespace Common
|
@ -52,6 +52,11 @@ namespace Common::Color {
|
||||
return value >> 2;
|
||||
}
|
||||
|
||||
/// Averages the RGB components of a color
|
||||
[[nodiscard]] constexpr u8 AverageRgbComponents(const Common::Vec4<u8>& color) {
|
||||
return (static_cast<u32>(color.r()) + color.g() + color.b()) / 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in RGBA8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
@ -115,6 +120,44 @@ namespace Common::Color {
|
||||
Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in IA8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
[[nodiscard]] inline Common::Vec4<u8> DecodeIA8(const u8* bytes) {
|
||||
return {bytes[1], bytes[1], bytes[1], bytes[0]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in I8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
[[nodiscard]] inline Common::Vec4<u8> DecodeI8(const u8* bytes) {
|
||||
return {bytes[0], bytes[0], bytes[0], 255};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in A8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
[[nodiscard]] inline Common::Vec4<u8> DecodeA8(const u8* bytes) {
|
||||
return {0, 0, 0, bytes[0]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in IA4 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
[[nodiscard]] inline Common::Vec4<u8> DecodeIA4(const u8* bytes) {
|
||||
u8 i = Common::Color::Convert4To8((bytes[0] & 0xF0) >> 4);
|
||||
u8 a = Common::Color::Convert4To8(bytes[0] & 0x0F);
|
||||
return {i, i, i, a};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a depth value stored in D16 format
|
||||
* @param bytes Pointer to encoded source value
|
||||
@ -176,6 +219,7 @@ inline void EncodeRG8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[1] = color.r();
|
||||
bytes[0] = color.g();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a color as RGB565 format
|
||||
* @param color Source color to encode
|
||||
@ -212,6 +256,43 @@ inline void EncodeRGBA4(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
std::memcpy(bytes, &data, sizeof(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a color as IA8 format
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeIA8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[1] = AverageRgbComponents(color);
|
||||
bytes[0] = color.a();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a color as I8 format
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeI8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[0] = AverageRgbComponents(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a color as A8 format
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeA8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[0] = color.a();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a color as IA4 format
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeIA4(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[0] = (Convert8To4(AverageRgbComponents(color)) << 4) | Convert8To4(color.a());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a 16 bit depth value as D16 format
|
||||
* @param value 16 bit source depth value to encode
|
||||
|
@ -55,6 +55,60 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
#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.
|
||||
|
1465
src/common/dds-ktx.h
Normal file
1465
src/common/dds-ktx.h
Normal file
File diff suppressed because it is too large
Load Diff
57
src/common/error.cpp
Normal file
57
src/common/error.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstddef>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
std::string NativeErrorToString(int e) {
|
||||
#ifdef _WIN32
|
||||
LPSTR err_str;
|
||||
|
||||
DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPSTR>(&err_str), 1, nullptr);
|
||||
if (!res) {
|
||||
return "(FormatMessageA failed to format error)";
|
||||
}
|
||||
std::string ret(err_str);
|
||||
LocalFree(err_str);
|
||||
return ret;
|
||||
#else
|
||||
char err_str[255];
|
||||
#if (defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600))) || \
|
||||
defined(ANDROID)
|
||||
// Thread safe (GNU-specific)
|
||||
const char* str = strerror_r(e, err_str, sizeof(err_str));
|
||||
return std::string(str);
|
||||
#else
|
||||
// Thread safe (XSI-compliant)
|
||||
int second_err = strerror_r(e, err_str, sizeof(err_str));
|
||||
if (second_err != 0) {
|
||||
return "(strerror_r failed to format error)";
|
||||
}
|
||||
return std::string(err_str);
|
||||
#endif // GLIBC etc.
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
std::string GetLastErrorMsg() {
|
||||
#ifdef _WIN32
|
||||
return NativeErrorToString(GetLastError());
|
||||
#else
|
||||
return NativeErrorToString(errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
21
src/common/error.h
Normal file
21
src/common/error.h
Normal file
@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
// Generic function to get last error message.
|
||||
// Call directly after the command or use the error num.
|
||||
// This function might change the error code.
|
||||
// Defined in error.cpp.
|
||||
[[nodiscard]] std::string GetLastErrorMsg();
|
||||
|
||||
// Like GetLastErrorMsg(), but passing an explicit error code.
|
||||
// Defined in error.cpp.
|
||||
[[nodiscard]] std::string NativeErrorToString(int e);
|
||||
|
||||
} // namespace Common
|
@ -348,6 +348,9 @@ public:
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return IsGood();
|
||||
}
|
||||
[[nodiscard]] std::FILE* Handle() {
|
||||
return m_file;
|
||||
}
|
||||
|
||||
bool Seek(s64 off, int origin);
|
||||
[[nodiscard]] u64 Tell() const;
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "common/cityhash.h"
|
||||
@ -37,10 +38,17 @@ static inline u64 ComputeStructHash64(const T& data) noexcept {
|
||||
* Combines the seed parameter with the provided hash, producing a new unique hash
|
||||
* Implementation from: http://boost.sourceforge.net/doc/html/boost/hash_combine.html
|
||||
*/
|
||||
inline u64 HashCombine(std::size_t& seed, const u64 hash) {
|
||||
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 {
|
||||
|
165
src/common/image_util.cpp
Normal file
165
src/common/image_util.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <spng.h>
|
||||
#define DDSKTX_IMPLEMENT
|
||||
#include "common/file_util.h"
|
||||
#include "common/image_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace {
|
||||
|
||||
void spng_free(spng_ctx* ctx) {
|
||||
if (ctx) {
|
||||
spng_ctx_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
auto make_spng_ctx(int flags) {
|
||||
return std::unique_ptr<spng_ctx, decltype(&spng_free)>(spng_ctx_new(flags), spng_free);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
bool ParsePNG(std::span<const u8> png_data, size_t& decoded_size, u32& width, u32& height) {
|
||||
auto ctx = make_spng_ctx(0);
|
||||
if (!ctx) [[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spng_set_png_buffer(ctx.get(), png_data.data(), png_data.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
spng_ihdr ihdr{};
|
||||
if (spng_get_ihdr(ctx.get(), &ihdr)) {
|
||||
return false;
|
||||
}
|
||||
width = ihdr.width;
|
||||
height = ihdr.height;
|
||||
|
||||
const int format = SPNG_FMT_RGBA8;
|
||||
if (spng_decoded_image_size(ctx.get(), format, &decoded_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodePNG(std::span<const u8> png_data, std::span<u8> out_data) {
|
||||
auto ctx = make_spng_ctx(0);
|
||||
if (!ctx) [[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spng_set_png_buffer(ctx.get(), png_data.data(), png_data.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int format = SPNG_FMT_RGBA8;
|
||||
size_t decoded_len = 0;
|
||||
if (spng_decoded_image_size(ctx.get(), format, &decoded_len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spng_decode_image(ctx.get(), out_data.data(), decoded_len, format, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseDDSKTX(std::span<const u8> dds_data, size_t& decoded_size, u32& width, u32& height,
|
||||
ddsktx_format& format) {
|
||||
ddsktx_texture_info tc{};
|
||||
const int size = static_cast<int>(dds_data.size());
|
||||
if (!ddsktx_parse(&tc, dds_data.data(), size, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
width = tc.width;
|
||||
height = tc.height;
|
||||
format = tc.format;
|
||||
|
||||
ddsktx_sub_data sub_data{};
|
||||
ddsktx_get_sub(&tc, &sub_data, dds_data.data(), size, 0, 0, 0);
|
||||
decoded_size = sub_data.size_bytes;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadDDSKTX(std::span<const u8> dds_data, std::span<u8> out_data) {
|
||||
ddsktx_texture_info tc{};
|
||||
const int size = static_cast<int>(dds_data.size());
|
||||
if (!ddsktx_parse(&tc, dds_data.data(), size, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ddsktx_sub_data sub_data{};
|
||||
ddsktx_get_sub(&tc, &sub_data, dds_data.data(), size, 0, 0, 0);
|
||||
std::memcpy(out_data.data(), sub_data.buff, sub_data.size_bytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EncodePNG(const std::string& out_path, std::span<u8> in_data, u32 width, u32 height,
|
||||
s32 level) {
|
||||
auto ctx = make_spng_ctx(SPNG_CTX_ENCODER);
|
||||
if (!ctx) [[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spng_set_option(ctx.get(), SPNG_IMG_COMPRESSION_LEVEL, level)) {
|
||||
return false;
|
||||
}
|
||||
if (spng_set_option(ctx.get(), SPNG_ENCODE_TO_BUFFER, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
spng_ihdr ihdr{};
|
||||
ihdr.width = width;
|
||||
ihdr.height = height;
|
||||
ihdr.color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
|
||||
ihdr.bit_depth = 8;
|
||||
if (spng_set_ihdr(ctx.get(), &ihdr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spng_encode_image(ctx.get(), in_data.data(), in_data.size(), SPNG_FMT_PNG,
|
||||
SPNG_ENCODE_FINALIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret{};
|
||||
size_t png_size{};
|
||||
u8* png_buf = reinterpret_cast<u8*>(spng_get_png_buffer(ctx.get(), &png_size, &ret));
|
||||
|
||||
if (!png_buf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto file = FileUtil::IOFile(out_path, "wb");
|
||||
file.WriteBytes(png_buf, png_size);
|
||||
|
||||
size_t image_len = 0;
|
||||
spng_decoded_image_size(ctx.get(), SPNG_FMT_PNG, &image_len);
|
||||
LOG_ERROR(Common, "{} byte {} by {} image saved to {} at level {}", image_len, width, height,
|
||||
out_path, level);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlipTexture(std::span<u8> in_data, u32 width, u32 height, u32 stride) {
|
||||
for (u32 line = 0; line < height / 2; line++) {
|
||||
const u32 offset_1 = line * stride;
|
||||
const u32 offset_2 = (height - line - 1) * stride;
|
||||
// Swap lines
|
||||
std::swap_ranges(in_data.begin() + offset_1, in_data.begin() + offset_1 + stride,
|
||||
in_data.begin() + offset_2);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common
|
26
src/common/image_util.h
Normal file
26
src/common/image_util.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/dds-ktx.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
bool ParsePNG(std::span<const u8> png_data, size_t& decoded_size, u32& width, u32& height);
|
||||
|
||||
bool DecodePNG(std::span<const u8> png_data, std::span<u8> out_data);
|
||||
|
||||
bool ParseDDSKTX(std::span<const u8> dds_data, size_t& decoded_size, u32& width, u32& height,
|
||||
ddsktx_format& format);
|
||||
|
||||
bool LoadDDSKTX(std::span<const u8> dds_data, std::span<u8> out_data);
|
||||
|
||||
bool EncodePNG(const std::string& out_path, std::span<u8> in_data, u32 width, u32 height,
|
||||
s32 level = 6);
|
||||
|
||||
void FlipTexture(std::span<u8> in_data, u32 width, u32 height, u32 stride);
|
||||
|
||||
} // namespace Common
|
@ -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,41 @@ 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 void operator*=(const T value) {
|
||||
left *= value;
|
||||
top *= value;
|
||||
right *= value;
|
||||
bottom *= value;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(const Rectangle<T>& rhs) const {
|
||||
return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) &&
|
||||
(bottom == rhs.bottom);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator!=(const Rectangle<T>& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Rectangle<T> operator*(const T value) const {
|
||||
return Rectangle{left * value, top * value, right * value, bottom * value};
|
||||
}
|
||||
[[nodiscard]] constexpr Rectangle<T> 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{cptr, size > csize ? csize : size};
|
||||
}
|
||||
|
||||
auto GetReadBytes(std::size_t size) const {
|
||||
return std::span{cptr, size > csize ? csize : size};
|
||||
}
|
||||
|
||||
std::size_t GetSize() const {
|
||||
return csize;
|
||||
}
|
||||
|
337
src/common/polyfill_thread.h
Normal file
337
src/common/polyfill_thread.h
Normal file
@ -0,0 +1,337 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
//
|
||||
// TODO: remove this file when jthread is supported by all compilation targets
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <version>
|
||||
|
||||
#ifdef __cpp_lib_jthread
|
||||
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename Condvar, typename Lock, typename Pred>
|
||||
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
|
||||
cv.wait(lock, token, std::move(pred));
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#else
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace std {
|
||||
namespace polyfill {
|
||||
|
||||
using stop_state_callback = size_t;
|
||||
|
||||
class stop_state {
|
||||
public:
|
||||
stop_state() = default;
|
||||
~stop_state() = default;
|
||||
|
||||
bool request_stop() {
|
||||
unique_lock lk{m_lock};
|
||||
|
||||
if (m_stop_requested) {
|
||||
// Already set, nothing to do.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark stop requested.
|
||||
m_stop_requested = true;
|
||||
|
||||
while (!m_callbacks.empty()) {
|
||||
// Get an iterator to the first element.
|
||||
const auto it = m_callbacks.begin();
|
||||
|
||||
// Move the callback function out of the map.
|
||||
function<void()> f;
|
||||
swap(it->second, f);
|
||||
|
||||
// Erase the now-empty map element.
|
||||
m_callbacks.erase(it);
|
||||
|
||||
// Run the callback.
|
||||
if (f) {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool stop_requested() const {
|
||||
unique_lock lk{m_lock};
|
||||
return m_stop_requested;
|
||||
}
|
||||
|
||||
stop_state_callback insert_callback(function<void()> f) {
|
||||
unique_lock lk{m_lock};
|
||||
|
||||
if (m_stop_requested) {
|
||||
// Stop already requested. Don't insert anything,
|
||||
// just run the callback synchronously.
|
||||
if (f) {
|
||||
f();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Insert the callback.
|
||||
stop_state_callback ret = ++m_next_callback;
|
||||
m_callbacks.emplace(ret, move(f));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void remove_callback(stop_state_callback cb) {
|
||||
unique_lock lk{m_lock};
|
||||
m_callbacks.erase(cb);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable recursive_mutex m_lock;
|
||||
map<stop_state_callback, function<void()>> m_callbacks;
|
||||
stop_state_callback m_next_callback{0};
|
||||
bool m_stop_requested{false};
|
||||
};
|
||||
|
||||
} // namespace polyfill
|
||||
|
||||
#ifndef __cpp_lib_concepts
|
||||
template <class T, class... Args>
|
||||
concept constructible_from = is_nothrow_destructible_v<T> && is_constructible_v<T, Args...>;
|
||||
#endif
|
||||
|
||||
class stop_token;
|
||||
class stop_source;
|
||||
struct nostopstate_t {
|
||||
explicit nostopstate_t() = default;
|
||||
};
|
||||
inline constexpr nostopstate_t nostopstate{};
|
||||
|
||||
template <class Callback>
|
||||
class stop_callback;
|
||||
|
||||
class stop_token {
|
||||
public:
|
||||
stop_token() noexcept = default;
|
||||
|
||||
stop_token(const stop_token&) noexcept = default;
|
||||
stop_token(stop_token&&) noexcept = default;
|
||||
stop_token& operator=(const stop_token&) noexcept = default;
|
||||
stop_token& operator=(stop_token&&) noexcept = default;
|
||||
~stop_token() = default;
|
||||
|
||||
void swap(stop_token& other) noexcept {
|
||||
m_stop_state.swap(other.m_stop_state);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool stop_requested() const noexcept {
|
||||
return m_stop_state && m_stop_state->stop_requested();
|
||||
}
|
||||
[[nodiscard]] bool stop_possible() const noexcept {
|
||||
return m_stop_state != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class stop_source;
|
||||
template <typename Callback>
|
||||
friend class stop_callback;
|
||||
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
|
||||
|
||||
private:
|
||||
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||
};
|
||||
|
||||
class stop_source {
|
||||
public:
|
||||
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
|
||||
explicit stop_source(nostopstate_t) noexcept {}
|
||||
|
||||
stop_source(const stop_source&) noexcept = default;
|
||||
stop_source(stop_source&&) noexcept = default;
|
||||
stop_source& operator=(const stop_source&) noexcept = default;
|
||||
stop_source& operator=(stop_source&&) noexcept = default;
|
||||
~stop_source() = default;
|
||||
void swap(stop_source& other) noexcept {
|
||||
m_stop_state.swap(other.m_stop_state);
|
||||
}
|
||||
|
||||
[[nodiscard]] stop_token get_token() const noexcept {
|
||||
return stop_token(m_stop_state);
|
||||
}
|
||||
[[nodiscard]] bool stop_possible() const noexcept {
|
||||
return m_stop_state != nullptr;
|
||||
}
|
||||
[[nodiscard]] bool stop_requested() const noexcept {
|
||||
return m_stop_state && m_stop_state->stop_requested();
|
||||
}
|
||||
bool request_stop() noexcept {
|
||||
return m_stop_state && m_stop_state->request_stop();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class jthread;
|
||||
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
|
||||
: m_stop_state(move(stop_state)) {}
|
||||
|
||||
private:
|
||||
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
class stop_callback {
|
||||
static_assert(is_nothrow_destructible_v<Callback>);
|
||||
static_assert(is_invocable_v<Callback>);
|
||||
|
||||
public:
|
||||
using callback_type = Callback;
|
||||
|
||||
template <typename C>
|
||||
requires constructible_from<Callback, C>
|
||||
explicit stop_callback(const stop_token& st,
|
||||
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
|
||||
: m_stop_state(st.m_stop_state) {
|
||||
if (m_stop_state) {
|
||||
m_callback = m_stop_state->insert_callback(move(cb));
|
||||
}
|
||||
}
|
||||
template <typename C>
|
||||
requires constructible_from<Callback, C>
|
||||
explicit stop_callback(stop_token&& st,
|
||||
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
|
||||
: m_stop_state(move(st.m_stop_state)) {
|
||||
if (m_stop_state) {
|
||||
m_callback = m_stop_state->insert_callback(move(cb));
|
||||
}
|
||||
}
|
||||
~stop_callback() {
|
||||
if (m_stop_state && m_callback) {
|
||||
m_stop_state->remove_callback(m_callback);
|
||||
}
|
||||
}
|
||||
|
||||
stop_callback(const stop_callback&) = delete;
|
||||
stop_callback(stop_callback&&) = delete;
|
||||
stop_callback& operator=(const stop_callback&) = delete;
|
||||
stop_callback& operator=(stop_callback&&) = delete;
|
||||
|
||||
private:
|
||||
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||
polyfill::stop_state_callback m_callback;
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
|
||||
|
||||
class jthread {
|
||||
public:
|
||||
using id = thread::id;
|
||||
using native_handle_type = thread::native_handle_type;
|
||||
|
||||
jthread() noexcept = default;
|
||||
|
||||
template <typename F, typename... Args,
|
||||
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
|
||||
explicit jthread(F&& f, Args&&... args)
|
||||
: m_stop_state(make_shared<polyfill::stop_state>()),
|
||||
m_thread(make_thread(move(f), move(args)...)) {}
|
||||
|
||||
~jthread() {
|
||||
if (joinable()) {
|
||||
request_stop();
|
||||
join();
|
||||
}
|
||||
}
|
||||
|
||||
jthread(const jthread&) = delete;
|
||||
jthread(jthread&&) noexcept = default;
|
||||
jthread& operator=(const jthread&) = delete;
|
||||
|
||||
jthread& operator=(jthread&& other) noexcept {
|
||||
m_thread.swap(other.m_thread);
|
||||
m_stop_state.swap(other.m_stop_state);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(jthread& other) noexcept {
|
||||
m_thread.swap(other.m_thread);
|
||||
m_stop_state.swap(other.m_stop_state);
|
||||
}
|
||||
[[nodiscard]] bool joinable() const noexcept {
|
||||
return m_thread.joinable();
|
||||
}
|
||||
void join() {
|
||||
m_thread.join();
|
||||
}
|
||||
void detach() {
|
||||
m_thread.detach();
|
||||
m_stop_state.reset();
|
||||
}
|
||||
|
||||
[[nodiscard]] id get_id() const noexcept {
|
||||
return m_thread.get_id();
|
||||
}
|
||||
[[nodiscard]] native_handle_type native_handle() {
|
||||
return m_thread.native_handle();
|
||||
}
|
||||
[[nodiscard]] stop_source get_stop_source() noexcept {
|
||||
return stop_source(m_stop_state);
|
||||
}
|
||||
[[nodiscard]] stop_token get_stop_token() const noexcept {
|
||||
return stop_source(m_stop_state).get_token();
|
||||
}
|
||||
bool request_stop() noexcept {
|
||||
return get_stop_source().request_stop();
|
||||
}
|
||||
[[nodiscard]] static unsigned int hardware_concurrency() noexcept {
|
||||
return thread::hardware_concurrency();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename F, typename... Args>
|
||||
thread make_thread(F&& f, Args&&... args) {
|
||||
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
|
||||
return thread(move(f), get_stop_token(), move(args)...);
|
||||
} else {
|
||||
return thread(move(f), move(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||
thread m_thread;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename Condvar, typename Lock, typename Pred>
|
||||
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
|
||||
if (token.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stop_callback callback(token, [&] { cv.notify_all(); });
|
||||
cv.wait(lock, [&] { return pred() || token.stop_requested(); });
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif
|
47
src/common/scratch_buffer.h
Normal file
47
src/common/scratch_buffer.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* A ScratchBuffer is a simple heap allocated array without member initialization.
|
||||
* Main usage is for temporary buffers passed to threads for example
|
||||
*/
|
||||
template <typename T>
|
||||
class ScratchBuffer {
|
||||
static_assert(std::is_trivial_v<T>, "Must use a POD type");
|
||||
|
||||
public:
|
||||
ScratchBuffer(std::size_t size_) : size{size_} {
|
||||
buffer = std::unique_ptr<T>(new typename std::remove_extent<T>::type[size]);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t Size() const noexcept {
|
||||
return size;
|
||||
}
|
||||
|
||||
[[nodiscard]] T* Data() const noexcept {
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<const T> Span(u32 index = 0) const noexcept {
|
||||
return std::span<const T>{buffer.get() + index, size - index};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<T> Span(u32 index = 0) noexcept {
|
||||
return std::span<T>{buffer.get() + index, size - index};
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<T> buffer;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
} // namespace Common
|
@ -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 {
|
||||
|
||||
std::string_view GetAPIName(GraphicsAPI api) {
|
||||
switch (api) {
|
||||
case GraphicsAPI::OpenGL:
|
||||
return "OpenGL";
|
||||
case GraphicsAPI::OpenGLES:
|
||||
return "OpenGLES";
|
||||
case GraphicsAPI::Vulkan:
|
||||
return "Vulkan";
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view GetAudioEmulationName(AudioEmulation emulation) {
|
||||
switch (emulation) {
|
||||
case AudioEmulation::HLE:
|
||||
@ -41,9 +51,8 @@ void Apply() {
|
||||
VideoCore::g_hw_renderer_enabled = values.use_hw_renderer.GetValue();
|
||||
VideoCore::g_shader_jit_enabled = values.use_shader_jit.GetValue();
|
||||
VideoCore::g_hw_shader_enabled = values.use_hw_shader.GetValue();
|
||||
VideoCore::g_separable_shader_enabled = values.separable_shader.GetValue();
|
||||
VideoCore::g_hw_shader_accurate_mul = values.shaders_accurate_mul.GetValue();
|
||||
VideoCore::g_use_disk_shader_cache = values.use_disk_shader_cache.GetValue();
|
||||
VideoCore::g_texture_filter_update_requested = true;
|
||||
|
||||
#ifndef ANDROID
|
||||
if (VideoCore::g_renderer) {
|
||||
@ -51,10 +60,12 @@ void Apply() {
|
||||
}
|
||||
#endif
|
||||
|
||||
VideoCore::g_renderer_bg_color_update_requested = true;
|
||||
VideoCore::g_renderer_sampler_update_requested = true;
|
||||
VideoCore::g_renderer_shader_update_requested = true;
|
||||
VideoCore::g_texture_filter_update_requested = true;
|
||||
if (VideoCore::g_renderer) {
|
||||
auto& settings = VideoCore::g_renderer->Settings();
|
||||
settings.bg_color_update_requested = true;
|
||||
settings.sampler_update_requested = true;
|
||||
settings.shader_update_requested = true;
|
||||
}
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
if (system.IsPoweredOn()) {
|
||||
@ -100,7 +111,10 @@ 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_AsyncShaders", values.async_shader_compilation.GetValue());
|
||||
log_setting("Renderer_SpirvShaderGen", values.spirv_shader_gen.GetValue());
|
||||
log_setting("Renderer_Debug", values.renderer_debug.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());
|
||||
@ -188,7 +202,10 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
// Renderer
|
||||
values.use_hw_renderer.SetGlobal(true);
|
||||
values.use_hw_shader.SetGlobal(true);
|
||||
values.graphics_api.SetGlobal(true);
|
||||
values.physical_device.SetGlobal(true);
|
||||
values.separable_shader.SetGlobal(true);
|
||||
values.async_shader_compilation.SetGlobal(true);
|
||||
values.use_disk_shader_cache.SetGlobal(true);
|
||||
values.shaders_accurate_mul.SetGlobal(true);
|
||||
values.use_vsync_new.SetGlobal(true);
|
||||
|
@ -15,6 +15,12 @@
|
||||
|
||||
namespace Settings {
|
||||
|
||||
enum class GraphicsAPI {
|
||||
OpenGL = 0,
|
||||
OpenGLES = 1,
|
||||
Vulkan = 2,
|
||||
};
|
||||
|
||||
enum class InitClock : u32 {
|
||||
SystemTime = 0,
|
||||
FixedTime = 1,
|
||||
@ -415,7 +421,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<u32> physical_device{0, "physical_device"};
|
||||
Setting<bool> renderer_debug{false, "renderer_debug"};
|
||||
Setting<bool> dump_command_buffers{false, "dump_command_buffers"};
|
||||
SwitchableSetting<bool> spirv_shader_gen{true, "spirv_shader_gen"};
|
||||
SwitchableSetting<bool> async_shader_compilation{false, "async_shader_compilation"};
|
||||
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"};
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common::Telemetry {
|
||||
@ -52,8 +53,8 @@ public:
|
||||
template <typename T>
|
||||
class Field : public FieldInterface {
|
||||
public:
|
||||
Field(FieldType type, std::string name, T value)
|
||||
: name(std::move(name)), type(type), value(std::move(value)) {}
|
||||
Field(FieldType type, std::string_view name, T value)
|
||||
: name(name), type(type), value(std::move(value)) {}
|
||||
|
||||
Field(const Field&) = default;
|
||||
Field& operator=(const Field&) = default;
|
||||
@ -115,7 +116,7 @@ public:
|
||||
* @param value Value for the field to add.
|
||||
*/
|
||||
template <typename T>
|
||||
void AddField(FieldType type, const char* name, T value) {
|
||||
void AddField(FieldType type, std::string_view name, T value) {
|
||||
return AddField(std::make_unique<Field<T>>(type, name, std::move(value)));
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include <string>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#ifdef __APPLE__
|
||||
@ -20,7 +22,6 @@
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <string>
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#define cpu_set_t cpuset_t
|
||||
@ -28,6 +29,56 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
auto handle = GetCurrentThread();
|
||||
int windows_priority = 0;
|
||||
switch (new_priority) {
|
||||
case ThreadPriority::Low:
|
||||
windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::Normal:
|
||||
windows_priority = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::High:
|
||||
windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::VeryHigh:
|
||||
windows_priority = THREAD_PRIORITY_HIGHEST;
|
||||
break;
|
||||
case ThreadPriority::Critical:
|
||||
windows_priority = THREAD_PRIORITY_TIME_CRITICAL;
|
||||
break;
|
||||
default:
|
||||
windows_priority = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
}
|
||||
SetThreadPriority(handle, windows_priority);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
pthread_t this_thread = pthread_self();
|
||||
|
||||
const auto scheduling_type = SCHED_OTHER;
|
||||
s32 max_prio = sched_get_priority_max(scheduling_type);
|
||||
s32 min_prio = sched_get_priority_min(scheduling_type);
|
||||
u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U);
|
||||
|
||||
struct sched_param params;
|
||||
if (max_prio > min_prio) {
|
||||
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
|
||||
} else {
|
||||
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
|
||||
}
|
||||
|
||||
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
@ -47,7 +98,7 @@ void SetCurrentThreadName(const char* name) {
|
||||
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = static_cast<DWORD>(-1);
|
||||
info.dwThreadID = std::numeric_limits<DWORD>::max();
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try {
|
||||
@ -81,6 +132,12 @@ void SetCurrentThreadName(const char* name) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
// Do Nothing on MingW
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -10,13 +10,15 @@
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class Event {
|
||||
public:
|
||||
void Set() {
|
||||
std::lock_guard lk{mutex};
|
||||
std::scoped_lock lk{mutex};
|
||||
if (!is_set) {
|
||||
is_set = true;
|
||||
condvar.notify_one();
|
||||
@ -29,8 +31,7 @@ public:
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
bool WaitFor(const std::chrono::duration<Duration>& time) {
|
||||
bool WaitFor(const std::chrono::nanoseconds& time) {
|
||||
std::unique_lock lk{mutex};
|
||||
if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
|
||||
return false;
|
||||
@ -54,6 +55,10 @@ public:
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsSet() {
|
||||
return is_set;
|
||||
}
|
||||
|
||||
private:
|
||||
std::condition_variable condvar;
|
||||
std::mutex mutex;
|
||||
@ -65,7 +70,7 @@ public:
|
||||
explicit Barrier(std::size_t count_) : count(count_) {}
|
||||
|
||||
/// Blocks until all "count" threads have called Sync()
|
||||
void Sync() {
|
||||
bool Sync(std::stop_token token = {}) {
|
||||
std::unique_lock lk{mutex};
|
||||
const std::size_t current_generation = generation;
|
||||
|
||||
@ -73,25 +78,37 @@ public:
|
||||
generation++;
|
||||
waiting = 0;
|
||||
condvar.notify_all();
|
||||
return true;
|
||||
} else {
|
||||
condvar.wait(lk,
|
||||
[this, current_generation] { return current_generation != generation; });
|
||||
CondvarWait(condvar, lk, token,
|
||||
[this, current_generation] { return current_generation != generation; });
|
||||
return !token.stop_requested();
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t Generation() const {
|
||||
std::unique_lock lk(mutex);
|
||||
std::size_t Generation() {
|
||||
std::unique_lock lk{mutex};
|
||||
return generation;
|
||||
}
|
||||
|
||||
private:
|
||||
std::condition_variable condvar;
|
||||
mutable std::mutex mutex;
|
||||
std::condition_variable_any condvar;
|
||||
std::mutex mutex;
|
||||
std::size_t count;
|
||||
std::size_t waiting = 0;
|
||||
std::size_t generation = 0; // Incremented once each time the barrier is used
|
||||
};
|
||||
|
||||
enum class ThreadPriority : u32 {
|
||||
Low = 0,
|
||||
Normal = 1,
|
||||
High = 2,
|
||||
VeryHigh = 3,
|
||||
Critical = 4,
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
|
||||
void SetCurrentThreadName(const char* name);
|
||||
|
||||
} // namespace Common
|
||||
|
121
src/common/thread_worker.h
Normal file
121
src/common/thread_worker.h
Normal file
@ -0,0 +1,121 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/unique_function.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <class StateType = void>
|
||||
class StatefulThreadWorker {
|
||||
static constexpr bool with_state = !std::is_same_v<StateType, void>;
|
||||
|
||||
struct DummyCallable {
|
||||
int operator()() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
using Task =
|
||||
std::conditional_t<with_state, UniqueFunction<void, StateType*>, UniqueFunction<void>>;
|
||||
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||
|
||||
public:
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string_view name, StateMaker func = {})
|
||||
: workers_queued{num_workers}, thread_name{name} {
|
||||
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName(thread_name.data());
|
||||
{
|
||||
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||
while (!stop_token.stop_requested()) {
|
||||
Task task;
|
||||
{
|
||||
std::unique_lock lock{queue_mutex};
|
||||
if (requests.empty()) {
|
||||
wait_condition.notify_all();
|
||||
}
|
||||
Common::CondvarWait(condition, lock, stop_token,
|
||||
[this] { return !requests.empty(); });
|
||||
if (stop_token.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
task = std::move(requests.front());
|
||||
requests.pop();
|
||||
}
|
||||
if constexpr (with_state) {
|
||||
task(&state);
|
||||
} else {
|
||||
task();
|
||||
}
|
||||
++work_done;
|
||||
}
|
||||
}
|
||||
++workers_stopped;
|
||||
wait_condition.notify_all();
|
||||
};
|
||||
threads.reserve(num_workers);
|
||||
for (size_t i = 0; i < num_workers; ++i) {
|
||||
threads.emplace_back(lambda);
|
||||
}
|
||||
}
|
||||
|
||||
StatefulThreadWorker& operator=(const StatefulThreadWorker&) = delete;
|
||||
StatefulThreadWorker(const StatefulThreadWorker&) = delete;
|
||||
|
||||
StatefulThreadWorker& operator=(StatefulThreadWorker&&) = delete;
|
||||
StatefulThreadWorker(StatefulThreadWorker&&) = delete;
|
||||
|
||||
void QueueWork(Task work) {
|
||||
{
|
||||
std::unique_lock lock{queue_mutex};
|
||||
requests.emplace(std::move(work));
|
||||
++work_scheduled;
|
||||
}
|
||||
condition.notify_one();
|
||||
}
|
||||
|
||||
void WaitForRequests(std::stop_token stop_token = {}) {
|
||||
std::stop_callback callback(stop_token, [this] {
|
||||
for (auto& thread : threads) {
|
||||
thread.request_stop();
|
||||
}
|
||||
});
|
||||
std::unique_lock lock{queue_mutex};
|
||||
wait_condition.wait(lock, [this] {
|
||||
return workers_stopped >= workers_queued || work_done >= work_scheduled;
|
||||
});
|
||||
}
|
||||
|
||||
const std::size_t NumWorkers() const noexcept {
|
||||
return threads.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<Task> requests;
|
||||
std::mutex queue_mutex;
|
||||
std::condition_variable_any condition;
|
||||
std::condition_variable wait_condition;
|
||||
std::atomic<size_t> work_scheduled{};
|
||||
std::atomic<size_t> work_done{};
|
||||
std::atomic<size_t> workers_stopped{};
|
||||
std::atomic<size_t> workers_queued{};
|
||||
std::string_view thread_name;
|
||||
std::vector<std::jthread> threads;
|
||||
};
|
||||
|
||||
using ThreadWorker = StatefulThreadWorker<>;
|
||||
|
||||
} // namespace Common
|
@ -13,8 +13,10 @@
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
|
||||
namespace Common {
|
||||
template <typename T>
|
||||
template <typename T, bool with_stop_token = false>
|
||||
class SPSCQueue {
|
||||
public:
|
||||
SPSCQueue() {
|
||||
@ -40,21 +42,19 @@ public:
|
||||
template <typename Arg>
|
||||
void Push(Arg&& t) {
|
||||
// create the element, add it to the queue
|
||||
write_ptr->current = std::forward<Arg>(t);
|
||||
write_ptr->current = std::move(t);
|
||||
// set the next pointer to a new element ptr
|
||||
// then advance the write pointer
|
||||
ElementPtr* new_ptr = new ElementPtr();
|
||||
write_ptr->next.store(new_ptr, std::memory_order_release);
|
||||
write_ptr = new_ptr;
|
||||
++size;
|
||||
|
||||
const size_t previous_size{size++};
|
||||
|
||||
// Acquire the mutex and then immediately release it as a fence.
|
||||
// cv_mutex must be held or else there will be a missed wakeup if the other thread is in the
|
||||
// line before cv.wait
|
||||
// TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
|
||||
// See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
|
||||
if (previous_size == 0) {
|
||||
std::lock_guard lock{cv_mutex};
|
||||
}
|
||||
std::scoped_lock lock{cv_mutex};
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
@ -83,10 +83,27 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
T PopWait() {
|
||||
void Wait() {
|
||||
if (Empty()) {
|
||||
std::unique_lock lock{cv_mutex};
|
||||
cv.wait(lock, [this]() { return !Empty(); });
|
||||
cv.wait(lock, [this] { return !Empty(); });
|
||||
}
|
||||
}
|
||||
|
||||
T PopWait() {
|
||||
Wait();
|
||||
T t;
|
||||
Pop(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
T PopWait(std::stop_token stop_token) {
|
||||
if (Empty()) {
|
||||
std::unique_lock lock{cv_mutex};
|
||||
Common::CondvarWait(cv, lock, stop_token, [this] { return !Empty(); });
|
||||
}
|
||||
if (stop_token.stop_requested()) {
|
||||
return T{};
|
||||
}
|
||||
T t;
|
||||
Pop(t);
|
||||
@ -105,7 +122,7 @@ private:
|
||||
// and a pointer to the next ElementPtr
|
||||
class ElementPtr {
|
||||
public:
|
||||
ElementPtr() = default;
|
||||
ElementPtr() {}
|
||||
~ElementPtr() {
|
||||
ElementPtr* next_ptr = next.load();
|
||||
|
||||
@ -121,13 +138,13 @@ private:
|
||||
ElementPtr* read_ptr;
|
||||
std::atomic_size_t size{0};
|
||||
std::mutex cv_mutex;
|
||||
std::condition_variable cv;
|
||||
std::conditional_t<with_stop_token, std::condition_variable_any, std::condition_variable> cv;
|
||||
};
|
||||
|
||||
// a simple thread-safe,
|
||||
// single reader, multiple writer queue
|
||||
|
||||
template <typename T>
|
||||
template <typename T, bool with_stop_token = false>
|
||||
class MPSCQueue {
|
||||
public:
|
||||
[[nodiscard]] std::size_t Size() const {
|
||||
@ -144,7 +161,7 @@ public:
|
||||
|
||||
template <typename Arg>
|
||||
void Push(Arg&& t) {
|
||||
std::lock_guard lock{write_lock};
|
||||
std::scoped_lock lock{write_lock};
|
||||
spsc_queue.Push(t);
|
||||
}
|
||||
|
||||
@ -156,17 +173,25 @@ public:
|
||||
return spsc_queue.Pop(t);
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
spsc_queue.Wait();
|
||||
}
|
||||
|
||||
T PopWait() {
|
||||
return spsc_queue.PopWait();
|
||||
}
|
||||
|
||||
T PopWait(std::stop_token stop_token) {
|
||||
return spsc_queue.PopWait(stop_token);
|
||||
}
|
||||
|
||||
// not thread-safe
|
||||
void Clear() {
|
||||
spsc_queue.Clear();
|
||||
}
|
||||
|
||||
private:
|
||||
SPSCQueue<T> spsc_queue;
|
||||
SPSCQueue<T, with_stop_token> spsc_queue;
|
||||
std::mutex write_lock;
|
||||
};
|
||||
} // namespace Common
|
||||
|
61
src/common/unique_function.h
Normal file
61
src/common/unique_function.h
Normal file
@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
/// General purpose function wrapper similar to std::function.
|
||||
/// Unlike std::function, the captured values don't have to be copyable.
|
||||
/// This class can be moved but not copied.
|
||||
template <typename ResultType, typename... Args>
|
||||
class UniqueFunction {
|
||||
class CallableBase {
|
||||
public:
|
||||
virtual ~CallableBase() = default;
|
||||
virtual ResultType operator()(Args&&...) = 0;
|
||||
};
|
||||
|
||||
template <typename Functor>
|
||||
class Callable final : public CallableBase {
|
||||
public:
|
||||
Callable(Functor&& functor_) : functor{std::move(functor_)} {}
|
||||
~Callable() override = default;
|
||||
|
||||
ResultType operator()(Args&&... args) override {
|
||||
return functor(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Functor functor;
|
||||
};
|
||||
|
||||
public:
|
||||
UniqueFunction() = default;
|
||||
|
||||
template <typename Functor>
|
||||
UniqueFunction(Functor&& functor)
|
||||
: callable{std::make_unique<Callable<Functor>>(std::move(functor))} {}
|
||||
|
||||
UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default;
|
||||
UniqueFunction(UniqueFunction&& rhs) noexcept = default;
|
||||
|
||||
UniqueFunction& operator=(const UniqueFunction&) = delete;
|
||||
UniqueFunction(const UniqueFunction&) = delete;
|
||||
|
||||
ResultType operator()(Args&&... args) const {
|
||||
return (*callable)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return static_cast<bool>(callable);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<CallableBase> callable;
|
||||
};
|
||||
|
||||
} // namespace Common
|
@ -36,8 +36,6 @@ add_library(core STATIC
|
||||
core.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
custom_tex_cache.cpp
|
||||
custom_tex_cache.h
|
||||
dumping/backend.cpp
|
||||
dumping/backend.h
|
||||
file_sys/archive_backend.cpp
|
||||
@ -113,8 +111,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
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include "audio_core/lle/lle.h"
|
||||
#include "common/arch.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/texture.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
|
||||
@ -27,7 +26,6 @@
|
||||
#include "core/dumping/ffmpeg_backend.h"
|
||||
#endif
|
||||
#include "common/settings.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/global.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
@ -48,6 +46,7 @@
|
||||
#include "core/movie.h"
|
||||
#include "core/rpc/rpc_server.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/rasterizer_cache/custom_tex_manager.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@ -317,16 +316,9 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
perf_stats = std::make_unique<PerfStats>(title_id);
|
||||
custom_tex_cache = std::make_unique<Core::CustomTexCache>();
|
||||
|
||||
if (Settings::values.custom_textures) {
|
||||
const u64 program_id = Kernel().GetCurrentProcess()->codeset->program_id;
|
||||
FileUtil::CreateFullPath(fmt::format(
|
||||
"{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id));
|
||||
custom_tex_cache->FindCustomTextures(program_id);
|
||||
}
|
||||
if (Settings::values.preload_textures) {
|
||||
custom_tex_cache->PreloadTextures(*GetImageInterface());
|
||||
custom_tex_manager->FindCustomTextures();
|
||||
}
|
||||
|
||||
status = ResultStatus::Success;
|
||||
@ -431,13 +423,13 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
video_dumper = std::make_unique<VideoDumper::NullBackend>();
|
||||
#endif
|
||||
|
||||
VideoCore::ResultStatus result = VideoCore::Init(emu_window, secondary_window, *memory);
|
||||
custom_tex_manager = std::make_unique<VideoCore::CustomTexManager>(*this);
|
||||
|
||||
VideoCore::ResultStatus result = VideoCore::Init(emu_window, secondary_window, *this);
|
||||
if (result != VideoCore::ResultStatus::Success) {
|
||||
switch (result) {
|
||||
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
||||
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
|
||||
case VideoCore::ResultStatus::ErrorBelowGL43:
|
||||
return ResultStatus::ErrorVideoCore_ErrorBelowGL43;
|
||||
default:
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
@ -450,7 +442,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
RendererBase& System::Renderer() {
|
||||
VideoCore::RendererBase& System::Renderer() {
|
||||
return *VideoCore::g_renderer;
|
||||
}
|
||||
|
||||
@ -514,12 +506,12 @@ const VideoDumper::Backend& System::VideoDumper() const {
|
||||
return *video_dumper;
|
||||
}
|
||||
|
||||
Core::CustomTexCache& System::CustomTexCache() {
|
||||
return *custom_tex_cache;
|
||||
VideoCore::CustomTexManager& System::CustomTexManager() {
|
||||
return *custom_tex_manager;
|
||||
}
|
||||
|
||||
const Core::CustomTexCache& System::CustomTexCache() const {
|
||||
return *custom_tex_cache;
|
||||
const VideoCore::CustomTexManager& System::CustomTexManager() const {
|
||||
return *custom_tex_manager;
|
||||
}
|
||||
|
||||
void System::RegisterMiiSelector(std::shared_ptr<Frontend::MiiSelector> mii_selector) {
|
||||
@ -530,10 +522,6 @@ void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard
|
||||
registered_swkbd = std::move(swkbd);
|
||||
}
|
||||
|
||||
void System::RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> image_interface) {
|
||||
registered_image_interface = std::move(image_interface);
|
||||
}
|
||||
|
||||
void System::Shutdown(bool is_deserializing) {
|
||||
// Log last frame performance stats
|
||||
const auto perf_results = GetAndResetPerfStats();
|
||||
|
@ -9,10 +9,8 @@
|
||||
#include <string>
|
||||
#include <boost/serialization/version.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
#include "core/frontend/applets/mii_selector.h"
|
||||
#include "core/frontend/applets/swkbd.h"
|
||||
#include "core/frontend/image_interface.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/perf_stats.h"
|
||||
@ -57,7 +55,10 @@ namespace VideoDumper {
|
||||
class Backend;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class CustomTexManager;
|
||||
class RendererBase;
|
||||
} // namespace VideoCore
|
||||
|
||||
namespace Core {
|
||||
|
||||
@ -90,8 +91,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
|
||||
@ -210,7 +209,7 @@ public:
|
||||
return *dsp_core;
|
||||
}
|
||||
|
||||
[[nodiscard]] RendererBase& Renderer();
|
||||
[[nodiscard]] VideoCore::RendererBase& Renderer();
|
||||
|
||||
/**
|
||||
* Gets a reference to the service manager.
|
||||
@ -257,11 +256,11 @@ public:
|
||||
/// Gets a const reference to the cheat engine
|
||||
[[nodiscard]] const Cheats::CheatEngine& CheatEngine() const;
|
||||
|
||||
/// Gets a reference to the custom texture cache system
|
||||
[[nodiscard]] Core::CustomTexCache& CustomTexCache();
|
||||
/// Gets a reference to the custom texture management system
|
||||
[[nodiscard]] VideoCore::CustomTexManager& CustomTexManager();
|
||||
|
||||
/// Gets a const reference to the custom texture cache system
|
||||
[[nodiscard]] const Core::CustomTexCache& CustomTexCache() const;
|
||||
/// Gets a const reference to the custom texture management system
|
||||
[[nodiscard]] const VideoCore::CustomTexManager& CustomTexManager() const;
|
||||
|
||||
/// Gets a reference to the video dumper backend
|
||||
[[nodiscard]] VideoDumper::Backend& VideoDumper();
|
||||
@ -301,14 +300,6 @@ public:
|
||||
return registered_swkbd;
|
||||
}
|
||||
|
||||
/// Image interface
|
||||
|
||||
void RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> image_interface);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Frontend::ImageInterface> GetImageInterface() const {
|
||||
return registered_image_interface;
|
||||
}
|
||||
|
||||
void SaveState(u32 slot) const;
|
||||
|
||||
void LoadState(u32 slot);
|
||||
@ -367,10 +358,7 @@ private:
|
||||
std::unique_ptr<VideoDumper::Backend> video_dumper;
|
||||
|
||||
/// Custom texture cache system
|
||||
std::unique_ptr<Core::CustomTexCache> custom_tex_cache;
|
||||
|
||||
/// Image interface
|
||||
std::shared_ptr<Frontend::ImageInterface> registered_image_interface;
|
||||
std::unique_ptr<VideoCore::CustomTexManager> custom_tex_manager;
|
||||
|
||||
/// RPC Server for scripting support
|
||||
std::unique_ptr<RPC::RPCServer> rpc_server;
|
||||
|
@ -1,109 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/file_util.h"
|
||||
#include "common/texture.h"
|
||||
#include "core.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
|
||||
namespace Core {
|
||||
CustomTexCache::CustomTexCache() = default;
|
||||
|
||||
CustomTexCache::~CustomTexCache() = default;
|
||||
|
||||
bool CustomTexCache::IsTextureDumped(u64 hash) const {
|
||||
return dumped_textures.count(hash);
|
||||
}
|
||||
|
||||
void CustomTexCache::SetTextureDumped(const u64 hash) {
|
||||
dumped_textures.insert(hash);
|
||||
}
|
||||
|
||||
bool CustomTexCache::IsTextureCached(u64 hash) const {
|
||||
return custom_textures.count(hash);
|
||||
}
|
||||
|
||||
const CustomTexInfo& CustomTexCache::LookupTexture(u64 hash) const {
|
||||
return custom_textures.at(hash);
|
||||
}
|
||||
|
||||
void CustomTexCache::CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height) {
|
||||
custom_textures[hash] = {width, height, tex};
|
||||
}
|
||||
|
||||
void CustomTexCache::AddTexturePath(u64 hash, const std::string& path) {
|
||||
if (custom_texture_paths.count(hash))
|
||||
LOG_ERROR(Core, "Textures {} and {} conflict!", custom_texture_paths[hash].path, path);
|
||||
else
|
||||
custom_texture_paths[hash] = {path, hash};
|
||||
}
|
||||
|
||||
void CustomTexCache::FindCustomTextures(u64 program_id) {
|
||||
// Custom textures are currently stored as
|
||||
// [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
|
||||
|
||||
const std::string load_path = fmt::format(
|
||||
"{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id);
|
||||
|
||||
if (FileUtil::Exists(load_path)) {
|
||||
FileUtil::FSTEntry texture_dir;
|
||||
std::vector<FileUtil::FSTEntry> textures;
|
||||
// 64 nested folders should be plenty for most cases
|
||||
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
|
||||
FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures);
|
||||
|
||||
for (const auto& file : textures) {
|
||||
if (file.isDirectory)
|
||||
continue;
|
||||
if (file.virtualName.substr(0, 5) != "tex1_")
|
||||
continue;
|
||||
|
||||
u32 width;
|
||||
u32 height;
|
||||
u64 hash;
|
||||
u32 format; // unused
|
||||
// TODO: more modern way of doing this
|
||||
if (std::sscanf(file.virtualName.c_str(), "tex1_%ux%u_%llX_%u.png", &width, &height,
|
||||
&hash, &format) == 4) {
|
||||
AddTexturePath(hash, file.physicalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomTexCache::PreloadTextures(Frontend::ImageInterface& image_interface) {
|
||||
for (const auto& path : custom_texture_paths) {
|
||||
const auto& path_info = path.second;
|
||||
Core::CustomTexInfo tex_info;
|
||||
if (image_interface.DecodePNG(tex_info.tex, tex_info.width, tex_info.height,
|
||||
path_info.path)) {
|
||||
// Make sure the texture size is a power of 2
|
||||
std::bitset<32> width_bits(tex_info.width);
|
||||
std::bitset<32> height_bits(tex_info.height);
|
||||
if (width_bits.count() == 1 && height_bits.count() == 1) {
|
||||
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
|
||||
Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
|
||||
CacheTexture(path_info.hash, tex_info.tex, tex_info.width, tex_info.height);
|
||||
} else {
|
||||
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CustomTexCache::CustomTextureExists(u64 hash) const {
|
||||
return custom_texture_paths.count(hash);
|
||||
}
|
||||
|
||||
const CustomTexPathInfo& CustomTexCache::LookupTexturePathInfo(u64 hash) const {
|
||||
return custom_texture_paths.at(hash);
|
||||
}
|
||||
|
||||
bool CustomTexCache::IsTexturePathMapEmpty() const {
|
||||
return custom_texture_paths.size() == 0;
|
||||
}
|
||||
} // namespace Core
|
@ -1,55 +0,0 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Frontend {
|
||||
class ImageInterface;
|
||||
} // namespace Frontend
|
||||
|
||||
namespace Core {
|
||||
struct CustomTexInfo {
|
||||
u32 width;
|
||||
u32 height;
|
||||
std::vector<u8> tex;
|
||||
};
|
||||
|
||||
// This is to avoid parsing the filename multiple times
|
||||
struct CustomTexPathInfo {
|
||||
std::string path;
|
||||
u64 hash;
|
||||
};
|
||||
|
||||
// TODO: think of a better name for this class...
|
||||
class CustomTexCache {
|
||||
public:
|
||||
explicit CustomTexCache();
|
||||
~CustomTexCache();
|
||||
|
||||
bool IsTextureDumped(u64 hash) const;
|
||||
void SetTextureDumped(u64 hash);
|
||||
|
||||
bool IsTextureCached(u64 hash) const;
|
||||
const CustomTexInfo& LookupTexture(u64 hash) const;
|
||||
void CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height);
|
||||
|
||||
void AddTexturePath(u64 hash, const std::string& path);
|
||||
void FindCustomTextures(u64 program_id);
|
||||
void PreloadTextures(Frontend::ImageInterface& image_interface);
|
||||
bool CustomTextureExists(u64 hash) const;
|
||||
const CustomTexPathInfo& LookupTexturePathInfo(u64 hash) const;
|
||||
bool IsTexturePathMapEmpty() const;
|
||||
|
||||
private:
|
||||
std::unordered_set<u64> dumped_textures;
|
||||
std::unordered_map<u64, CustomTexInfo> custom_textures;
|
||||
std::unordered_map<u64, CustomTexPathInfo> custom_texture_paths;
|
||||
};
|
||||
} // namespace Core
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user