diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cc776d30a0..48937e2874 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -34,12 +34,12 @@ jobs:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
submodules: recursive
- name: Add MSBuild to PATH
- uses: microsoft/setup-msbuild@v1
+ uses: microsoft/setup-msbuild@v1.1
- name: Build Windows
working-directory: ${{ env.GITHUB_WORKSPACE }}
@@ -63,20 +63,20 @@ jobs:
run: cp Windows/Release/*.exe ppsspp/
- name: Upload build
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v3
with:
name: Windows ${{ matrix.platform }} build
path: ppsspp/
build-uwp:
- runs-on: windows-latest
+ runs-on: windows-2019
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
submodules: recursive
- name: Add MSBuild to PATH
- uses: microsoft/setup-msbuild@v1
+ uses: microsoft/setup-msbuild@v1.1
- name: Build UWP
working-directory: ${{ env.GITHUB_WORKSPACE }}
@@ -87,7 +87,7 @@ jobs:
needs: build-windows
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
submodules: false
@@ -97,7 +97,7 @@ jobs:
run: git submodule update --init pspautotests assets/lang
- name: Download build
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: Windows x64 build
path: ppsspp/
@@ -151,6 +151,18 @@ jobs:
cxx: clang++
args: cd android && ./ab.sh -j2 APP_ABI=x86_64 UNITTEST=1 HEADLESS=1
id: android-x86_64
+ - os: ubuntu-latest
+ extra: android
+ cc: clang
+ cxx: clang++
+ args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1 OPENXR_PLATFORM_QUEST=1
+ id: android-vr-quest
+ - os: ubuntu-latest
+ extra: android
+ cc: clang
+ cxx: clang++
+ args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1 OPENXR_PLATFORM_PICO=1
+ id: android-vr-pico
- os: ubuntu-latest
extra: android
cc: clang
@@ -168,13 +180,13 @@ jobs:
extra: libretro
cc: gcc
cxx: g++
- args: ./b.sh --libretro
+ args: make -C libretro -f Makefile -j2
id: gcc-libretro
- os: ubuntu-latest
extra: libretro
cc: clang
cxx: clang++
- args: ./b.sh --libretro
+ args: make -C libretro -f Makefile -j2
id: clang-libretro
- os: macos-latest
@@ -192,7 +204,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
submodules: recursive
@@ -202,19 +214,11 @@ jobs:
run: |
git fetch --deepen=15000 --no-recurse-submodules --tags || exit 0
- - name: Cache Qt
- uses: actions/cache@v1
- if: matrix.extra == 'qt'
- id: cache-qt
- with:
- path: ${{ runner.workspace }}/Qt
- key: ${{ runner.os }}-QtCache
-
- name: Install Qt
- uses: jurplel/install-qt-action@v2
+ uses: jurplel/install-qt-action@v3
if: matrix.extra == 'qt'
with:
- cached: ${{ steps.cache-qt.outputs.cache-hit }}
+ cache: true
- uses: nttld/setup-ndk@v1
if: matrix.extra == 'android'
@@ -236,7 +240,7 @@ jobs:
echo "#define PPSSPP_GIT_VERSION_NO_UPDATE 1" >> git-version.cpp
- name: Setup ccache
- uses: hendrikmuhs/ccache-action@v1
+ uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ matrix.id }}
@@ -246,8 +250,20 @@ jobs:
CXX: ${{ matrix.cxx }}
NDK: ${{ steps.setup-ndk.outputs.ndk-path }}
NDK_CCACHE: ccache
+ USE_CCACHE: 1
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
+ export CCACHE_SLOPPINESS=clang_index_store,ivfsoverlay,include_file_ctime,include_file_mtime,modules,system_headers,time_macros
+ if [ "${{ matrix.extra }}" == "android" ]; then
+ export CCACHE_FILECLONE=true
+ export CCACHE_DEPEND=true
+ export CCACHE_COMPILERCHECK=content
+ elif [ "$RUNNER_OS" == "macOS" ]; then
+ export CCACHE_SLOPPINESS=pch_defines,$CCACHE_SLOPPINESS
+ export CCACHE_FILECLONE=true
+ export CCACHE_DEPEND=true
+ export CCACHE_COMPILERCHECK=content
+ fi
${{ matrix.args }}
- name: Package build
@@ -279,7 +295,7 @@ jobs:
fi
- name: Upload build
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v3
if: matrix.extra == 'test'
with:
name: ${{ matrix.os }} build
@@ -313,7 +329,7 @@ jobs:
needs: build
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
submodules: false
@@ -335,7 +351,7 @@ jobs:
git submodule update --init SDL/macOS
- name: Download build
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: ${{ matrix.os }} build
path: ppsspp/
diff --git a/.github/workflows/generateDockerLayer.yml b/.github/workflows/generateDockerLayer.yml
index 91164f5af8..5e038f398f 100644
--- a/.github/workflows/generateDockerLayer.yml
+++ b/.github/workflows/generateDockerLayer.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Extract DOCKER_TAG using tag name
if: startsWith(github.ref, 'refs/tags/')
@@ -25,13 +25,13 @@ jobs:
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
- name: Login to Github registry
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
- - uses: docker/build-push-action@v2
+ - uses: docker/build-push-action@v3
with:
push: true
- tags: ghcr.io/${{ github.repository }}:${{ env.DOCKER_TAG }}
\ No newline at end of file
+ tags: ghcr.io/${{ github.repository }}:${{ env.DOCKER_TAG }}
diff --git a/.github/workflows/manual_generate_apk.yml b/.github/workflows/manual_generate_apk.yml
index 3f38055ac5..4c48320259 100644
--- a/.github/workflows/manual_generate_apk.yml
+++ b/.github/workflows/manual_generate_apk.yml
@@ -19,14 +19,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
- name: Setup JDK
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v2
with:
+ distribution: 'zulu'
java-version: '11'
#- name: Setup SDK
@@ -54,7 +55,7 @@ jobs:
fi
- name: Upload APK
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v3
with:
name: android-${{ github.event.inputs.buildVariant }} build
path: ppsspp/
diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml
index 47c022593e..84fec3625a 100644
--- a/.github/workflows/tarball.yml
+++ b/.github/workflows/tarball.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
submodules: recursive
@@ -40,7 +40,7 @@ jobs:
sed -i "s;unknown;${VERSION};" $PKGNAME/git-version.cmake || true
TARBALL=$PKGNAME.tar.xz
tar cJf $TARBALL $PKGNAME
- echo "::set-output name=tarball::$TARBALL"
+ echo "tarball=$TARBALL" >> $GITHUB_OUTPUT
- name: upload tarball
uses: softprops/action-gh-release@v1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 28ce455b8a..90cd57ace1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -119,7 +119,6 @@ add_definitions(-DASSETS_DIR="${CMAKE_INSTALL_FULL_DATADIR}/ppsspp/assets/")
if(OPENXR)
add_definitions(-DOPENXR)
add_library(openxr SHARED IMPORTED)
- include_directories(ext/openxr)
if(OPENXR_PLATFORM_PICO)
add_definitions(-DOPENXR_PLATFORM_PICO)
set_property(TARGET openxr PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/ext/openxr/libs/pico/arm64-v8a/libopenxr_loader.so")
@@ -507,6 +506,24 @@ if(WIN32)
)
endif()
+set(CommonVR
+ Common/VR/OpenXRLoader.cpp
+ Common/VR/OpenXRLoader.h
+ Common/VR/PPSSPPVR.cpp
+ Common/VR/PPSSPPVR.h
+ Common/VR/VRBase.cpp
+ Common/VR/VRBase.h
+ Common/VR/VRFramebuffer.cpp
+ Common/VR/VRFramebuffer.h
+ Common/VR/VRInput.cpp
+ Common/VR/VRInput.h
+ Common/VR/VRMath.cpp
+ Common/VR/VRMath.h
+ Common/VR/VRRenderer.cpp
+ Common/VR/VRRenderer.h
+)
+include_directories(ext/openxr)
+
add_library(Common STATIC
${CommonX86}
${CommonARM}
@@ -514,6 +531,7 @@ add_library(Common STATIC
${CommonMIPS}
${CommonRISCV64}
${CommonD3D}
+ ${CommonVR}
Common/Serialize/Serializer.cpp
Common/Serialize/Serializer.h
Common/Serialize/SerializeDeque.h
@@ -588,6 +606,7 @@ add_library(Common STATIC
Common/File/FileDescriptor.cpp
Common/File/FileDescriptor.h
Common/GPU/DataFormat.h
+ Common/GPU/MiscTypes.h
Common/GPU/thin3d.cpp
Common/GPU/thin3d.h
Common/GPU/thin3d_create.h
@@ -619,6 +638,8 @@ add_library(Common STATIC
Common/GPU/Vulkan/VulkanDebug.h
Common/GPU/Vulkan/VulkanContext.cpp
Common/GPU/Vulkan/VulkanContext.h
+ Common/GPU/Vulkan/VulkanFramebuffer.cpp
+ Common/GPU/Vulkan/VulkanFramebuffer.h
Common/GPU/Vulkan/VulkanImage.cpp
Common/GPU/Vulkan/VulkanImage.h
Common/GPU/Vulkan/VulkanLoader.cpp
@@ -674,6 +695,8 @@ add_library(Common STATIC
Common/Render/TextureAtlas.h
Common/Render/DrawBuffer.cpp
Common/Render/DrawBuffer.h
+ Common/Render/ManagedTexture.cpp
+ Common/Render/ManagedTexture.h
Common/Render/Text/draw_text.cpp
Common/Render/Text/draw_text.h
Common/Render/Text/draw_text_android.cpp
@@ -692,6 +715,8 @@ add_library(Common STATIC
Common/Thread/ThreadUtil.h
Common/Thread/ThreadManager.cpp
Common/Thread/ThreadManager.h
+ Common/UI/AsyncImageFileView.cpp
+ Common/UI/AsyncImageFileView.h
Common/UI/Root.cpp
Common/UI/Root.h
Common/UI/Screen.cpp
@@ -859,7 +884,7 @@ add_library(cityhash STATIC
ext/cityhash/city.h
ext/cityhash/citycrc.h
)
-include_directories(ext/cityhash)
+target_include_directories(cityhash PRIVATE ext/cityhash)
if(NOT MSVC)
# These can be fast even for debug.
@@ -1091,24 +1116,7 @@ if(ANDROID)
android/jni/OpenSLContext.cpp
android/jni/OpenSLContext.h
)
-
if (OPENXR)
- set(nativeExtra ${nativeExtra}
- Common/VR/PPSSPPVR.cpp
- Common/VR/PPSSPPVR.h
- Common/VR/VRBase.cpp
- Common/VR/VRBase.h
- Common/VR/VRFramebuffer.cpp
- Common/VR/VRFramebuffer.h
- Common/VR/VRInput.cpp
- Common/VR/VRInput.h
- Common/VR/VRMath.cpp
- Common/VR/VRMath.h
- Common/VR/VRRenderer.cpp
- Common/VR/VRRenderer.h
- Common/VR/VRTweaks.cpp
- Common/VR/VRTweaks.h
- )
set(nativeExtraLibs ${nativeExtraLibs} openxr)
endif()
# No target
@@ -1242,8 +1250,6 @@ list(APPEND NativeAppSource
UI/ChatScreen.cpp
UI/DevScreens.cpp
UI/DevScreens.h
- UI/DisplayLayoutEditor.cpp
- UI/DisplayLayoutEditor.h
UI/DisplayLayoutScreen.cpp
UI/DisplayLayoutScreen.h
UI/EmuScreen.h
@@ -1292,8 +1298,6 @@ list(APPEND NativeAppSource
UI/MemStickScreen.cpp
UI/ProfilerDraw.h
UI/ProfilerDraw.cpp
- UI/TextureUtil.h
- UI/TextureUtil.cpp
UI/ComboKeyMappingScreen.h
UI/ComboKeyMappingScreen.cpp
UI/Theme.h
@@ -1312,6 +1316,10 @@ if(ANDROID)
endif()
endif()
+if (IOS)
+ set(nativeExtra ${nativeExtra} ${NativeAppSource})
+endif()
+
add_library(native STATIC
${nativeExtra}
Common/Render/Text/draw_text_qt.cpp
@@ -1356,7 +1364,7 @@ add_library(kirk STATIC
ext/libkirk/kirk_engine.c
ext/libkirk/kirk_engine.h
)
-include_directories(ext/libkirk)
+target_include_directories(kirk PRIVATE ext/libkirk)
add_library(sfmt19937 STATIC
ext/sfmt19937/SFMT.c
@@ -1365,19 +1373,20 @@ add_library(sfmt19937 STATIC
ext/sfmt19937/SFMT-params.h
ext/sfmt19937/SFMT-params19937.h
)
-include_directories(ext/sfmt19937)
+target_compile_definitions(sfmt19937 PRIVATE SFMT_MEXP=19937)
+target_include_directories(sfmt19937 PRIVATE ext/sfmt19937)
add_library(xbrz STATIC
ext/xbrz/xbrz.cpp
ext/xbrz/xbrz.h
)
-include_directories(ext/xbrz)
+target_include_directories(xbrz PRIVATE ext/xbrz)
add_library(xxhash STATIC
ext/xxhash.c
ext/xxhash.h
)
-include_directories(ext/xxhash)
+target_include_directories(xxhash PRIVATE ext/xxhash)
set(CoreExtra)
set(CoreExtraLibs)
@@ -2063,6 +2072,7 @@ if(USE_SYSTEM_ZSTD)
target_link_libraries(${CoreLibName} ${ZSTD_LIBRARY})
else()
set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "we don't need zstd programs" FORCE)
+ set(ZSTD_LEGACY_SUPPORT OFF CACHE BOOL "we don't use any old zstd files" FORCE)
add_subdirectory(ext/zstd/build/cmake EXCLUDE_FROM_ALL)
set(CoreExtraLibs ${CoreExtraLibs} libzstd_static)
include_directories(ext/zstd/lib)
diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h
index 66add4e269..aaeb24fb1d 100644
--- a/Common/Arm64Emitter.h
+++ b/Common/Arm64Emitter.h
@@ -253,7 +253,7 @@ public:
private:
ARM64Reg m_destReg;
WidthSpecifier m_width;
- ExtendSpecifier m_extend;
+ ExtendSpecifier m_extend = EXTEND_UXTB;
TypeSpecifier m_type;
ShiftType m_shifttype;
u32 m_shift;
diff --git a/Common/ArmEmitter.h b/Common/ArmEmitter.h
index d71c1efdf2..46a36c8805 100644
--- a/Common/ArmEmitter.h
+++ b/Common/ArmEmitter.h
@@ -112,32 +112,29 @@ private:
OpType Type;
// IMM types
- u8 Rotation; // Only for u8 values
+ u8 Rotation = 0; // Only for u8 values
// Register types
- u8 IndexOrShift;
- ShiftType Shift;
+ u8 IndexOrShift = 0;
+ ShiftType Shift = ST_LSL;
public:
- OpType GetType() const
- {
+ OpType GetType() const {
return Type;
}
- Operand2() {}
- Operand2(u32 imm, OpType type = TYPE_IMM)
- {
- Type = type;
- Value = imm;
- Rotation = 0;
+ Operand2() {
+ Type = TYPE_IMM;
+ Value = 0;
+ }
+ Operand2(u32 imm, OpType type = TYPE_IMM) {
+ Type = type;
+ Value = imm;
}
- Operand2(ARMReg Reg)
- {
+ Operand2(ARMReg Reg) {
Type = TYPE_REG;
Value = Reg;
- Rotation = 0;
}
- Operand2(u8 imm, u8 rotation)
- {
+ Operand2(u8 imm, u8 rotation) {
Type = TYPE_IMM;
Value = imm;
Rotation = rotation;
diff --git a/Common/Buffer.cpp b/Common/Buffer.cpp
index 6dc6b74c5a..58a485593c 100644
--- a/Common/Buffer.cpp
+++ b/Common/Buffer.cpp
@@ -67,7 +67,9 @@ int Buffer::TakeLineCRLF(std::string *dest) {
if (after_next_line < 0) {
return after_next_line;
} else {
- Take(after_next_line - 2, dest);
+ _dbg_assert_(after_next_line >= 2);
+ if (after_next_line != 2)
+ Take((size_t)after_next_line - 2, dest);
Skip(2); // Skip the CRLF
return after_next_line - 2;
}
diff --git a/Common/CPUDetect.cpp b/Common/CPUDetect.cpp
index 262bbf4def..ebf78012b6 100644
--- a/Common/CPUDetect.cpp
+++ b/Common/CPUDetect.cpp
@@ -321,7 +321,10 @@ void CPUInfo::Detect() {
#if PPSSPP_PLATFORM(WINDOWS)
#if !PPSSPP_PLATFORM(UWP)
typedef BOOL (WINAPI *getLogicalProcessorInformationEx_f)(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX Buffer, PDWORD ReturnedLength);
- auto getLogicalProcessorInformationEx = (getLogicalProcessorInformationEx_f)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetLogicalProcessorInformationEx");
+ getLogicalProcessorInformationEx_f getLogicalProcessorInformationEx = nullptr;
+ HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
+ if (kernel32)
+ getLogicalProcessorInformationEx = (getLogicalProcessorInformationEx_f)GetProcAddress(kernel32, "GetLogicalProcessorInformationEx");
#else
void *getLogicalProcessorInformationEx = nullptr;
#endif
diff --git a/Common/CodeBlock.h b/Common/CodeBlock.h
index 0369c66893..0307cf9df6 100644
--- a/Common/CodeBlock.h
+++ b/Common/CodeBlock.h
@@ -57,7 +57,10 @@ private:
public:
CodeBlock() {}
- virtual ~CodeBlock() { if (region) FreeCodeSpace(); }
+ ~CodeBlock() {
+ if (region)
+ FreeCodeSpace();
+ }
// Call this before you generate any code.
void AllocCodeSpace(int size) {
@@ -80,7 +83,7 @@ public:
// If not WX Exclusive, no need to call ProtectMemoryPages because we never change the protection from RWX.
PoisonMemory(offset);
ResetCodePtr(offset);
- if (PlatformIsWXExclusive()) {
+ if (PlatformIsWXExclusive() && offset != 0) {
// Need to re-protect the part we didn't clear.
ProtectMemoryPages(region, offset, MEM_PROT_READ | MEM_PROT_EXEC);
}
@@ -95,15 +98,37 @@ public:
// In case the last block made the current page exec/no-write, let's fix that.
if (PlatformIsWXExclusive()) {
writeStart_ = GetCodePtr();
+ if (writeStart_ + sizeEstimate - region > (ptrdiff_t)region_size)
+ sizeEstimate = region_size - (writeStart_ - region);
+ writeEstimated_ = sizeEstimate;
ProtectMemoryPages(writeStart_, sizeEstimate, MEM_PROT_READ | MEM_PROT_WRITE);
}
}
+ // In case you now know your original estimate is wrong.
+ void ContinueWrite(size_t sizeEstimate = 1) {
+ _dbg_assert_msg_(writeStart_, "Must have already called BeginWrite()");
+ if (PlatformIsWXExclusive()) {
+ const uint8_t *pos = GetCodePtr();
+ if (pos + sizeEstimate - region > (ptrdiff_t)region_size)
+ sizeEstimate = region_size - (pos - region);
+ writeEstimated_ = pos - writeStart_ + sizeEstimate;
+ ProtectMemoryPages(pos, sizeEstimate, MEM_PROT_READ | MEM_PROT_WRITE);
+ }
+ }
+
void EndWrite() {
// OK, we're done. Re-protect the memory we touched.
if (PlatformIsWXExclusive() && writeStart_ != nullptr) {
const uint8_t *end = GetCodePtr();
- ProtectMemoryPages(writeStart_, end - writeStart_, MEM_PROT_READ | MEM_PROT_EXEC);
+ size_t sz = end - writeStart_;
+ if (sz > writeEstimated_)
+ WARN_LOG(JIT, "EndWrite(): Estimated %d bytes, wrote %d", (int)writeEstimated_, (int)sz);
+ // If we protected and wrote less, we may need to unprotect.
+ // Especially if we're linking blocks or similar.
+ if (sz < writeEstimated_)
+ sz = writeEstimated_;
+ ProtectMemoryPages(writeStart_, sz, MEM_PROT_READ | MEM_PROT_EXEC);
writeStart_ = nullptr;
}
}
@@ -150,5 +175,6 @@ private:
// Note: this is a readable pointer.
const uint8_t *writeStart_ = nullptr;
uint8_t *writableRegion = nullptr;
+ size_t writeEstimated_ = 0;
};
diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index aa0f9bb1e7..d51b49d585 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -136,7 +136,7 @@
_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
Common/DbgNew.h
MultiThreadedDebug
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
StreamingSIMDExtensions2
Precise
true
@@ -162,7 +162,7 @@
Common/DbgNew.h
MultiThreadedDebug
_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
NotSet
Precise
false
@@ -189,7 +189,7 @@
Common/DbgNew.h
MultiThreadedDebug
_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_64=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
NotSet
Precise
false
@@ -217,7 +217,7 @@
MultiThreadedDebug
_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_32=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
NotSet
Precise
false
@@ -250,7 +250,7 @@
false
StreamingSIMDExtensions2
Precise
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
Speed
true
false
@@ -280,7 +280,7 @@
USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_64=1;_M_X64=1;NDEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
false
NotSet
Precise
@@ -315,7 +315,7 @@
USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_64=1;NDEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
false
NotSet
Precise
@@ -350,7 +350,7 @@
USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_32=1;NDEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
- ../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
+ ../ext/openxr;../ext/glew;../ext/snappy;../ext/glslang;../ext/libpng17;../ext;../ext/zlib;..;../ext/zstd/lib
false
NotSet
Precise
@@ -432,6 +432,7 @@
+
@@ -449,6 +450,7 @@
+
@@ -477,6 +479,7 @@
+
@@ -549,6 +552,7 @@
+
@@ -557,6 +561,13 @@
+
+
+
+
+
+
+
@@ -871,6 +882,7 @@
+
@@ -898,6 +910,7 @@
+
@@ -981,6 +994,7 @@
+
@@ -989,6 +1003,13 @@
+
+
+
+
+
+
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index 0cb9e79423..18cbbd9985 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -425,6 +425,39 @@
Math
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ UI
+
+
+ Render
+
+
+ GPU
+
+
+ GPU\Vulkan
+
@@ -803,6 +836,36 @@
Math
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ VR
+
+
+ UI
+
+
+ Render
+
+
+ GPU\Vulkan
+
@@ -907,6 +970,9 @@
{7b17065c-729c-47c3-a02d-66dc383529dd}
+
+ {9d1c29fd-8ac7-4475-8ea6-c8c759b695fe}
+
diff --git a/Common/ConsoleListener.h b/Common/ConsoleListener.h
index 976e7531cd..99c6730750 100644
--- a/Common/ConsoleListener.h
+++ b/Common/ConsoleListener.h
@@ -41,7 +41,7 @@ public:
#if defined(USING_WIN_UI)
COORD GetCoordinates(int BytesRead, int BufferWidth);
#endif
- void Log(const LogMessage &message);
+ void Log(const LogMessage &message) override;
void ClearScreen(bool Cursor = true);
void Show(bool bShow);
@@ -66,8 +66,8 @@ private:
static std::atomic logPendingReadPos;
static std::atomic logPendingWritePos;
- int openWidth_;
- int openHeight_;
+ int openWidth_ = 0;
+ int openHeight_ = 0;
std::wstring title_;
#endif
bool bHidden;
diff --git a/Common/Data/Color/RGBAUtil.h b/Common/Data/Color/RGBAUtil.h
index 9e8128bae1..3ea03f328a 100644
--- a/Common/Data/Color/RGBAUtil.h
+++ b/Common/Data/Color/RGBAUtil.h
@@ -16,3 +16,9 @@ typedef unsigned int Color;
inline Color darkenColor(Color color) {
return (color & 0xFF000000) | ((color >> 1) & 0x7F7F7F);
}
+
+inline Color lightenColor(Color color) {
+ color = ~color;
+ color = (color & 0xFF000000) | ((color >> 1) & 0x7F7F7F);
+ return ~color;
+}
diff --git a/Common/Data/Convert/ColorConv.cpp b/Common/Data/Convert/ColorConv.cpp
index 55686f5c00..01c434e6e5 100644
--- a/Common/Data/Convert/ColorConv.cpp
+++ b/Common/Data/Convert/ColorConv.cpp
@@ -34,88 +34,6 @@
#endif
#endif
-// convert 4444 image to 8888, parallelizable
-void convert4444_gl(u16* data, u32* out, int width, int l, int u) {
- for (int y = l; y < u; ++y) {
- for (int x = 0; x < width; ++x) {
- u32 val = data[y*width + x];
- u32 r = ((val >> 12) & 0xF) * 17;
- u32 g = ((val >> 8) & 0xF) * 17;
- u32 b = ((val >> 4) & 0xF) * 17;
- u32 a = ((val >> 0) & 0xF) * 17;
- out[y*width + x] = (a << 24) | (b << 16) | (g << 8) | r;
- }
- }
-}
-
-// convert 565 image to 8888, parallelizable
-void convert565_gl(u16* data, u32* out, int width, int l, int u) {
- for (int y = l; y < u; ++y) {
- for (int x = 0; x < width; ++x) {
- u32 val = data[y*width + x];
- u32 r = Convert5To8((val >> 11) & 0x1F);
- u32 g = Convert6To8((val >> 5) & 0x3F);
- u32 b = Convert5To8((val)& 0x1F);
- out[y*width + x] = (0xFF << 24) | (b << 16) | (g << 8) | r;
- }
- }
-}
-
-// convert 5551 image to 8888, parallelizable
-void convert5551_gl(u16* data, u32* out, int width, int l, int u) {
- for (int y = l; y < u; ++y) {
- for (int x = 0; x < width; ++x) {
- u32 val = data[y*width + x];
- u32 r = Convert5To8((val >> 11) & 0x1F);
- u32 g = Convert5To8((val >> 6) & 0x1F);
- u32 b = Convert5To8((val >> 1) & 0x1F);
- u32 a = (val & 0x1) * 255;
- out[y*width + x] = (a << 24) | (b << 16) | (g << 8) | r;
- }
- }
-}
-
-// convert 4444 image to 8888, parallelizable
-void convert4444_dx9(u16* data, u32* out, int width, int l, int u) {
- for (int y = l; y < u; ++y) {
- for (int x = 0; x < width; ++x) {
- u32 val = data[y*width + x];
- u32 r = ((val >> 0) & 0xF) * 17;
- u32 g = ((val >> 4) & 0xF) * 17;
- u32 b = ((val >> 8) & 0xF) * 17;
- u32 a = ((val >> 12) & 0xF) * 17;
- out[y*width + x] = (a << 24) | (b << 16) | (g << 8) | r;
- }
- }
-}
-
-// convert 565 image to 8888, parallelizable
-void convert565_dx9(u16* data, u32* out, int width, int l, int u) {
- for (int y = l; y < u; ++y) {
- for (int x = 0; x < width; ++x) {
- u32 val = data[y*width + x];
- u32 r = Convert5To8((val)& 0x1F);
- u32 g = Convert6To8((val >> 5) & 0x3F);
- u32 b = Convert5To8((val >> 11) & 0x1F);
- out[y*width + x] = (0xFF << 24) | (b << 16) | (g << 8) | r;
- }
- }
-}
-
-// convert 5551 image to 8888, parallelizable
-void convert5551_dx9(u16* data, u32* out, int width, int l, int u) {
- for (int y = l; y < u; ++y) {
- for (int x = 0; x < width; ++x) {
- u32 val = data[y*width + x];
- u32 r = Convert5To8((val >> 0) & 0x1F);
- u32 g = Convert5To8((val >> 5) & 0x1F);
- u32 b = Convert5To8((val >> 10) & 0x1F);
- u32 a = ((val >> 15) & 0x1) * 255;
- out[y*width + x] = (a << 24) | (b << 16) | (g << 8) | r;
- }
- }
-}
-
void ConvertBGRA8888ToRGBA8888(u32 *dst, const u32 *src, u32 numPixels) {
#ifdef _M_SSE
const __m128i maskGA = _mm_set1_epi32(0xFF00FF00);
diff --git a/Common/Data/Convert/ColorConv.h b/Common/Data/Convert/ColorConv.h
index 077533171b..53a286d127 100644
--- a/Common/Data/Convert/ColorConv.h
+++ b/Common/Data/Convert/ColorConv.h
@@ -101,6 +101,14 @@ inline u16 RGBA8888ToRGBA5551(u32 value) {
return (u16)(r | g | b | a);
}
+// Used in fast sprite path.
+inline u16 RGBA8888ToRGBA555X(u32 value) {
+ u32 r = (value >> 3) & 0x1F;
+ u32 g = (value >> 6) & (0x1F << 5);
+ u32 b = (value >> 9) & (0x1F << 10);
+ return (u16)(r | g | b);
+}
+
inline u16 RGBA8888ToRGBA4444(u32 value) {
const u32 c = value >> 4;
const u16 r = (c >> 0) & 0x000F;
@@ -110,14 +118,13 @@ inline u16 RGBA8888ToRGBA4444(u32 value) {
return r | g | b | a;
}
-// convert image to 8888, parallelizable
-// TODO: Implement these in terms of the conversion functions below.
-void convert4444_gl(u16* data, u32* out, int width, int l, int u);
-void convert565_gl(u16* data, u32* out, int width, int l, int u);
-void convert5551_gl(u16* data, u32* out, int width, int l, int u);
-void convert4444_dx9(u16* data, u32* out, int width, int l, int u);
-void convert565_dx9(u16* data, u32* out, int width, int l, int u);
-void convert5551_dx9(u16* data, u32* out, int width, int l, int u);
+inline u16 RGBA8888ToRGBA444X(u32 value) {
+ const u32 c = value >> 4;
+ const u16 r = (c >> 0) & 0x000F;
+ const u16 g = (c >> 4) & 0x00F0;
+ const u16 b = (c >> 8) & 0x0F00;
+ return r | g | b;
+}
// "Complete" set of color conversion functions between the usual formats.
diff --git a/Common/Data/Convert/SmallDataConvert.cpp b/Common/Data/Convert/SmallDataConvert.cpp
index 4f0d65ea11..ee2673aba1 100644
--- a/Common/Data/Convert/SmallDataConvert.cpp
+++ b/Common/Data/Convert/SmallDataConvert.cpp
@@ -1,3 +1,4 @@
#include "Common/Data/Convert/SmallDataConvert.h"
alignas(16) const float one_over_255_x4[4] = { 1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f, };
+alignas(16) const float exactly_255_x4[4] = { 255.0f, 255.0f, 255.0f, 255.0f, };
diff --git a/Common/Data/Convert/SmallDataConvert.h b/Common/Data/Convert/SmallDataConvert.h
index 95558a17df..0449d3df16 100644
--- a/Common/Data/Convert/SmallDataConvert.h
+++ b/Common/Data/Convert/SmallDataConvert.h
@@ -18,12 +18,12 @@
#endif
extern const float one_over_255_x4[4];
+extern const float exactly_255_x4[4];
// Utilities useful for filling in std140-layout uniform buffers, and similar.
-// NEON intrinsics: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491f/BABDCGGF.html
+// NEON intrinsics: https://developer.arm.com/documentation/den0018/a/NEON-Intrinsics?lang=en
// LSBs in f[0], etc.
-// Could be SSE optimized.
inline void Uint8x4ToFloat4(float f[4], uint32_t u) {
#ifdef _M_SSE
__m128i zero = _mm_setzero_si128();
@@ -47,6 +47,19 @@ inline void Uint8x4ToFloat4(float f[4], uint32_t u) {
// Could be SSE optimized.
inline uint32_t Float4ToUint8x4(const float f[4]) {
+#ifdef _M_SSE
+ __m128i zero = _mm_setzero_si128();
+ __m128 value = _mm_mul_ps(_mm_loadu_ps(f), _mm_load_ps(exactly_255_x4));
+ __m128i ivalue = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(value), zero), zero);
+ return _mm_cvtsi128_si32(ivalue);
+#elif PPSSPP_ARCH(ARM_NEON)
+ const float32x4_t value = vmulq_f32(vld1q_f32(f), vdupq_n_f32(255.0f));
+ uint32x4_t ivalue32 = vcvtq_u32_f32(value);
+ uint16x4_t ivalue16 = vqmovn_u32(ivalue32);
+ uint8x8_t ivalue8 = vqmovn_u16(vcombine_u16(ivalue16, ivalue16)); // Is there no way to avoid the combine here?
+ uint32x2_t outValue32 = vreinterpret_u32_u8(ivalue8);
+ return vget_lane_u32(outValue32, 0);
+#else
int i4[4];
for (int i = 0; i < 4; i++) {
if (f[i] > 1.0f) {
@@ -58,6 +71,30 @@ inline uint32_t Float4ToUint8x4(const float f[4]) {
}
}
return i4[0] | (i4[1] << 8) | (i4[2] << 16) | (i4[3] << 24);
+#endif
+}
+
+inline uint32_t Float4ToUint8x4_NoClamp(const float f[4]) {
+#ifdef _M_SSE
+ // Does actually clamp, no way to avoid it with the pack ops!
+ __m128i zero = _mm_setzero_si128();
+ __m128 value = _mm_mul_ps(_mm_loadu_ps(f), _mm_load_ps(exactly_255_x4));
+ __m128i ivalue = _mm_packus_epi16(_mm_packs_epi32(_mm_cvtps_epi32(value), zero), zero);
+ return _mm_cvtsi128_si32(ivalue);
+#elif PPSSPP_ARCH(ARM_NEON)
+ const float32x4_t value = vmulq_f32(vld1q_f32(f), vdupq_n_f32(255.0f));
+ uint32x4_t ivalue32 = vcvtq_u32_f32(value);
+ uint16x4_t ivalue16 = vqmovn_u32(ivalue32);
+ uint8x8_t ivalue8 = vqmovn_u16(vcombine_u16(ivalue16, ivalue16)); // Is there no way to avoid the combine here?
+ uint32x2_t outValue32 = vreinterpret_u32_u8(ivalue8);
+ return vget_lane_u32(outValue32, 0);
+#else
+ u32 i4[4];
+ for (int i = 0; i < 4; i++) {
+ i4[i] = (int)(f[i] * 255.0f);
+ }
+ return i4[0] | (i4[1] << 8) | (i4[2] << 16) | (i4[3] << 24);
+#endif
}
inline void Uint8x3ToFloat4_AlphaUint8(float f[4], uint32_t u, uint8_t alpha) {
diff --git a/Common/Data/Encoding/Utf8.cpp b/Common/Data/Encoding/Utf8.cpp
index d3ddbdbc21..760fe6b07d 100644
--- a/Common/Data/Encoding/Utf8.cpp
+++ b/Common/Data/Encoding/Utf8.cpp
@@ -112,7 +112,7 @@ int u8_toucs(uint32_t *dest, int sz, const char *src, int srcsz)
the NUL as well.
the destination string will never be bigger than the source string.
*/
-int u8_toutf8(char *dest, int sz, uint32_t *src, int srcsz)
+int u8_toutf8(char *dest, int sz, const uint32_t *src, int srcsz)
{
uint32_t ch;
int i = 0;
@@ -219,19 +219,14 @@ int u8_strlen(const char *s)
}
/* reads the next utf-8 sequence out of a string, updating an index */
-uint32_t u8_nextchar(const char *s, int *i)
-{
- uint32_t ch = 0;
- int sz = 0;
-
- do {
- ch <<= 6;
- ch += (unsigned char)s[(*i)++];
- sz++;
- } while (s[*i] && !isutf(s[*i]));
- ch -= offsetsFromUTF8[sz-1];
-
- return ch;
+uint32_t u8_nextchar(const char *s, int *i) {
+ uint32_t ch = 0;
+ int sz = 0;
+ do {
+ ch = (ch << 6) + (unsigned char)s[(*i)++];
+ sz++;
+ } while (s[*i] && ((s[*i]) & 0xC0) == 0x80);
+ return ch - offsetsFromUTF8[sz - 1];
}
uint32_t u8_nextchar_unsafe(const char *s, int *i) {
diff --git a/Common/Data/Format/IniFile.cpp b/Common/Data/Format/IniFile.cpp
index cd2a658e5c..72aeee6f89 100644
--- a/Common/Data/Format/IniFile.cpp
+++ b/Common/Data/Format/IniFile.cpp
@@ -174,6 +174,19 @@ std::string* Section::GetLine(const char* key, std::string* valueOut, std::strin
return 0;
}
+const std::string* Section::GetLine(const char* key, std::string* valueOut, std::string* commentOut) const
+{
+ for (std::vector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
+ {
+ const std::string& line = *iter;
+ std::string lineKey;
+ ParseLine(line, &lineKey, valueOut, commentOut);
+ if (!strcasecmp(lineKey.c_str(), key))
+ return &line;
+ }
+ return 0;
+}
+
void Section::Set(const char* key, uint32_t newValue) {
Set(key, StringFromFormat("0x%08x", newValue).c_str());
}
@@ -218,7 +231,7 @@ void Section::Set(const char* key, const std::string& newValue, const std::strin
Delete(key);
}
-bool Section::Get(const char* key, std::string* value, const char* defaultValue)
+bool Section::Get(const char* key, std::string* value, const char* defaultValue) const
{
const std::string* line = GetLine(key, value, 0);
if (!line)
@@ -275,7 +288,7 @@ void Section::AddComment(const std::string &comment) {
lines.emplace_back("# " + comment);
}
-bool Section::Get(const char* key, std::vector& values)
+bool Section::Get(const char* key, std::vector& values) const
{
std::string temp;
bool retval = Get(key, &temp, 0);
@@ -303,7 +316,7 @@ bool Section::Get(const char* key, std::vector& values)
return true;
}
-bool Section::Get(const char* key, int* value, int defaultValue)
+bool Section::Get(const char* key, int* value, int defaultValue) const
{
std::string temp;
bool retval = Get(key, &temp, 0);
@@ -313,7 +326,7 @@ bool Section::Get(const char* key, int* value, int defaultValue)
return false;
}
-bool Section::Get(const char* key, uint32_t* value, uint32_t defaultValue)
+bool Section::Get(const char* key, uint32_t* value, uint32_t defaultValue) const
{
std::string temp;
bool retval = Get(key, &temp, 0);
@@ -323,7 +336,7 @@ bool Section::Get(const char* key, uint32_t* value, uint32_t defaultValue)
return false;
}
-bool Section::Get(const char* key, uint64_t* value, uint64_t defaultValue)
+bool Section::Get(const char* key, uint64_t* value, uint64_t defaultValue) const
{
std::string temp;
bool retval = Get(key, &temp, 0);
@@ -333,7 +346,7 @@ bool Section::Get(const char* key, uint64_t* value, uint64_t defaultValue)
return false;
}
-bool Section::Get(const char* key, bool* value, bool defaultValue)
+bool Section::Get(const char* key, bool* value, bool defaultValue) const
{
std::string temp;
bool retval = Get(key, &temp, 0);
@@ -343,7 +356,7 @@ bool Section::Get(const char* key, bool* value, bool defaultValue)
return false;
}
-bool Section::Get(const char* key, float* value, float defaultValue)
+bool Section::Get(const char* key, float* value, float defaultValue) const
{
std::string temp;
bool retval = Get(key, &temp, 0);
@@ -353,7 +366,7 @@ bool Section::Get(const char* key, float* value, float defaultValue)
return false;
}
-bool Section::Get(const char* key, double* value, double defaultValue)
+bool Section::Get(const char* key, double* value, double defaultValue) const
{
std::string temp;
bool retval = Get(key, &temp, 0);
diff --git a/Common/Data/Format/IniFile.h b/Common/Data/Format/IniFile.h
index 87bd1d1830..707f0ab3eb 100644
--- a/Common/Data/Format/IniFile.h
+++ b/Common/Data/Format/IniFile.h
@@ -25,14 +25,16 @@ public:
std::map ToMap() const;
- std::string* GetLine(const char* key, std::string* valueOut, std::string* commentOut);
+ std::string *GetLine(const char* key, std::string* valueOut, std::string* commentOut);
+ const std::string *GetLine(const char* key, std::string* valueOut, std::string* commentOut) const;
+
void Set(const char* key, const char* newValue);
void Set(const char* key, const std::string& newValue, const std::string& defaultValue);
void Set(const std::string &key, const std::string &value) {
Set(key.c_str(), value.c_str());
}
- bool Get(const char* key, std::string* value, const char* defaultValue);
+ bool Get(const char* key, std::string* value, const char* defaultValue) const;
void Set(const char* key, uint32_t newValue);
void Set(const char* key, uint64_t newValue);
@@ -57,13 +59,13 @@ public:
void AddComment(const std::string &comment);
- bool Get(const char* key, int* value, int defaultValue = 0);
- bool Get(const char* key, uint32_t* value, uint32_t defaultValue = 0);
- bool Get(const char* key, uint64_t* value, uint64_t defaultValue = 0);
- bool Get(const char* key, bool* value, bool defaultValue = false);
- bool Get(const char* key, float* value, float defaultValue = false);
- bool Get(const char* key, double* value, double defaultValue = false);
- bool Get(const char* key, std::vector& values);
+ bool Get(const char* key, int* value, int defaultValue = 0) const;
+ bool Get(const char* key, uint32_t* value, uint32_t defaultValue = 0) const;
+ bool Get(const char* key, uint64_t* value, uint64_t defaultValue = 0) const;
+ bool Get(const char* key, bool* value, bool defaultValue = false) const;
+ bool Get(const char* key, float* value, float defaultValue = false) const;
+ bool Get(const char* key, double* value, double defaultValue = false) const;
+ bool Get(const char* key, std::vector& values) const;
bool operator < (const Section& other) const {
return name_ < other.name_;
diff --git a/Common/Data/Format/JSONReader.h b/Common/Data/Format/JSONReader.h
index 4156650301..792d6a669a 100644
--- a/Common/Data/Format/JSONReader.h
+++ b/Common/Data/Format/JSONReader.h
@@ -47,9 +47,11 @@ public:
JsonReader(const std::string &filename);
JsonReader(const void *data, size_t size) {
buffer_ = (char *)malloc(size + 1);
- memcpy(buffer_, data, size);
- buffer_[size] = 0;
- parse();
+ if (buffer_) {
+ memcpy(buffer_, data, size);
+ buffer_[size] = 0;
+ parse();
+ }
}
JsonReader(const JsonNode *node) {
ok_ = true;
diff --git a/Common/Data/Text/WrapText.h b/Common/Data/Text/WrapText.h
index bcdd19a6fd..d51f860362 100644
--- a/Common/Data/Text/WrapText.h
+++ b/Common/Data/Text/WrapText.h
@@ -7,6 +7,7 @@ public:
WordWrapper(const char *str, float maxW, int flags)
: str_(str), maxW_(maxW), flags_(flags) {
}
+ virtual ~WordWrapper() {}
std::string Wrapped();
diff --git a/Common/ExceptionHandlerSetup.cpp b/Common/ExceptionHandlerSetup.cpp
index 5594f9a5ce..deb78eb139 100644
--- a/Common/ExceptionHandlerSetup.cpp
+++ b/Common/ExceptionHandlerSetup.cpp
@@ -127,7 +127,7 @@ static void ExceptionThread(mach_port_t port) {
#pragma pack()
memset(&msg_in, 0xee, sizeof(msg_in));
memset(&msg_out, 0xee, sizeof(msg_out));
- mach_msg_header_t* send_msg = nullptr;
+ mach_msg_header_t* send_msg = &msg_out.Head;
mach_msg_size_t send_size = 0;
mach_msg_option_t option = MACH_RCV_MSG;
while (true) {
diff --git a/Common/File/DirListing.cpp b/Common/File/DirListing.cpp
index d17a4f2241..b793e85057 100644
--- a/Common/File/DirListing.cpp
+++ b/Common/File/DirListing.cpp
@@ -317,7 +317,7 @@ std::vector GetWindowsDrives()
{
if (logicaldrives & (1 << i))
{
- CHAR driveName[] = { TEXT('A') + i, TEXT(':'), TEXT('\\'), TEXT('\0') };
+ CHAR driveName[] = { (CHAR)(TEXT('A') + i), TEXT(':'), TEXT('\\'), TEXT('\0') };
std::string str(driveName);
drives.push_back(driveName);
}
diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp
index 8bab82ae6e..e8088e90bc 100644
--- a/Common/File/FileUtil.cpp
+++ b/Common/File/FileUtil.cpp
@@ -141,7 +141,7 @@ FILE *OpenCFile(const Path &path, const char *mode) {
return nullptr;
}
FILE *f = fdopen(descriptor, "wb");
- if (!strcmp(mode, "at") || !strcmp(mode, "a")) {
+ if (f && (!strcmp(mode, "at") || !strcmp(mode, "a"))) {
// Append mode.
fseek(f, 0, SEEK_END);
}
@@ -250,7 +250,8 @@ static bool ResolvePathVista(const std::wstring &path, wchar_t *buf, DWORD bufSi
#else
if (!getFinalPathNameByHandleW) {
HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
- getFinalPathNameByHandleW = (getFinalPathNameByHandleW_f)GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
+ if (kernel32)
+ getFinalPathNameByHandleW = (getFinalPathNameByHandleW_f)GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
}
#endif
diff --git a/Common/File/Path.h b/Common/File/Path.h
index ab49fb10cf..aded7b5bde 100644
--- a/Common/File/Path.h
+++ b/Common/File/Path.h
@@ -118,6 +118,9 @@ public:
bool operator <(const Path &other) const {
return path_ < other.path_;
}
+ bool operator >(const Path &other) const {
+ return path_ > other.path_;
+ }
private:
// The internal representation is currently always the plain string.
diff --git a/Common/File/VFS/AssetReader.h b/Common/File/VFS/AssetReader.h
index df0c4f2821..36ccd60161 100644
--- a/Common/File/VFS/AssetReader.h
+++ b/Common/File/VFS/AssetReader.h
@@ -47,10 +47,10 @@ class DirectoryAssetReader : public AssetReader {
public:
explicit DirectoryAssetReader(const Path &path);
// use delete[]
- virtual uint8_t *ReadAsset(const char *path, size_t *size);
- virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter);
- virtual bool GetFileInfo(const char *path, File::FileInfo *info);
- virtual std::string toString() const {
+ uint8_t *ReadAsset(const char *path, size_t *size) override;
+ bool GetFileListing(const char *path, std::vector *listing, const char *filter) override;
+ bool GetFileInfo(const char *path, File::FileInfo *info) override;
+ std::string toString() const override {
return path_.ToString();
}
diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp
index 0b10980c0f..aa52c919dd 100644
--- a/Common/GPU/D3D11/thin3d_d3d11.cpp
+++ b/Common/GPU/D3D11/thin3d_d3d11.cpp
@@ -95,16 +95,13 @@ public:
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
- Framebuffer *GetCurrentRenderTarget() override {
- return curRenderTarget_;
- }
- void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
+ void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
- void InvalidateCachedState() override;
+ void Invalidate(InvalidationFlags flags) override;
- void BindTextures(int start, int count, Texture **textures) override;
+ void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int index, void *nativeTexture) override;
void BindSamplerStates(int start, int count, SamplerState **states) override;
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override;
@@ -166,21 +163,20 @@ public:
void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override;
- int GetCurrentStepId() const override {
- return stepId_;
+ void SetInvalidationCallback(InvalidationCallback callback) override {
+ invalidationCallback_ = callback;
}
private:
void ApplyCurrentState();
- ID3D11DepthStencilState *GetCachedDepthStencilState(D3D11DepthStencilState *state, uint8_t stencilWriteMask, uint8_t stencilCompareMask);
+ ID3D11DepthStencilState *GetCachedDepthStencilState(const D3D11DepthStencilState *state, uint8_t stencilWriteMask, uint8_t stencilCompareMask);
HWND hWnd_;
ID3D11Device *device_;
ID3D11DeviceContext *context_;
ID3D11Device1 *device1_;
ID3D11DeviceContext1 *context1_;
- int stepId_ = -1;
ID3D11Texture2D *bbRenderTargetTex_ = nullptr; // NOT OWNED
ID3D11RenderTargetView *bbRenderTargetView_ = nullptr;
@@ -219,6 +215,8 @@ private:
ID3D11Buffer *nextIndexBuffer_ = nullptr;
int nextIndexBufferOffset_ = 0;
+ InvalidationCallback invalidationCallback_;
+
// Dynamic state
float blendFactor_[4]{};
bool blendFactorDirty_ = false;
@@ -264,13 +262,16 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de
caps_.framebufferStencilBlitSupported = false;
caps_.framebufferDepthCopySupported = true;
caps_.framebufferSeparateDepthCopySupported = false; // Though could be emulated with a draw.
+ caps_.preferredDepthBufferFormat = DataFormat::D24_S8;
caps_.textureDepthSupported = true;
caps_.texture3DSupported = true;
caps_.fragmentShaderInt32Supported = true;
caps_.anisoSupported = true;
caps_.textureNPOTFullySupported = true;
caps_.fragmentShaderDepthWriteSupported = true;
+ caps_.fragmentShaderStencilWriteSupported = false;
caps_.blendMinMaxSupported = true;
+ caps_.multiSampleLevelsMask = 1; // More could be supported with some work.
D3D11_FEATURE_DATA_D3D11_OPTIONS options{};
HRESULT result = device_->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &options, sizeof(options));
@@ -309,6 +310,14 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de
dxgiDevice->Release();
}
+ caps_.isTilingGPU = false;
+
+ // Hide D3D9 when we know it likely won't work well.
+ caps_.supportsD3D9 = true;
+ if (!strcmp(adapterDesc_.c_str(), "Intel(R) Iris(R) Xe Graphics")) {
+ caps_.supportsD3D9 = false;
+ }
+
// Temp texture for read-back of small images. Custom textures are created on demand for larger ones.
// TODO: Should really benchmark if this extra complexity has any benefit.
D3D11_TEXTURE2D_DESC packDesc{};
@@ -401,7 +410,6 @@ void D3D11DrawContext::HandleEvent(Event ev, int width, int height, void *param1
// Make sure that we don't eliminate the next time the render target is set.
curRenderTargetView_ = nullptr;
curDepthStencilView_ = nullptr;
- stepId_ = 0;
break;
}
}
@@ -548,7 +556,7 @@ public:
float blendFactor[4];
};
-ID3D11DepthStencilState *D3D11DrawContext::GetCachedDepthStencilState(D3D11DepthStencilState *state, uint8_t stencilWriteMask, uint8_t stencilCompareMask) {
+ID3D11DepthStencilState *D3D11DrawContext::GetCachedDepthStencilState(const D3D11DepthStencilState *state, uint8_t stencilWriteMask, uint8_t stencilCompareMask) {
D3D11DepthStencilKey key;
key.desc = state->desc;
key.writeMask = stencilWriteMask;
@@ -1081,17 +1089,19 @@ void D3D11DrawContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
context_->Unmap(curPipeline_->dynamicUniforms, 0);
}
-void D3D11DrawContext::InvalidateCachedState() {
- // This is a signal to forget all our state caching.
- curBlend_ = nullptr;
- curDepthStencil_ = nullptr;
- curRaster_ = nullptr;
- curPS_ = nullptr;
- curVS_ = nullptr;
- curGS_ = nullptr;
- curInputLayout_ = nullptr;
- curTopology_ = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
- curPipeline_ = nullptr;
+void D3D11DrawContext::Invalidate(InvalidationFlags flags) {
+ if (flags & InvalidationFlags::CACHED_RENDER_STATE) {
+ // This is a signal to forget all our state caching.
+ curBlend_ = nullptr;
+ curDepthStencil_ = nullptr;
+ curRaster_ = nullptr;
+ curPS_ = nullptr;
+ curVS_ = nullptr;
+ curGS_ = nullptr;
+ curInputLayout_ = nullptr;
+ curTopology_ = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
+ curPipeline_ = nullptr;
+ }
}
void D3D11DrawContext::BindPipeline(Pipeline *pipeline) {
@@ -1306,35 +1316,38 @@ public:
Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) {
HRESULT hr;
D3D11Framebuffer *fb = new D3D11Framebuffer(desc.width, desc.height);
- if (desc.numColorAttachments) {
- fb->colorFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
- D3D11_TEXTURE2D_DESC descColor{};
- descColor.Width = desc.width;
- descColor.Height = desc.height;
- descColor.MipLevels = 1;
- descColor.ArraySize = 1;
- descColor.Format = fb->colorFormat;
- descColor.SampleDesc.Count = 1;
- descColor.SampleDesc.Quality = 0;
- descColor.Usage = D3D11_USAGE_DEFAULT;
- descColor.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
- descColor.CPUAccessFlags = 0;
- descColor.MiscFlags = 0;
- hr = device_->CreateTexture2D(&descColor, nullptr, &fb->colorTex);
- if (FAILED(hr)) {
- delete fb;
- return nullptr;
- }
- hr = device_->CreateRenderTargetView(fb->colorTex, nullptr, &fb->colorRTView);
- if (FAILED(hr)) {
- delete fb;
- return nullptr;
- }
- hr = device_->CreateShaderResourceView(fb->colorTex, nullptr, &fb->colorSRView);
- if (FAILED(hr)) {
- delete fb;
- return nullptr;
- }
+
+ // We don't (yet?) support multiview for D3D11. Not sure if there's a way to do it.
+ // Texture arrays are supported but we don't have any other use cases yet.
+ _dbg_assert_(desc.numLayers == 1);
+
+ fb->colorFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
+ D3D11_TEXTURE2D_DESC descColor{};
+ descColor.Width = desc.width;
+ descColor.Height = desc.height;
+ descColor.MipLevels = 1;
+ descColor.ArraySize = 1;
+ descColor.Format = fb->colorFormat;
+ descColor.SampleDesc.Count = 1;
+ descColor.SampleDesc.Quality = 0;
+ descColor.Usage = D3D11_USAGE_DEFAULT;
+ descColor.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
+ descColor.CPUAccessFlags = 0;
+ descColor.MiscFlags = 0;
+ hr = device_->CreateTexture2D(&descColor, nullptr, &fb->colorTex);
+ if (FAILED(hr)) {
+ delete fb;
+ return nullptr;
+ }
+ hr = device_->CreateRenderTargetView(fb->colorTex, nullptr, &fb->colorRTView);
+ if (FAILED(hr)) {
+ delete fb;
+ return nullptr;
+ }
+ hr = device_->CreateShaderResourceView(fb->colorTex, nullptr, &fb->colorSRView);
+ if (FAILED(hr)) {
+ delete fb;
+ return nullptr;
}
if (desc.z_stencil) {
@@ -1381,7 +1394,7 @@ Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) {
return fb;
}
-void D3D11DrawContext::BindTextures(int start, int count, Texture **textures) {
+void D3D11DrawContext::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
// Collect the resource views from the textures.
ID3D11ShaderResourceView *views[MAX_BOUND_TEXTURES];
_assert_(start + count <= ARRAY_SIZE(views));
@@ -1469,6 +1482,7 @@ void D3D11DrawContext::CopyFramebufferImage(Framebuffer *srcfb, int level, int x
dstTex = dst->depthStencilTex;
break;
}
+ _assert_(srcTex && dstTex);
// TODO: Check for level too!
if (width == src->Width() && width == dst->Width() && height == src->Height() && height == dst->Height() && x == 0 && y == 0 && z == 0 && dstX == 0 && dstY == 0 && dstZ == 0) {
@@ -1499,13 +1513,11 @@ void D3D11DrawContext::CopyFramebufferImage(Framebuffer *srcfb, int level, int x
D3D11_BOX srcBox{ (UINT)x, (UINT)y, (UINT)z, (UINT)(x + width), (UINT)(y + height), (UINT)(z + depth) };
context_->CopySubresourceRegion(dstTex, dstLevel, dstX, dstY, dstZ, srcTex, level, &srcBox);
}
- stepId_++;
}
bool D3D11DrawContext::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dstfb, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) {
// Unfortunately D3D11 has no equivalent to this, gotta render a quad. Well, in some cases we can issue a copy instead.
Crash();
- stepId_++;
return false;
}
@@ -1529,7 +1541,7 @@ bool D3D11DrawContext::CopyFramebufferToMemorySync(Framebuffer *src, int channel
bool useGlobalPacktex = (bx + bw <= 512 && by + bh <= 512) && channelBits == FB_COLOR_BIT;
- ID3D11Texture2D *packTex;
+ ID3D11Texture2D *packTex = nullptr;
if (!useGlobalPacktex) {
D3D11_TEXTURE2D_DESC packDesc{};
packDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
@@ -1568,6 +1580,9 @@ bool D3D11DrawContext::CopyFramebufferToMemorySync(Framebuffer *src, int channel
packTex = packTexture_;
}
+ if (!packTex)
+ return false;
+
D3D11_BOX srcBox{ (UINT)bx, (UINT)by, 0, (UINT)(bx + bw), (UINT)(by + bh), 1 };
DataFormat srcFormat = DataFormat::UNDEFINED;
switch (channelBits) {
@@ -1651,7 +1666,6 @@ bool D3D11DrawContext::CopyFramebufferToMemorySync(Framebuffer *src, int channel
if (!useGlobalPacktex) {
packTex->Release();
}
- stepId_++;
return true;
}
@@ -1698,11 +1712,14 @@ void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Ren
context_->ClearDepthStencilView(curDepthStencilView_, mask, rp.clearDepth, rp.clearStencil);
}
- stepId_++;
+ if (invalidationCallback_) {
+ invalidationCallback_(InvalidationCallbackFlags::RENDER_PASS_STATE);
+ }
}
-void D3D11DrawContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) {
- _assert_(binding < MAX_BOUND_TEXTURES);
+void D3D11DrawContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) {
+ _dbg_assert_(binding < MAX_BOUND_TEXTURES);
+ _dbg_assert_(layer == ALL_LAYERS || layer == 0); // No multiple layer support on D3D
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
switch (channelBit) {
case FBChannel::FB_COLOR_BIT:
diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp
index 20bcc14e5d..a4d668e369 100644
--- a/Common/GPU/D3D9/thin3d_d3d9.cpp
+++ b/Common/GPU/D3D9/thin3d_d3d9.cpp
@@ -291,7 +291,7 @@ public:
D3D9Texture(LPDIRECT3DDEVICE9 device, LPDIRECT3DDEVICE9EX deviceEx, const TextureDesc &desc);
~D3D9Texture();
void SetToSampler(LPDIRECT3DDEVICE9 device, int sampler);
- LPDIRECT3DBASETEXTURE9 Texture() const {
+ LPDIRECT3DBASETEXTURE9 TexturePtr() const {
// TODO: Cleanup
if (tex_) {
return tex_;
@@ -532,16 +532,13 @@ public:
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
- Framebuffer *GetCurrentRenderTarget() override {
- return curRenderTarget_;
- }
- void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
-
+ void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override;
+
uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
- void BindTextures(int start, int count, Texture **textures) override;
+ void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int index, void *nativeTexture) override;
void BindSamplerStates(int start, int count, SamplerState **states) override {
@@ -593,7 +590,7 @@ public:
case NativeObject::DEVICE_EX:
return (uint64_t)(uintptr_t)deviceEx_;
case NativeObject::TEXTURE_VIEW:
- return (uint64_t)(((D3D9Texture *)srcObject)->Texture());
+ return (uint64_t)(((D3D9Texture *)srcObject)->TexturePtr());
default:
return 0;
}
@@ -613,18 +610,17 @@ public:
void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override;
- int GetCurrentStepId() const override {
- return stepId_;
- }
+ void Invalidate(InvalidationFlags flags) override;
- void InvalidateCachedState() override;
+ void SetInvalidationCallback(InvalidationCallback callback) override {
+ invalidationCallback_ = callback;
+ }
private:
LPDIRECT3D9 d3d_;
LPDIRECT3D9EX d3dEx_;
LPDIRECT3DDEVICE9 device_;
LPDIRECT3DDEVICE9EX deviceEx_;
- int stepId_ = -1;
int adapterId_ = -1;
D3DADAPTER_IDENTIFIER9 identifier_{};
D3DCAPS9 d3dCaps_;
@@ -650,10 +646,14 @@ private:
// Dynamic state
uint8_t stencilRef_ = 0;
+
+ InvalidationCallback invalidationCallback_;
};
-void D3D9Context::InvalidateCachedState() {
- curPipeline_ = nullptr;
+void D3D9Context::Invalidate(InvalidationFlags flags) {
+ if (flags & InvalidationFlags::CACHED_RENDER_STATE) {
+ curPipeline_ = nullptr;
+ }
}
// TODO: Move this detection elsewhere when it's needed elsewhere, not before. It's ugly.
@@ -749,7 +749,6 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID
}
caps_.deviceID = identifier_.DeviceId;
- caps_.multiViewport = false;
caps_.depthRangeMinusOneToOne = false;
caps_.preferredDepthBufferFormat = DataFormat::D24_S8;
caps_.dualSourceBlend = false;
@@ -762,7 +761,10 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID
caps_.framebufferSeparateDepthCopySupported = false;
caps_.texture3DSupported = true;
caps_.fragmentShaderDepthWriteSupported = true;
+ caps_.fragmentShaderStencilWriteSupported = false;
caps_.blendMinMaxSupported = true;
+ caps_.isTilingGPU = false;
+ caps_.multiSampleLevelsMask = 1; // More could be supported with some work.
if ((caps.RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0 && caps.MaxAnisotropy > 1) {
caps_.anisoSupported = true;
@@ -771,6 +773,11 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID
caps_.textureNPOTFullySupported = true;
}
+ caps_.supportsD3D9 = true;
+ if (!strcmp(identifier_.Description, "Intel(R) Iris(R) Xe Graphics")) {
+ caps_.supportsD3D9 = false;
+ }
+
// VS range culling (killing triangles in the vertex shader using NaN) causes problems on Intel.
// Also causes problems on old NVIDIA.
switch (caps_.vendor) {
@@ -915,7 +922,7 @@ Texture *D3D9Context::CreateTexture(const TextureDesc &desc) {
return tex;
}
-void D3D9Context::BindTextures(int start, int count, Texture **textures) {
+void D3D9Context::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
D3D9Texture *tex = static_cast(textures[i - start]);
@@ -1005,7 +1012,7 @@ public:
device->CreateVertexBuffer((UINT)size, usage, 0, D3DPOOL_DEFAULT, &vbuffer_, NULL);
}
}
- virtual ~D3D9Buffer() override {
+ ~D3D9Buffer() {
if (ibuffer_) {
ibuffer_->Release();
}
@@ -1070,16 +1077,16 @@ void D3D9Context::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offse
return;
}
if (buf->vbuffer_) {
- void *ptr;
+ void *ptr = nullptr;
HRESULT res = buf->vbuffer_->Lock((UINT)offset, (UINT)size, &ptr, (flags & UPDATE_DISCARD) ? D3DLOCK_DISCARD : 0);
- if (!FAILED(res)) {
+ if (!FAILED(res) && ptr) {
memcpy(ptr, data, size);
buf->vbuffer_->Unlock();
}
} else if (buf->ibuffer_) {
- void *ptr;
+ void *ptr = nullptr;
HRESULT res = buf->ibuffer_->Lock((UINT)offset, (UINT)size, &ptr, (flags & UPDATE_DISCARD) ? D3DLOCK_DISCARD : 0);
- if (!FAILED(res)) {
+ if (!FAILED(res) && ptr) {
memcpy(ptr, data, size);
buf->ibuffer_->Unlock();
}
@@ -1244,15 +1251,18 @@ public:
};
Framebuffer *D3D9Context::CreateFramebuffer(const FramebufferDesc &desc) {
+ // Don't think D3D9 does array layers.
+ _dbg_assert_(desc.numLayers == 1);
+
static uint32_t id = 0;
D3D9Framebuffer *fbo = new D3D9Framebuffer(desc.width, desc.height);
fbo->depthstenciltex = nullptr;
- HRESULT rtResult = device_->CreateTexture(desc.width, desc.height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &fbo->tex, NULL);
+ HRESULT rtResult = device_->CreateTexture(desc.width, desc.height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &fbo->tex, nullptr);
if (FAILED(rtResult)) {
ERROR_LOG(G3D, "Failed to create render target");
- delete fbo;
+ fbo->Release();
return NULL;
}
fbo->tex->GetSurfaceLevel(0, &fbo->surf);
@@ -1281,9 +1291,15 @@ Framebuffer *D3D9Context::CreateFramebuffer(const FramebufferDesc &desc) {
}
D3D9Framebuffer::~D3D9Framebuffer() {
- tex->Release();
- surf->Release();
- depthstencil->Release();
+ if (tex) {
+ tex->Release();
+ }
+ if (surf) {
+ surf->Release();
+ }
+ if (depthstencil) {
+ depthstencil->Release();
+ }
if (depthstenciltex) {
depthstenciltex->Release();
}
@@ -1320,7 +1336,10 @@ void D3D9Context::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPa
dxstate.scissorRect.restore();
dxstate.scissorTest.restore();
dxstate.viewport.restore();
- stepId_++;
+
+ if (invalidationCallback_) {
+ invalidationCallback_(InvalidationCallbackFlags::RENDER_PASS_STATE);
+ }
}
uintptr_t D3D9Context::GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) {
@@ -1348,8 +1367,9 @@ uintptr_t D3D9Context::GetFramebufferAPITexture(Framebuffer *fbo, int channelBit
}
}
-void D3D9Context::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int color) {
- _assert_(binding < MAX_BOUND_TEXTURES);
+void D3D9Context::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) {
+ _dbg_assert_(binding < MAX_BOUND_TEXTURES);
+ _dbg_assert_(layer == ALL_LAYERS || layer == 0); // No stereo support
D3D9Framebuffer *fb = (D3D9Framebuffer *)fbo;
switch (channelBit) {
case FB_DEPTH_BIT:
@@ -1398,7 +1418,6 @@ bool D3D9Context::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1, int
} else {
return false;
}
- stepId_++;
return SUCCEEDED(device_->StretchRect(srcSurf, &srcRect, dstSurf, &dstRect, (filter == FB_BLIT_LINEAR && channelBits == FB_COLOR_BIT) ? D3DTEXF_LINEAR : D3DTEXF_POINT));
}
@@ -1430,6 +1449,7 @@ bool D3D9Context::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits,
LPDIRECT3DSURFACE9 offscreen = nullptr;
HRESULT hr = E_UNEXPECTED;
+ _assert_(fb != nullptr);
if (channelBits == FB_COLOR_BIT) {
fb->tex->GetLevelDesc(0, &desc);
@@ -1519,7 +1539,6 @@ void D3D9Context::HandleEvent(Event ev, int width, int height, void *param1, voi
device_->GetDepthStencilSurface(&deviceDSsurf);
break;
case Event::PRESENTED:
- stepId_ = 0;
break;
}
}
diff --git a/Common/GPU/DataFormat.h b/Common/GPU/DataFormat.h
index 0c8a93e627..32463d3a16 100644
--- a/Common/GPU/DataFormat.h
+++ b/Common/GPU/DataFormat.h
@@ -65,6 +65,7 @@ enum class DataFormat : uint8_t {
S8,
D16,
+ D16_S8,
D24_S8,
D32F,
D32F_S8,
@@ -76,6 +77,9 @@ inline bool DataFormatIsColor(DataFormat fmt) {
return !DataFormatIsDepthStencil(fmt);
}
+// Limited format support for now.
+const char *DataFormatToString(DataFormat fmt);
+
void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format);
void ConvertFromBGRA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format);
void ConvertToD32F(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format);
diff --git a/Common/GPU/MiscTypes.h b/Common/GPU/MiscTypes.h
new file mode 100644
index 0000000000..0db111a73d
--- /dev/null
+++ b/Common/GPU/MiscTypes.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "Common/Common.h"
+
+enum class InvalidationFlags {
+ CACHED_RENDER_STATE = 1,
+};
+ENUM_CLASS_BITOPS(InvalidationFlags);
+
+enum class InvalidationCallbackFlags {
+ RENDER_PASS_STATE = 1,
+ COMMAND_BUFFER_STATE = 2,
+};
+ENUM_CLASS_BITOPS(InvalidationCallbackFlags);
+
+typedef std::function InvalidationCallback;
diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp
index ecec49c82e..06ef1ead3b 100644
--- a/Common/GPU/OpenGL/GLFeatures.cpp
+++ b/Common/GPU/OpenGL/GLFeatures.cpp
@@ -371,6 +371,7 @@ void CheckGLExtensions() {
gl_extensions.ARB_uniform_buffer_object = g_set_gl_extensions.count("GL_ARB_uniform_buffer_object") != 0;
gl_extensions.ARB_explicit_attrib_location = g_set_gl_extensions.count("GL_ARB_explicit_attrib_location") != 0;
gl_extensions.ARB_texture_non_power_of_two = g_set_gl_extensions.count("GL_ARB_texture_non_power_of_two") != 0;
+ gl_extensions.ARB_shader_stencil_export = g_set_gl_extensions.count("GL_ARB_shader_stencil_export") != 0;
if (gl_extensions.IsGLES) {
gl_extensions.EXT_blend_func_extended = g_set_gl_extensions.count("GL_EXT_blend_func_extended") != 0;
gl_extensions.OES_texture_npot = g_set_gl_extensions.count("GL_OES_texture_npot") != 0;
diff --git a/Common/GPU/OpenGL/GLFeatures.h b/Common/GPU/OpenGL/GLFeatures.h
index 0e07fd266f..a2d20b178f 100644
--- a/Common/GPU/OpenGL/GLFeatures.h
+++ b/Common/GPU/OpenGL/GLFeatures.h
@@ -72,6 +72,7 @@ struct GLExtensions {
bool ARB_uniform_buffer_object;
bool ARB_texture_non_power_of_two;
bool ARB_stencil_texturing;
+ bool ARB_shader_stencil_export;
// EXT
bool EXT_swap_control_tear;
diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp
index c63f42301a..bf27a4fcfa 100644
--- a/Common/GPU/OpenGL/GLQueueRunner.cpp
+++ b/Common/GPU/OpenGL/GLQueueRunner.cpp
@@ -280,7 +280,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski
_dbg_assert_(query.name);
int location = -1;
- if (IsVRBuild() && IsMultiviewSupported()) {
+ if (IsVREnabled() && IsMultiviewSupported()) {
int index = GetStereoBufferIndex(query.name);
if (index >= 0) {
std::string layout = GetStereoBufferLayout(query.name);
@@ -648,7 +648,7 @@ retry_depth:
currentReadHandle_ = fbo->handle;
}
-void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCalls, bool keepSteps) {
+void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCalls, bool keepSteps, bool useVR) {
if (skipGLCalls) {
if (keepSteps) {
return;
@@ -707,7 +707,7 @@ void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCal
switch (step.stepType) {
case GLRStepType::RENDER:
renderCount++;
- if (IsVRBuild()) {
+ if (IsVREnabled()) {
GLRStep vrStep = step;
PreprocessStepVR(&vrStep);
PerformRenderPass(vrStep, renderCount == 1, renderCount == totalRenderCount);
@@ -1603,6 +1603,9 @@ void GLQueueRunner::PerformBindFramebufferAsRenderTarget(const GLRStep &pass) {
fbo_bind_fb_target(false, curFB_->handle);
} else {
fbo_unbind();
+ if (IsVREnabled()) {
+ BindVRFramebuffer();
+ }
// Backbuffer is now bound.
}
CHECK_GL_ERROR_IF_DEBUG();
@@ -1755,10 +1758,6 @@ void GLQueueRunner::fbo_unbind() {
bindDefaultFBO();
#endif
- if (IsVRBuild()) {
- BindVRFramebuffer();
- }
-
currentDrawHandle_ = 0;
currentReadHandle_ = 0;
CHECK_GL_ERROR_IF_DEBUG();
diff --git a/Common/GPU/OpenGL/GLQueueRunner.h b/Common/GPU/OpenGL/GLQueueRunner.h
index 1e009787ae..e9ad214eb4 100644
--- a/Common/GPU/OpenGL/GLQueueRunner.h
+++ b/Common/GPU/OpenGL/GLQueueRunner.h
@@ -363,7 +363,7 @@ public:
void RunInitSteps(const std::vector &steps, bool skipGLCalls);
- void RunSteps(const std::vector &steps, bool skipGLCalls, bool keepSteps = false);
+ void RunSteps(const std::vector &steps, bool skipGLCalls, bool keepSteps, bool useVR);
void LogSteps(const std::vector &steps);
void CreateDeviceObjects();
diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp
index 01cccb8874..f66b9b78fc 100644
--- a/Common/GPU/OpenGL/GLRenderManager.cpp
+++ b/Common/GPU/OpenGL/GLRenderManager.cpp
@@ -5,8 +5,6 @@
#include "Common/Thread/ThreadUtil.h"
#include "Common/VR/PPSSPPVR.h"
-#include "Core/Config.h"
-
#include "Common/Log.h"
#include "Common/MemoryUtil.h"
#include "Common/Math/math_util.h"
@@ -356,9 +354,13 @@ void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRende
step->dependencies.insert(fb);
}
}
+
+ if (invalidationCallback_) {
+ invalidationCallback_(InvalidationCallbackFlags::RENDER_PASS_STATE);
+ }
}
-void GLRenderManager::BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, int aspectBit, int attachment) {
+void GLRenderManager::BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, int aspectBit) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(binding < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::BIND_FB_TEXTURE };
@@ -482,7 +484,6 @@ void GLRenderManager::BeginFrame() {
// In GL, we have to do deletes on the submission thread.
insideFrame_ = true;
- renderStepOffset_ = 0;
}
void GLRenderManager::Finish() {
@@ -579,18 +580,15 @@ void GLRenderManager::Run(int frame) {
}
}
- if (IsVRBuild()) {
- int passes = 1;
- if (!IsMultiviewSupported() && g_Config.bEnableStereo) {
- passes = 2;
- }
+ if (IsVREnabled()) {
+ int passes = GetVRPassesCount();
for (int i = 0; i < passes; i++) {
PreVRFrameRender(i);
- queueRunner_.RunSteps(stepsOnThread, skipGLCalls_, i < passes - 1);
+ queueRunner_.RunSteps(stepsOnThread, skipGLCalls_, i < passes - 1, true);
PostVRFrameRender();
}
} else {
- queueRunner_.RunSteps(stepsOnThread, skipGLCalls_);
+ queueRunner_.RunSteps(stepsOnThread, skipGLCalls_, false, false);
}
stepsOnThread.clear();
@@ -617,9 +615,6 @@ void GLRenderManager::Run(int frame) {
}
void GLRenderManager::FlushSync() {
- // TODO: Reset curRenderStep_?
- renderStepOffset_ += (int)steps_.size();
-
int curFrame = curFrame_;
FrameData &frameData = frameData_[curFrame];
{
diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h
index 51d7e1e9eb..52ac971cec 100644
--- a/Common/GPU/OpenGL/GLRenderManager.h
+++ b/Common/GPU/OpenGL/GLRenderManager.h
@@ -10,6 +10,7 @@
#include
#include "Common/GPU/OpenGL/GLCommon.h"
+#include "Common/GPU/MiscTypes.h"
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Log.h"
#include "GLQueueRunner.h"
@@ -98,12 +99,23 @@ struct GLRProgramFlags {
bool useClipDistance2 : 1;
};
+// Unless you manage lifetimes in some smart way,
+// your loc data for uniforms and samplers need to be in a struct
+// derived from this, and passed into CreateProgram.
+class GLRProgramLocData {
+public:
+ virtual ~GLRProgramLocData() {}
+};
+
class GLRProgram {
public:
~GLRProgram() {
if (program) {
glDeleteProgram(program);
}
+ if (locData_) {
+ delete locData_;
+ }
}
struct Semantic {
int location;
@@ -126,6 +138,8 @@ public:
std::vector semantics_;
std::vector queries_;
std::vector initialize_;
+
+ GLRProgramLocData *locData_;
bool use_clip_distance[8]{};
struct UniformInfo {
@@ -147,6 +161,7 @@ public:
}
return loc;
}
+
std::unordered_map uniformCache_;
};
@@ -368,6 +383,9 @@ public:
GLRenderManager() {}
~GLRenderManager();
+ void SetInvalidationCallback(InvalidationCallback callback) {
+ invalidationCallback_ = callback;
+ }
void SetErrorCallback(ErrorCallbackFn callback, void *userdata) {
queueRunner_.SetErrorCallback(callback, userdata);
}
@@ -434,13 +452,14 @@ public:
// not be an active render pass.
GLRProgram *CreateProgram(
std::vector shaders, std::vector semantics, std::vector queries,
- std::vector initializers, const GLRProgramFlags &flags) {
+ std::vector initializers, GLRProgramLocData *locData, const GLRProgramFlags &flags) {
GLRInitStep step{ GLRInitStepType::CREATE_PROGRAM };
_assert_(shaders.size() <= ARRAY_SIZE(step.create_program.shaders));
step.create_program.program = new GLRProgram();
step.create_program.program->semantics_ = semantics;
step.create_program.program->queries_ = queries;
step.create_program.program->initialize_ = initializers;
+ step.create_program.program->locData_ = locData;
step.create_program.program->use_clip_distance[0] = flags.useClipDistance0;
step.create_program.program->use_clip_distance[1] = flags.useClipDistance1;
step.create_program.program->use_clip_distance[2] = flags.useClipDistance2;
@@ -462,7 +481,7 @@ public:
return step.create_program.program;
}
- GLRInputLayout *CreateInputLayout(std::vector &entries) {
+ GLRInputLayout *CreateInputLayout(const std::vector &entries) {
GLRInitStep step{ GLRInitStepType::CREATE_INPUT_LAYOUT };
step.create_input_layout.inputLayout = new GLRInputLayout();
step.create_input_layout.inputLayout->entries = entries;
@@ -526,7 +545,7 @@ public:
void BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRenderPassAction color, GLRRenderPassAction depth, GLRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag);
// Binds a framebuffer as a texture, for the following draws.
- void BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, int aspectBit, int attachment);
+ void BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, int aspectBit);
bool CopyFramebufferToMemorySync(GLRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag);
void CopyImageToMemorySync(GLRTexture *texture, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag);
@@ -980,12 +999,6 @@ public:
skipGLCalls_ = true;
}
- // Gets a frame-unique ID of the current step being recorded. Can be used to figure out
- // when the current step has changed, which means the caller will need to re-record its state.
- int GetCurrentStepId() const {
- return renderStepOffset_ + (int)steps_.size();
- }
-
private:
void BeginSubmitFrame(int frame);
void EndSubmitFrame(int frame);
@@ -1033,8 +1046,7 @@ private:
// Submission time state
bool insideFrame_ = false;
- // This is the offset within this frame, in case of a mid-frame sync.
- int renderStepOffset_ = 0;
+
GLRStep *curRenderStep_ = nullptr;
std::vector steps_;
std::vector initSteps_;
@@ -1074,4 +1086,6 @@ private:
GLRProgram *curProgram_ = nullptr;
#endif
Draw::DeviceCaps caps_{};
+
+ InvalidationCallback invalidationCallback_;
};
diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp
index bb937379ef..b7737bd919 100644
--- a/Common/GPU/OpenGL/thin3d_gl.cpp
+++ b/Common/GPU/OpenGL/thin3d_gl.cpp
@@ -10,6 +10,8 @@
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Math/math_util.h"
#include "Common/Math/lin/matrix4x4.h"
+#include "Common/System/System.h"
+#include "Common/Log.h"
#include "Common/GPU/thin3d.h"
#include "Common/GPU/Shader.h"
#include "Common/GPU/OpenGL/DataFormatGL.h"
@@ -249,12 +251,21 @@ bool OpenGLShaderModule::Compile(GLRenderManager *render, ShaderLanguage languag
if (source_.find("#version") == source_.npos) {
source_ = ApplyGLSLPrelude(source_, glstage_);
}
+ } else {
+ // Unsupported shader type
+ return false;
}
shader_ = render->CreateShader(glstage_, source_, tag_);
+ _assert_(shader_ != nullptr); // normally can't fail since we defer creation, unless there's a memory error or similar.
return true;
}
+struct PipelineLocData : GLRProgramLocData {
+ GLint samplerLocs_[MAX_TEXTURE_SLOTS]{};
+ std::vector dynamicUniformLocs_;
+};
+
class OpenGLInputLayout : public InputLayout {
public:
OpenGLInputLayout(GLRenderManager *render) : render_(render) {}
@@ -276,10 +287,13 @@ public:
for (auto &iter : shaders) {
iter->Release();
}
- if (program_) render_->DeleteProgram(program_);
+ if (program_) {
+ render_->DeleteProgram(program_);
+ }
+ // DO NOT delete locs_ here, it's deleted by the render manager.
}
- bool LinkShaders();
+ bool LinkShaders(const PipelineDesc &desc);
GLuint prim = 0;
std::vector shaders;
@@ -288,12 +302,14 @@ public:
AutoRef blend;
AutoRef raster;
+ // Not owned!
+ PipelineLocData *locs_ = nullptr;
+
// TODO: Optimize by getting the locations first and putting in a custom struct
UniformBufferDesc dynamicUniforms;
- GLint samplerLocs_[MAX_TEXTURE_SLOTS]{};
- std::vector dynamicUniformLocs_;
GLRProgram *program_ = nullptr;
+
// Allow using other sampler names than sampler0, sampler1 etc in shaders.
// If not set, will default to those, though.
Slice samplers_;
@@ -308,7 +324,7 @@ class OpenGLTexture;
class OpenGLContext : public DrawContext {
public:
OpenGLContext();
- virtual ~OpenGLContext();
+ ~OpenGLContext();
void SetTargetSize(int w, int h) override {
DrawContext::SetTargetSize(w, h);
@@ -355,10 +371,7 @@ public:
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
- Framebuffer *GetCurrentRenderTarget() override {
- return curRenderTarget_;
- }
- void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
+ void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@@ -400,7 +413,7 @@ public:
curPipeline_->depthStencil->stencilPass);
}
- void BindTextures(int start, int count, Texture **textures) override;
+ void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int sampler, void *nativeTexture) override;
void BindPipeline(Pipeline *pipeline) override;
@@ -462,11 +475,11 @@ public:
void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override {}
- int GetCurrentStepId() const override {
- return renderManager_.GetCurrentStepId();
- }
+ void Invalidate(InvalidationFlags flags) override;
- void InvalidateCachedState() override;
+ void SetInvalidationCallback(InvalidationCallback callback) override {
+ renderManager_.SetInvalidationCallback(callback);
+ }
private:
void ApplySamplers();
@@ -504,7 +517,7 @@ static constexpr int MakeIntelSimpleVer(int v1, int v2, int v3) {
return (v1 << 16) | (v2 << 8) | v3;
}
-static bool HasIntelDualSrcBug(int versions[4]) {
+static bool HasIntelDualSrcBug(const int versions[4]) {
// Intel uses a confusing set of at least 3 version numbering schemes. This is the one given to OpenGL.
switch (MakeIntelSimpleVer(versions[0], versions[1], versions[2])) {
case MakeIntelSimpleVer(9, 17, 10):
@@ -521,7 +534,6 @@ static bool HasIntelDualSrcBug(int versions[4]) {
}
OpenGLContext::OpenGLContext() {
- // TODO: Detect more caps
if (gl_extensions.IsGLES) {
if (gl_extensions.OES_packed_depth_stencil || gl_extensions.OES_depth24) {
caps_.preferredDepthBufferFormat = DataFormat::D24_S8;
@@ -553,6 +565,7 @@ OpenGLContext::OpenGLContext() {
caps_.framebufferStencilBlitSupported = caps_.framebufferBlitSupported;
caps_.depthClampSupported = gl_extensions.ARB_depth_clamp || gl_extensions.EXT_depth_clamp;
caps_.blendMinMaxSupported = gl_extensions.EXT_blend_minmax;
+ caps_.multiSampleLevelsMask = 1; // More could be supported with some work.
if (gl_extensions.IsGLES) {
caps_.clipDistanceSupported = gl_extensions.EXT_clip_cull_distance || gl_extensions.APPLE_clip_distance;
@@ -572,6 +585,7 @@ OpenGLContext::OpenGLContext() {
} else {
caps_.fragmentShaderDepthWriteSupported = true;
}
+ caps_.fragmentShaderStencilWriteSupported = gl_extensions.ARB_shader_stencil_export;
// GLES has no support for logic framebuffer operations. There doesn't even seem to exist any such extensions.
caps_.logicOpSupported = !gl_extensions.IsGLES;
@@ -596,6 +610,18 @@ OpenGLContext::OpenGLContext() {
caps_.vendor = GPUVendor::VENDOR_UNKNOWN;
break;
}
+
+ // Hide D3D9 when we know it likely won't work well.
+#if PPSSPP_PLATFORM(WINDOWS)
+ caps_.supportsD3D9 = true;
+ if (!strcmp(gl_extensions.model, "Intel(R) Iris(R) Xe Graphics")) {
+ caps_.supportsD3D9 = false;
+ }
+#endif
+
+ // Very rough heuristic!
+ caps_.isTilingGPU = gl_extensions.IsGLES && caps_.vendor != GPUVendor::VENDOR_NVIDIA && caps_.vendor != GPUVendor::VENDOR_INTEL;
+
for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) {
frameData_[i].push = renderManager_.CreatePushBuffer(i, GL_ARRAY_BUFFER, 64 * 1024);
}
@@ -642,6 +668,15 @@ OpenGLContext::OpenGLContext() {
bugs_.Infest(Bugs::PVR_GENMIPMAP_HEIGHT_GREATER);
}
+ if (caps_.vendor == GPUVendor::VENDOR_QUALCOMM) {
+#if PPSSPP_PLATFORM(ANDROID)
+ // The bug seems to affect Adreno 3xx and 5xx, and appeared in Android 8.0 Oreo, so API 26.
+ // See https://github.com/hrydgard/ppsspp/issues/16015#issuecomment-1328316080.
+ if (gl_extensions.modelNumber < 600 && System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 26)
+ bugs_.Infest(Bugs::ADRENO_RESOURCE_DEADLOCK);
+#endif
+ }
+
#if PPSSPP_PLATFORM(IOS)
// For some reason, this bug does not appear on M1.
if (caps_.vendor == GPUVendor::VENDOR_APPLE) {
@@ -747,18 +782,20 @@ void OpenGLContext::EndFrame() {
renderManager_.EndPushBuffer(frameData.push); // upload the data!
renderManager_.Finish();
- InvalidateCachedState();
+ Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
}
-void OpenGLContext::InvalidateCachedState() {
- // Unbind stuff.
- for (auto &texture : boundTextures_) {
- texture = nullptr;
+void OpenGLContext::Invalidate(InvalidationFlags flags) {
+ if (flags & InvalidationFlags::CACHED_RENDER_STATE) {
+ // Unbind stuff.
+ for (auto &texture : boundTextures_) {
+ texture = nullptr;
+ }
+ for (auto &sampler : boundSamplers_) {
+ sampler = nullptr;
+ }
+ curPipeline_ = nullptr;
}
- for (auto &sampler : boundSamplers_) {
- sampler = nullptr;
- }
- curPipeline_ = nullptr;
}
InputLayout *OpenGLContext::CreateInputLayout(const InputLayoutDesc &desc) {
@@ -1055,7 +1092,7 @@ public:
buffer_ = render->CreateBuffer(target_, size, usage_);
totalSize_ = size;
}
- ~OpenGLBuffer() override {
+ ~OpenGLBuffer() {
render_->DeleteBuffer(buffer_);
}
@@ -1112,10 +1149,10 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc, const
}
if (desc.uniformDesc) {
pipeline->dynamicUniforms = *desc.uniformDesc;
- pipeline->dynamicUniformLocs_.resize(desc.uniformDesc->uniforms.size());
}
+
pipeline->samplers_ = desc.samplers;
- if (pipeline->LinkShaders()) {
+ if (pipeline->LinkShaders(desc)) {
// Build the rest of the virtual pipeline object.
pipeline->prim = primToGL[(int)desc.prim];
pipeline->depthStencil = (OpenGLDepthStencilState *)desc.depthStencil;
@@ -1130,7 +1167,7 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc, const
}
}
-void OpenGLContext::BindTextures(int start, int count, Texture **textures) {
+void OpenGLContext::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_TEXTURE_SLOTS);
for (int i = start; i < start + count; i++) {
OpenGLTexture *glTex = static_cast(textures[i - start]);
@@ -1185,7 +1222,7 @@ ShaderModule *OpenGLContext::CreateShaderModule(ShaderStage stage, ShaderLanguag
}
}
-bool OpenGLPipeline::LinkShaders() {
+bool OpenGLPipeline::LinkShaders(const PipelineDesc &desc) {
std::vector linkShaders;
for (auto shaderModule : shaders) {
if (shaderModule) {
@@ -1216,32 +1253,38 @@ bool OpenGLPipeline::LinkShaders() {
semantics.push_back({ SEM_POSITION, "a_position" });
semantics.push_back({ SEM_TEXCOORD0, "a_texcoord0" });
+ locs_ = new PipelineLocData();
+ locs_->dynamicUniformLocs_.resize(desc.uniformDesc->uniforms.size());
+
std::vector queries;
+ int samplersToCheck;
if (!samplers_.is_empty()) {
for (int i = 0; i < (int)std::min((const uint32_t)samplers_.size(), MAX_TEXTURE_SLOTS); i++) {
- queries.push_back({ &samplerLocs_[i], samplers_[i].name, true });
+ queries.push_back({ &locs_->samplerLocs_[i], samplers_[i].name, true });
}
+ samplersToCheck = (int)samplers_.size();
} else {
- queries.push_back({ &samplerLocs_[0], "sampler0" });
- queries.push_back({ &samplerLocs_[1], "sampler1" });
- queries.push_back({ &samplerLocs_[2], "sampler2" });
+ queries.push_back({ &locs_->samplerLocs_[0], "sampler0" });
+ queries.push_back({ &locs_->samplerLocs_[1], "sampler1" });
+ queries.push_back({ &locs_->samplerLocs_[2], "sampler2" });
+ samplersToCheck = 3;
}
_assert_(queries.size() <= MAX_TEXTURE_SLOTS);
for (size_t i = 0; i < dynamicUniforms.uniforms.size(); ++i) {
- queries.push_back({ &dynamicUniformLocs_[i], dynamicUniforms.uniforms[i].name });
+ queries.push_back({ &locs_->dynamicUniformLocs_[i], dynamicUniforms.uniforms[i].name });
}
std::vector initialize;
for (int i = 0; i < MAX_TEXTURE_SLOTS; ++i) {
- if (i < (int)samplers_.size()) {
- initialize.push_back({ &samplerLocs_[i], 0, i });
+ if (i < samplersToCheck) {
+ initialize.push_back({ &locs_->samplerLocs_[i], 0, i });
} else {
- samplerLocs_[i] = -1;
+ locs_->samplerLocs_[i] = -1;
}
}
GLRProgramFlags flags{};
- program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, flags);
+ program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, locs_, flags);
return true;
}
@@ -1263,7 +1306,7 @@ void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
for (size_t i = 0; i < curPipeline_->dynamicUniforms.uniforms.size(); ++i) {
const auto &uniform = curPipeline_->dynamicUniforms.uniforms[i];
- const GLint &loc = curPipeline_->dynamicUniformLocs_[i];
+ const GLint &loc = curPipeline_->locs_->dynamicUniformLocs_[i];
const float *data = (const float *)((uint8_t *)ub + uniform.offset);
switch (uniform.type) {
case UniformType::FLOAT1:
@@ -1390,6 +1433,9 @@ void OpenGLInputLayout::Compile(const InputLayoutDesc &desc) {
Framebuffer *OpenGLContext::CreateFramebuffer(const FramebufferDesc &desc) {
CheckGLExtensions();
+ // TODO: Support multiview later. (It's our only use case for multi layers).
+ _dbg_assert_(desc.numLayers == 1);
+
GLRFramebuffer *framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil);
OpenGLFramebuffer *fbo = new OpenGLFramebuffer(&renderManager_, framebuffer);
return fbo;
@@ -1436,7 +1482,7 @@ bool OpenGLContext::BlitFramebuffer(Framebuffer *fbsrc, int srcX1, int srcY1, in
return true;
}
-void OpenGLContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int color) {
+void OpenGLContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) {
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo;
_assert_(binding < MAX_TEXTURE_SLOTS);
@@ -1453,7 +1499,7 @@ void OpenGLContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBCh
aspect |= GL_STENCIL_BUFFER_BIT;
boundTextures_[binding] = &fb->framebuffer_->z_stencil_texture;
}
- renderManager_.BindFramebufferAsTexture(fb->framebuffer_, binding, aspect, color);
+ renderManager_.BindFramebufferAsTexture(fb->framebuffer_, binding, aspect);
}
void OpenGLContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) {
@@ -1499,6 +1545,13 @@ uint32_t OpenGLContext::GetDataFormatSupport(DataFormat fmt) const {
case DataFormat::R8_UNORM:
return FMT_TEXTURE;
+ case DataFormat::R16_UNORM:
+ if (!gl_extensions.IsGLES) {
+ return FMT_TEXTURE;
+ } else {
+ return 0;
+ }
+ break;
case DataFormat::BC1_RGBA_UNORM_BLOCK:
case DataFormat::BC2_UNORM_BLOCK:
diff --git a/Common/GPU/Shader.h b/Common/GPU/Shader.h
index 75e1a9b2e9..6e29445734 100644
--- a/Common/GPU/Shader.h
+++ b/Common/GPU/Shader.h
@@ -4,9 +4,12 @@
#include
#include // for size_t
+#include "Common/Common.h"
+
// GLSL_1xx and GLSL_3xx each cover a lot of sub variants. All the little quirks
// that differ are covered in ShaderLanguageDesc.
// Defined as a bitmask so stuff like GetSupportedShaderLanguages can return combinations.
+// TODO: We can probably move away from this distinction soon, now that we mostly generate/translate shaders.
enum ShaderLanguage {
GLSL_1xx = 1,
GLSL_3xx = 2,
@@ -30,7 +33,6 @@ enum class ShaderStage {
const char *ShaderStageAsString(ShaderStage lang);
-
struct ShaderLanguageDesc {
ShaderLanguageDesc() {}
explicit ShaderLanguageDesc(ShaderLanguage lang);
@@ -91,12 +93,18 @@ struct UniformDef {
int index;
};
+enum class SamplerFlags {
+ ARRAY_ON_VULKAN = 1,
+};
+ENUM_CLASS_BITOPS(SamplerFlags);
+
struct SamplerDef {
+ int binding; // Might only be used by some backends.
const char *name;
+ SamplerFlags flags;
// TODO: Might need unsigned samplers, 3d samplers, or other types in the future.
};
-
// For passing error messages from shader compilation (and other critical issues) back to the host.
// This can run on any thread - be aware!
// TODO: See if we can find a less generic name for this.
diff --git a/Common/GPU/ShaderTranslation.cpp b/Common/GPU/ShaderTranslation.cpp
index 9391d1b7da..732ef68633 100644
--- a/Common/GPU/ShaderTranslation.cpp
+++ b/Common/GPU/ShaderTranslation.cpp
@@ -98,7 +98,7 @@ R"(#version 450
)";
static const char *vulkanUboDecl = R"(
-layout (std140, set = 0, binding = 0) uniform Data {
+layout (std140, set = 1, binding = 0) uniform Data {
vec2 u_texelDelta;
vec2 u_pixelDelta;
vec4 u_time;
@@ -187,11 +187,11 @@ bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetada
continue;
} else if (line.find("uniform sampler2D") == 0) {
if (sscanf(line.c_str(), "uniform sampler2D sampler%d", &num) == 1)
- line = StringFromFormat("layout(set = 0, binding = %d) ", num + 1) + line;
+ line = StringFromFormat("layout(set = 1, binding = %d) ", num + 1) + line;
else if (line.find("sampler0") != line.npos)
- line = "layout(set = 0, binding = 1) " + line;
+ line = "layout(set = 1, binding = 1) " + line;
else
- line = "layout(set = 0, binding = 2) " + line;
+ line = "layout(set = 1, binding = 2) " + line;
} else if (line.find("uniform ") != std::string::npos) {
continue;
} else if (2 == sscanf(line.c_str(), "varying vec%d v_texcoord%d;", &vecSize, &num)) {
diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp
index 7ca6deaeb0..01132b11de 100644
--- a/Common/GPU/ShaderWriter.cpp
+++ b/Common/GPU/ShaderWriter.cpp
@@ -7,7 +7,6 @@
#include "Common/Log.h"
const char * const vulkan_glsl_preamble_fs =
-"#version 450\n"
"#extension GL_ARB_separate_shader_objects : enable\n"
"#extension GL_ARB_shading_language_420pack : enable\n"
"#extension GL_ARB_conservative_depth : enable\n"
@@ -45,7 +44,6 @@ static const char * const hlsl_d3d9_preamble_fs =
"#define DISCARD_BELOW(x) clip(x)\n";
static const char * const vulkan_glsl_preamble_vs =
-"#version 450\n"
"#extension GL_ARB_separate_shader_objects : enable\n"
"#extension GL_ARB_shading_language_420pack : enable\n"
"#define mul(x, y) ((x) * (y))\n"
@@ -69,7 +67,6 @@ static const char * const hlsl_preamble_gs =
"\n";
static const char * const vulkan_glsl_preamble_gs =
-"#version 450\n"
"#extension GL_ARB_separate_shader_objects : enable\n"
"#extension GL_ARB_shading_language_420pack : enable\n"
"#define mul(x, y) ((x) * (y))\n"
@@ -113,9 +110,17 @@ ShaderWriter & ShaderWriter::F(const char *format, ...) {
return *this;
}
-void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions) {
+void ShaderWriter::Preamble(Slice extensions) {
switch (lang_.shaderLanguage) {
case GLSL_VULKAN:
+ C("#version 450\n");
+ if (flags_ & ShaderWriterFlags::FS_AUTO_STEREO) {
+ C("#extension GL_EXT_multiview : enable\n");
+ }
+ // IMPORTANT! Extensions must be the first thing after #version.
+ for (size_t i = 0; i < extensions.size(); i++) {
+ F("%s\n", extensions[i]);
+ }
switch (stage_) {
case ShaderStage::Vertex:
W(vulkan_glsl_preamble_vs);
@@ -154,8 +159,8 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions
default: // OpenGL
F("#version %d%s\n", lang_.glslVersionNumber, lang_.gles && lang_.glslES30 ? " es" : "");
// IMPORTANT! Extensions must be the first thing after #version.
- for (size_t i = 0; i < num_gl_extensions; i++) {
- F("%s\n", gl_extensions[i]);
+ for (size_t i = 0; i < extensions.size(); i++) {
+ F("%s\n", extensions[i]);
}
// Print some system info - useful to gather information directly from screenshots.
if (strlen(lang_.driverInfo) != 0) {
@@ -250,7 +255,7 @@ void ShaderWriter::BeginVSMain(Slice inputs, Slice uniform
}
}
-void ShaderWriter::BeginFSMain(Slice uniforms, Slice varyings, FSFlags flags) {
+void ShaderWriter::BeginFSMain(Slice uniforms, Slice varyings) {
_assert_(this->stage_ == ShaderStage::Fragment);
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
@@ -264,13 +269,13 @@ void ShaderWriter::BeginFSMain(Slice uniforms, Slice var
C("};\n");
}
- if (flags & FSFLAG_WRITEDEPTH) {
+ if (flags_ & ShaderWriterFlags::FS_WRITE_DEPTH) {
C("float gl_FragDepth;\n");
}
C("struct PS_OUT {\n");
C(" vec4 target : SV_Target0;\n");
- if (flags & FSFLAG_WRITEDEPTH) {
+ if (flags_ & ShaderWriterFlags::FS_WRITE_DEPTH) {
C(" float depth : SV_Depth;\n");
}
C("};\n");
@@ -285,14 +290,14 @@ void ShaderWriter::BeginFSMain(Slice uniforms, Slice var
F(") {\n");
C(" PS_OUT ps_out;\n");
- if (flags & FSFLAG_WRITEDEPTH) {
+ if (flags_ & ShaderWriterFlags::FS_WRITE_DEPTH) {
C(" float gl_FragDepth;\n");
}
break;
case HLSL_D3D9:
C("struct PS_OUT {\n");
C(" vec4 target : SV_Target0;\n");
- if (flags & FSFLAG_WRITEDEPTH) {
+ if (flags_ & ShaderWriterFlags::FS_WRITE_DEPTH) {
C(" float depth : DEPTH;\n");
}
C("};\n");
@@ -310,7 +315,7 @@ void ShaderWriter::BeginFSMain(Slice uniforms, Slice var
F(") {\n");
C(" PS_OUT ps_out;\n");
- if (flags & FSFLAG_WRITEDEPTH) {
+ if (flags_ & ShaderWriterFlags::FS_WRITE_DEPTH) {
C(" float gl_FragDepth;\n");
}
@@ -321,7 +326,7 @@ void ShaderWriter::BeginFSMain(Slice uniforms, Slice var
}
C("layout(location = 0, index = 0) out vec4 fragColor0;\n");
if (!uniforms.is_empty()) {
- C("layout(std140, set = 0, binding = 0) uniform bufferVals {\n");
+ C("layout(std140, set = 1, binding = 0) uniform bufferVals {\n");
for (auto &uniform : uniforms) {
F("%s %s;\n", uniform.type, uniform.name);
}
@@ -404,13 +409,13 @@ void ShaderWriter::EndVSMain(Slice varyings) {
C("}\n");
}
-void ShaderWriter::EndFSMain(const char *vec4_color_variable, FSFlags flags) {
+void ShaderWriter::EndFSMain(const char *vec4_color_variable) {
_assert_(this->stage_ == ShaderStage::Fragment);
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
case HLSL_D3D9:
F(" ps_out.target = %s;\n", vec4_color_variable);
- if (flags & FSFLAG_WRITEDEPTH) {
+ if (flags_ & ShaderWriterFlags::FS_WRITE_DEPTH) {
C(" ps_out.depth = gl_FragDepth;\n");
}
C(" return ps_out;\n");
@@ -454,34 +459,43 @@ void ShaderWriter::ConstFloat(const char *name, float value) {
void ShaderWriter::DeclareSamplers(Slice samplers) {
for (int i = 0; i < (int)samplers.size(); i++) {
- DeclareTexture2D(samplers[i].name, i);
- DeclareSampler2D(samplers[i].name, i);
+ DeclareTexture2D(samplers[i]);
+ DeclareSampler2D(samplers[i]);
}
+ samplerDefs_ = samplers;
}
-void ShaderWriter::DeclareTexture2D(const char *name, int binding) {
+void ShaderWriter::ApplySamplerMetadata(Slice samplers) {
+ samplerDefs_ = samplers;
+}
+
+void ShaderWriter::DeclareTexture2D(const SamplerDef &def) {
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
- F("Texture2D %s : register(t%d);\n", name, binding);
+ F("Texture2D %s : register(t%d);\n", def.name, def.binding);
break;
case HLSL_D3D9:
- F("sampler %s: register(s%d);\n", name, binding);
+ F("sampler %s: register(s%d);\n", def.name, def.binding);
break;
case GLSL_VULKAN:
- // In the thin3d descriptor set layout, textures start at 1 in set 0. Hence the +1.
- F("layout(set = 0, binding = %d) uniform sampler2D %s;\n", binding + 1, name);
+ // texBindingBase_ is used for the thin3d descriptor set layout, where they start at 1.
+ if (def.flags & SamplerFlags::ARRAY_ON_VULKAN) {
+ F("layout(set = 1, binding = %d) uniform sampler2DArray %s;\n", def.binding + texBindingBase_, def.name);
+ } else {
+ F("layout(set = 1, binding = %d) uniform sampler2D %s;\n", def.binding + texBindingBase_, def.name);
+ }
break;
default:
- F("uniform sampler2D %s;\n", name);
+ F("uniform sampler2D %s;\n", def.name);
break;
}
}
-void ShaderWriter::DeclareSampler2D(const char *name, int binding) {
+void ShaderWriter::DeclareSampler2D(const SamplerDef &def) {
// We only use separate samplers in HLSL D3D11, where we have no choice.
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
- F("SamplerState %sSamp : register(s%d);\n", name, binding);
+ F("SamplerState %sSamp : register(s%d);\n", def.name, def.binding);
break;
default:
break;
@@ -489,6 +503,7 @@ void ShaderWriter::DeclareSampler2D(const char *name, int binding) {
}
ShaderWriter &ShaderWriter::SampleTexture2D(const char *sampName, const char *uv) {
+ const SamplerDef *samp = GetSamplerDef(sampName);
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
F("%s.Sample(%sSamp, %s)", sampName, sampName, uv);
@@ -498,13 +513,20 @@ ShaderWriter &ShaderWriter::SampleTexture2D(const char *sampName, const char *uv
break;
default:
// Note: we ignore the sampler. make sure you bound samplers to the textures correctly.
- F("%s(%s, %s)", lang_.texture, sampName, uv);
+ if (samp && (samp->flags & SamplerFlags::ARRAY_ON_VULKAN) && lang_.shaderLanguage == GLSL_VULKAN) {
+ const char *index = (flags_ & ShaderWriterFlags::FS_AUTO_STEREO) ? "float(gl_ViewIndex)" : "0.0";
+ F("%s(%s, vec3(%s, %s))", lang_.texture, sampName, uv, index);
+ } else {
+ F("%s(%s, %s)", lang_.texture, sampName, uv);
+ }
break;
}
return *this;
}
ShaderWriter &ShaderWriter::SampleTexture2DOffset(const char *sampName, const char *uv, int offX, int offY) {
+ const SamplerDef *samp = GetSamplerDef(sampName);
+
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
F("%s.Sample(%sSamp, %s, int2(%d, %d))", sampName, sampName, uv, offX, offY);
@@ -515,13 +537,20 @@ ShaderWriter &ShaderWriter::SampleTexture2DOffset(const char *sampName, const ch
break;
default:
// Note: we ignore the sampler. make sure you bound samplers to the textures correctly.
- F("%sOffset(%s, %s, ivec2(%d, %d))", lang_.texture, sampName, uv, offX, offY);
+ if (samp && (samp->flags & SamplerFlags::ARRAY_ON_VULKAN) && lang_.shaderLanguage == GLSL_VULKAN) {
+ const char *index = (flags_ & ShaderWriterFlags::FS_AUTO_STEREO) ? "float(gl_ViewIndex)" : "0.0";
+ F("%sOffset(%s, vec3(%s, %s), ivec2(%d, %d))", lang_.texture, sampName, uv, index, offX, offY);
+ } else {
+ F("%sOffset(%s, %s, ivec2(%d, %d))", lang_.texture, sampName, uv, offX, offY);
+ }
break;
}
return *this;
}
ShaderWriter &ShaderWriter::LoadTexture2D(const char *sampName, const char *uv, int level) {
+ const SamplerDef *samp = GetSamplerDef(sampName);
+
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
F("%s.Load(ivec3(%s, %d))", sampName, uv, level);
@@ -532,7 +561,12 @@ ShaderWriter &ShaderWriter::LoadTexture2D(const char *sampName, const char *uv,
break;
default:
// Note: we ignore the sampler. make sure you bound samplers to the textures correctly.
- F("texelFetch(%s, %s, %d)", sampName, uv, level);
+ if (samp && (samp->flags & SamplerFlags::ARRAY_ON_VULKAN) && lang_.shaderLanguage == GLSL_VULKAN) {
+ const char *index = (flags_ & ShaderWriterFlags::FS_AUTO_STEREO) ? "gl_ViewIndex" : "0";
+ F("texelFetch(%s, vec3(%s, %s), %d)", sampName, uv, index, level);
+ } else {
+ F("texelFetch(%s, %s, %d)", sampName, uv, level);
+ }
break;
}
return *this;
@@ -553,3 +587,12 @@ ShaderWriter &ShaderWriter::GetTextureSize(const char *szVariable, const char *t
}
return *this;
}
+
+const SamplerDef *ShaderWriter::GetSamplerDef(const char *name) const {
+ for (int i = 0; i < (int)samplerDefs_.size(); i++) {
+ if (!strcmp(samplerDefs_[i].name, name)) {
+ return &samplerDefs_[i];
+ }
+ }
+ return nullptr;
+}
diff --git a/Common/GPU/ShaderWriter.h b/Common/GPU/ShaderWriter.h
index 50f3bb1f0d..379e704319 100644
--- a/Common/GPU/ShaderWriter.h
+++ b/Common/GPU/ShaderWriter.h
@@ -30,15 +30,19 @@ struct VaryingDef {
const char *precision;
};
-enum FSFlags {
- FSFLAG_NONE = 0,
- FSFLAG_WRITEDEPTH = 1,
+enum class ShaderWriterFlags {
+ NONE = 0,
+ FS_WRITE_DEPTH = 1,
+ FS_AUTO_STEREO = 2, // Automatically indexes makes samplers tagged with `array` by gl_ViewIndex. Useful for stereo rendering.
};
+ENUM_CLASS_BITOPS(ShaderWriterFlags);
class ShaderWriter {
public:
- ShaderWriter(char *buffer, const ShaderLanguageDesc &lang, ShaderStage stage, const char **gl_extensions = nullptr, size_t num_gl_extensions = 0) : p_(buffer), lang_(lang), stage_(stage) {
- Preamble(gl_extensions, num_gl_extensions);
+ // Extensions are supported for both OpenGL ES and Vulkan (though of course, they're different).
+ ShaderWriter(char *buffer, const ShaderLanguageDesc &lang, ShaderStage stage, Slice extensions = Slice(), ShaderWriterFlags flags = ShaderWriterFlags::NONE) : p_(buffer), lang_(lang), stage_(stage), flags_(flags) {
+ buffer[0] = '\0';
+ Preamble(extensions);
}
ShaderWriter(const ShaderWriter &) = delete;
@@ -73,9 +77,16 @@ public:
void HighPrecisionFloat();
void LowPrecisionFloat();
+ // NOTE: samplers must live for the rest of ShaderWriter's lifetime. No way to express that in C++ though :(
void DeclareSamplers(Slice samplers);
+ // Same as DeclareSamplers, but doesn't actually declare them.
+ // This is currently only required by FragmentShaderGenerator.
+ void ApplySamplerMetadata(Slice samplers);
+
void ConstFloat(const char *name, float value);
+ void SetFlags(ShaderWriterFlags flags) { flags_ |= flags; }
+ void SetTexBindingBase(int base) { texBindingBase_ = base; }
ShaderWriter &SampleTexture2D(const char *texName, const char *uv);
ShaderWriter &SampleTexture2DOffset(const char *texName, const char *uv, int offX, int offY);
@@ -84,12 +95,12 @@ public:
// Simple shaders with no special tricks.
void BeginVSMain(Slice inputs, Slice uniforms, Slice varyings);
- void BeginFSMain(Slice uniforms, Slice varyings, FSFlags flags);
+ void BeginFSMain(Slice uniforms, Slice varyings);
void BeginGSMain(Slice varyings, Slice outVaryings);
// For simple shaders that output a single color, we can deal with this generically.
void EndVSMain(Slice varyings);
- void EndFSMain(const char *vec4_color_variable, FSFlags flags);
+ void EndFSMain(const char *vec4_color_variable);
void EndGSMain();
const ShaderLanguageDesc &Lang() const {
@@ -107,12 +118,17 @@ public:
private:
// Several of the shader languages ignore samplers, beware of that.
- void DeclareSampler2D(const char *name, int binding);
- void DeclareTexture2D(const char *name, int binding);
+ void DeclareSampler2D(const SamplerDef &def);
+ void DeclareTexture2D(const SamplerDef &def);
+ const SamplerDef *GetSamplerDef(const char *name) const;
- void Preamble(const char **gl_extensions, size_t num_gl_extensions);
+ void Preamble(Slice extensions);
char *p_;
const ShaderLanguageDesc &lang_;
const ShaderStage stage_;
+ Slice samplers_;
+ ShaderWriterFlags flags_ = ShaderWriterFlags::NONE;
+ Slice samplerDefs_;
+ int texBindingBase_ = 1;
};
diff --git a/Common/GPU/Vulkan/VulkanBarrier.cpp b/Common/GPU/Vulkan/VulkanBarrier.cpp
index e4f2d09089..d47f506845 100644
--- a/Common/GPU/Vulkan/VulkanBarrier.cpp
+++ b/Common/GPU/Vulkan/VulkanBarrier.cpp
@@ -9,4 +9,5 @@ void VulkanBarrier::Flush(VkCommandBuffer cmd) {
imageBarriers_.clear();
srcStageMask_ = 0;
dstStageMask_ = 0;
+ dependencyFlags_ = 0;
}
diff --git a/Common/GPU/Vulkan/VulkanBarrier.h b/Common/GPU/Vulkan/VulkanBarrier.h
index 19edc548ba..1fefb5adfa 100644
--- a/Common/GPU/Vulkan/VulkanBarrier.h
+++ b/Common/GPU/Vulkan/VulkanBarrier.h
@@ -14,7 +14,7 @@ class VulkanContext;
class VulkanBarrier {
public:
void TransitionImage(
- VkImage image, int baseMip, int numMipLevels, VkImageAspectFlags aspectMask,
+ VkImage image, int baseMip, int numMipLevels, int numLayers, VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout, VkImageLayout newImageLayout,
VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask,
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask
@@ -36,7 +36,7 @@ public:
imageBarrier.subresourceRange.aspectMask = aspectMask;
imageBarrier.subresourceRange.baseMipLevel = baseMip;
imageBarrier.subresourceRange.levelCount = numMipLevels;
- imageBarrier.subresourceRange.layerCount = 1; // We never use more than one layer, and old Mali drivers have problems with VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS.
+ imageBarrier.subresourceRange.layerCount = numLayers; // NOTE: We could usually use VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS, but really old Mali drivers have problems with those.
imageBarrier.subresourceRange.baseArrayLayer = 0;
imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@@ -46,7 +46,7 @@ public:
// Automatically determines access and stage masks from layouts.
// Not universally usable, but works for PPSSPP's use.
void TransitionImageAuto(
- VkImage image, int baseMip, int numMipLevels, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout
+ VkImage image, int baseMip, int numMipLevels, int numLayers, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout
) {
_dbg_assert_(image != VK_NULL_HANDLE);
@@ -104,7 +104,7 @@ public:
imageBarrier.subresourceRange.aspectMask = aspectMask;
imageBarrier.subresourceRange.baseMipLevel = baseMip;
imageBarrier.subresourceRange.levelCount = numMipLevels;
- imageBarrier.subresourceRange.layerCount = 1; // We never use more than one layer, and old Mali drivers have problems with VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS.
+ imageBarrier.subresourceRange.layerCount = numLayers; // NOTE: We could usually use VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS, but really old Mali drivers have problems with those.
imageBarrier.subresourceRange.baseArrayLayer = 0;
imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp
index 937276d10f..8e523d0bf2 100644
--- a/Common/GPU/Vulkan/VulkanContext.cpp
+++ b/Common/GPU/Vulkan/VulkanContext.cpp
@@ -57,7 +57,7 @@ std::string VulkanVendorString(uint32_t vendorId) {
case VULKAN_VENDOR_ARM: return "ARM";
case VULKAN_VENDOR_QUALCOMM: return "Qualcomm";
case VULKAN_VENDOR_IMGTEC: return "Imagination";
-
+ case VULKAN_VENDOR_APPLE: return "Apple";
default:
return StringFromFormat("%08x", vendorId);
}
@@ -253,15 +253,21 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) {
VkPhysicalDeviceProperties2 props2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
VkPhysicalDevicePushDescriptorPropertiesKHR pushProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR};
VkPhysicalDeviceExternalMemoryHostPropertiesEXT extHostMemProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT};
+ VkPhysicalDeviceDepthStencilResolveProperties depthStencilResolveProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES};
+
props2.pNext = &pushProps;
pushProps.pNext = &extHostMemProps;
+ extHostMemProps.pNext = &depthStencilResolveProps;
vkGetPhysicalDeviceProperties2KHR(physical_devices_[i], &props2);
// Don't want bad pointers sitting around.
props2.pNext = nullptr;
pushProps.pNext = nullptr;
+ extHostMemProps.pNext = nullptr;
+ depthStencilResolveProps.pNext = nullptr;
physicalDeviceProperties_[i].properties = props2.properties;
physicalDeviceProperties_[i].pushDescriptorProperties = pushProps;
physicalDeviceProperties_[i].externalMemoryHostProperties = extHostMemProps;
+ physicalDeviceProperties_[i].depthStencilResolve = depthStencilResolveProps;
}
} else {
for (uint32_t i = 0; i < gpu_count; i++) {
@@ -329,7 +335,7 @@ bool VulkanContext::MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirem
for (uint32_t i = 0; i < 32; i++) {
if ((typeBits & 1) == 1) {
// Type is available, does it match user properties?
- if ((memory_properties.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) {
+ if ((memory_properties_.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) {
*typeIndex = i;
return true;
}
@@ -569,38 +575,48 @@ void VulkanContext::ChooseDevice(int physical_device) {
}
// This is as good a place as any to do this.
- vkGetPhysicalDeviceMemoryProperties(physical_devices_[physical_device_], &memory_properties);
- INFO_LOG(G3D, "Memory Types (%d):", memory_properties.memoryTypeCount);
- for (int i = 0; i < (int)memory_properties.memoryTypeCount; i++) {
+ vkGetPhysicalDeviceMemoryProperties(physical_devices_[physical_device_], &memory_properties_);
+ INFO_LOG(G3D, "Memory Types (%d):", memory_properties_.memoryTypeCount);
+ for (int i = 0; i < (int)memory_properties_.memoryTypeCount; i++) {
// Don't bother printing dummy memory types.
- if (!memory_properties.memoryTypes[i].propertyFlags)
+ if (!memory_properties_.memoryTypes[i].propertyFlags)
continue;
- INFO_LOG(G3D, " %d: Heap %d; Flags: %s%s%s%s ", i, memory_properties.memoryTypes[i].heapIndex,
- (memory_properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ? "DEVICE_LOCAL " : "",
- (memory_properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ? "HOST_VISIBLE " : "",
- (memory_properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) ? "HOST_CACHED " : "",
- (memory_properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) ? "HOST_COHERENT " : "");
+ INFO_LOG(G3D, " %d: Heap %d; Flags: %s%s%s%s ", i, memory_properties_.memoryTypes[i].heapIndex,
+ (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ? "DEVICE_LOCAL " : "",
+ (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ? "HOST_VISIBLE " : "",
+ (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) ? "HOST_CACHED " : "",
+ (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) ? "HOST_COHERENT " : "");
}
// Optional features
if (extensionsLookup_.KHR_get_physical_device_properties2) {
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR};
+ // Add to chain even if not supported, GetPhysicalDeviceFeatures is supposed to ignore unknown structs.
+ VkPhysicalDeviceMultiviewFeatures multiViewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
+ features2.pNext = &multiViewFeatures;
vkGetPhysicalDeviceFeatures2KHR(physical_devices_[physical_device_], &features2);
- deviceFeatures_.available = features2.features;
+ deviceFeatures_.available.standard = features2.features;
+ deviceFeatures_.available.multiview = multiViewFeatures;
} else {
- vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available);
+ vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard);
+ deviceFeatures_.available.multiview = {};
}
deviceFeatures_.enabled = {};
// Enable a few safe ones if they are available.
- deviceFeatures_.enabled.dualSrcBlend = deviceFeatures_.available.dualSrcBlend;
- deviceFeatures_.enabled.logicOp = deviceFeatures_.available.logicOp;
- deviceFeatures_.enabled.depthClamp = deviceFeatures_.available.depthClamp;
- deviceFeatures_.enabled.depthBounds = deviceFeatures_.available.depthBounds;
- deviceFeatures_.enabled.samplerAnisotropy = deviceFeatures_.available.samplerAnisotropy;
- deviceFeatures_.enabled.shaderClipDistance = deviceFeatures_.available.shaderClipDistance;
- deviceFeatures_.enabled.shaderCullDistance = deviceFeatures_.available.shaderCullDistance;
- deviceFeatures_.enabled.geometryShader = deviceFeatures_.available.geometryShader;
+ deviceFeatures_.enabled.standard.dualSrcBlend = deviceFeatures_.available.standard.dualSrcBlend;
+ deviceFeatures_.enabled.standard.logicOp = deviceFeatures_.available.standard.logicOp;
+ deviceFeatures_.enabled.standard.depthClamp = deviceFeatures_.available.standard.depthClamp;
+ deviceFeatures_.enabled.standard.depthBounds = deviceFeatures_.available.standard.depthBounds;
+ deviceFeatures_.enabled.standard.samplerAnisotropy = deviceFeatures_.available.standard.samplerAnisotropy;
+ deviceFeatures_.enabled.standard.shaderClipDistance = deviceFeatures_.available.standard.shaderClipDistance;
+ deviceFeatures_.enabled.standard.shaderCullDistance = deviceFeatures_.available.standard.shaderCullDistance;
+ deviceFeatures_.enabled.standard.geometryShader = deviceFeatures_.available.standard.geometryShader;
+ deviceFeatures_.enabled.standard.sampleRateShading = deviceFeatures_.available.standard.sampleRateShading;
+
+ deviceFeatures_.enabled.multiview = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
+ deviceFeatures_.enabled.multiview.multiview = deviceFeatures_.available.multiview.multiview;
+ // deviceFeatures_.enabled.multiview.multiviewGeometryShader = deviceFeatures_.available.multiview.multiviewGeometryShader;
GetDeviceLayerExtensionList(nullptr, device_extension_properties_);
@@ -671,6 +687,8 @@ VkResult VulkanContext::CreateDevice() {
extensionsLookup_.EXT_fragment_shader_interlock = EnableDeviceExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME);
extensionsLookup_.ARM_rasterization_order_attachment_access = EnableDeviceExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME);
+ VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
+
VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
device_info.queueCreateInfoCount = 1;
device_info.pQueueCreateInfos = &queue_info;
@@ -678,7 +696,14 @@ VkResult VulkanContext::CreateDevice() {
device_info.ppEnabledLayerNames = device_info.enabledLayerCount ? device_layer_names_.data() : nullptr;
device_info.enabledExtensionCount = (uint32_t)device_extensions_enabled_.size();
device_info.ppEnabledExtensionNames = device_info.enabledExtensionCount ? device_extensions_enabled_.data() : nullptr;
- device_info.pEnabledFeatures = &deviceFeatures_.enabled;
+
+ if (extensionsLookup_.KHR_get_physical_device_properties2) {
+ device_info.pNext = &features2;
+ features2.features = deviceFeatures_.enabled.standard;
+ features2.pNext = &deviceFeatures_.enabled.multiview;
+ } else {
+ device_info.pEnabledFeatures = &deviceFeatures_.enabled.standard;
+ }
VkResult res = vkCreateDevice(physical_devices_[physical_device_], &device_info, nullptr, &device_);
if (res != VK_SUCCESS) {
@@ -1084,9 +1109,14 @@ bool VulkanContext::InitSwapchain() {
std::string currentTransform = surface_transforms_to_string(surfCapabilities_.currentTransform);
g_display_rotation = DisplayRotation::ROTATE_0;
g_display_rot_matrix.setIdentity();
+
+ uint32_t allowedRotations = VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
+ // Hack: Don't allow 270 degrees pretransform (inverse landscape), it creates bizarre issues on some devices (see #15773).
+ allowedRotations &= ~VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
+
if (surfCapabilities_.currentTransform & (VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR | VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR)) {
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
- } else if (surfCapabilities_.currentTransform & (VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR)) {
+ } else if (surfCapabilities_.currentTransform & allowedRotations) {
// Normal, sensible rotations. Let's handle it.
preTransform = surfCapabilities_.currentTransform;
g_display_rot_matrix.setIdentity();
@@ -1109,20 +1139,29 @@ bool VulkanContext::InitSwapchain() {
_dbg_assert_(false);
}
} else {
- // Let the OS rotate the image (potentially slow on many Android devices)
+ // Let the OS rotate the image (potentially slower on many Android devices)
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
}
std::string preTransformStr = surface_transforms_to_string(preTransform);
-
INFO_LOG(G3D, "Transform supported: %s current: %s chosen: %s", supportedTransforms.c_str(), currentTransform.c_str(), preTransformStr.c_str());
if (physicalDeviceProperties_[physical_device_].properties.vendorID == VULKAN_VENDOR_IMGTEC) {
- INFO_LOG(G3D, "Applying PowerVR hack (rounding off the width!)");
- // Swap chain width hack to avoid issue #11743 (PowerVR driver bug).
- // To keep the size consistent even with pretransform, do this after the swap. Should be fine.
- // This is fixed in newer PowerVR drivers but I don't know the cutoff.
- swapChainExtent_.width &= ~31;
+ u32 driverVersion = physicalDeviceProperties_[physical_device_].properties.driverVersion;
+ // Cutoff the hack at driver version 1.386.1368 (0x00582558, see issue #15773).
+ if (driverVersion < 0x00582558) {
+ INFO_LOG(G3D, "Applying PowerVR hack (rounding off the width!) driverVersion=%08x", driverVersion);
+ // Swap chain width hack to avoid issue #11743 (PowerVR driver bug).
+ // To keep the size consistent even with pretransform, do this after the swap. Should be fine.
+ // This is fixed in newer PowerVR drivers but I don't know the cutoff.
+ swapChainExtent_.width &= ~31;
+
+ // TODO: Also modify display_xres/display_yres appropriately for scissors to match.
+ // This will get a bit messy. Ideally we should remove that logic from app-android.cpp
+ // and move it here, but the OpenGL code still needs it.
+ } else {
+ INFO_LOG(G3D, "PowerVR driver version new enough (%08x), not applying swapchain width hack", driverVersion);
+ }
}
VkSwapchainCreateInfoKHR swap_chain_info{ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR };
@@ -1227,7 +1266,7 @@ bool VulkanContext::CreateShaderModule(const std::vector &spirv, VkSha
}
}
-void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int numMipLevels, VkImageAspectFlags aspectMask,
+void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int numMipLevels, int numLayers, VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout, VkImageLayout newImageLayout,
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask) {
@@ -1240,7 +1279,7 @@ void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int
image_memory_barrier.subresourceRange.aspectMask = aspectMask;
image_memory_barrier.subresourceRange.baseMipLevel = baseMip;
image_memory_barrier.subresourceRange.levelCount = numMipLevels;
- image_memory_barrier.subresourceRange.layerCount = 1; // We never use more than one layer, and old Mali drivers have problems with VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS.
+ image_memory_barrier.subresourceRange.layerCount = numLayers; // We never use more than one layer, and old Mali drivers have problems with VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS.
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
vkCmdPipelineBarrier(cmd, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
@@ -1354,37 +1393,6 @@ void finalize_glslang() {
glslang::FinalizeProcess();
}
-const char *VulkanResultToString(VkResult res) {
- switch (res) {
- case VK_NOT_READY: return "VK_NOT_READY";
- case VK_TIMEOUT: return "VK_TIMEOUT";
- case VK_EVENT_SET: return "VK_EVENT_SET";
- case VK_EVENT_RESET: return "VK_EVENT_RESET";
- case VK_INCOMPLETE: return "VK_INCOMPLETE";
- case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY";
- case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
- case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED";
- case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST";
- case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED";
- case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT";
- case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT";
- case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT";
- case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER";
- case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS";
- case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED";
- case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR";
- case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR";
- case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR";
- case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
- case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
- case VK_ERROR_OUT_OF_POOL_MEMORY_KHR: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR";
- case VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR: return "VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR";
-
- default:
- return "VK_ERROR_...(unknown)";
- }
-}
-
void VulkanDeleteList::Take(VulkanDeleteList &del) {
_dbg_assert_(cmdPools_.empty());
_dbg_assert_(descPools_.empty());
diff --git a/Common/GPU/Vulkan/VulkanContext.h b/Common/GPU/Vulkan/VulkanContext.h
index fb98b5a0db..464b78a47d 100644
--- a/Common/GPU/Vulkan/VulkanContext.h
+++ b/Common/GPU/Vulkan/VulkanContext.h
@@ -35,6 +35,7 @@ enum {
VULKAN_VENDOR_ARM = 0x000013B5, // Mali
VULKAN_VENDOR_QUALCOMM = 0x00005143,
VULKAN_VENDOR_IMGTEC = 0x00001010, // PowerVR
+ VULKAN_VENDOR_APPLE = 0x0000106b, // Apple through MoltenVK
};
VK_DEFINE_HANDLE(VmaAllocator);
@@ -222,6 +223,9 @@ public:
SetDebugNameImpl((uint64_t)handle, type, name);
}
}
+ bool DebugLayerEnabled() const {
+ return extensionsLookup_.EXT_debug_utils;
+ }
bool MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex);
@@ -250,6 +254,12 @@ public:
VkPhysicalDeviceProperties properties;
VkPhysicalDevicePushDescriptorPropertiesKHR pushDescriptorProperties;
VkPhysicalDeviceExternalMemoryHostPropertiesEXT externalMemoryHostProperties;
+ VkPhysicalDeviceDepthStencilResolveProperties depthStencilResolve;
+ };
+
+ struct AllPhysicalDeviceFeatures {
+ VkPhysicalDeviceFeatures standard;
+ VkPhysicalDeviceMultiviewFeatures multiview;
};
const PhysicalDeviceProps &GetPhysicalDeviceProperties(int i = -1) const {
@@ -275,9 +285,13 @@ public:
return device_extensions_enabled_;
}
+ const VkPhysicalDeviceMemoryProperties &GetMemoryProperties() const {
+ return memory_properties_;
+ }
+
struct PhysicalDeviceFeatures {
- VkPhysicalDeviceFeatures available{};
- VkPhysicalDeviceFeatures enabled{};
+ AllPhysicalDeviceFeatures available{};
+ AllPhysicalDeviceFeatures enabled{};
};
const PhysicalDeviceFeatures &GetDeviceFeatures() const { return deviceFeatures_; }
@@ -393,7 +407,8 @@ private:
uint32_t graphics_queue_family_index_ = -1;
std::vector physicalDeviceProperties_;
std::vector queueFamilyProperties_;
- VkPhysicalDeviceMemoryProperties memory_properties{};
+
+ VkPhysicalDeviceMemoryProperties memory_properties_{};
// Custom collection of things that are good to know
VulkanPhysicalDeviceInfo deviceInfo_{};
@@ -436,7 +451,7 @@ private:
};
// Detailed control.
-void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int mipLevels, VkImageAspectFlags aspectMask,
+void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int mipLevels, int numLayers, VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout, VkImageLayout newImageLayout,
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask);
@@ -453,7 +468,6 @@ enum class GLSLVariant {
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant, std::vector &spirv, std::string *errorMessage);
-const char *VulkanResultToString(VkResult res);
const char *VulkanColorSpaceToString(VkColorSpaceKHR colorSpace);
const char *VulkanFormatToString(VkFormat format);
diff --git a/Common/GPU/Vulkan/VulkanDebug.cpp b/Common/GPU/Vulkan/VulkanDebug.cpp
index 83b3d974c0..7a3d3a079f 100644
--- a/Common/GPU/Vulkan/VulkanDebug.cpp
+++ b/Common/GPU/Vulkan/VulkanDebug.cpp
@@ -42,6 +42,12 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
// UNASSIGNED
return false;
}
+ if (messageCode == 606910136 || messageCode == -392708513 || messageCode == -384083808) {
+ // VUID-vkCmdDraw-None-02686
+ // Kinda false positive, or at least very unnecessary, now that I solved the real issue.
+ // See https://github.com/hrydgard/ppsspp/pull/16354
+ return false;
+ }
if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
message << "ERROR(";
@@ -86,6 +92,7 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
} else {
WARN_LOG(G3D, "VKDEBUG: %s", msg.c_str());
}
+
// false indicates that layer should not bail-out of an
// API call that had validation failures. This may mean that the
// app dies inside the driver due to invalid parameter(s).
diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp
index ab74478396..4503ea1c82 100644
--- a/Common/GPU/Vulkan/VulkanFrameData.cpp
+++ b/Common/GPU/Vulkan/VulkanFrameData.cpp
@@ -121,7 +121,7 @@ VkCommandBuffer FrameData::GetInitCmd(VulkanContext *vulkan) {
}
void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, FrameDataShared &sharedData) {
- VkCommandBuffer cmdBufs[2];
+ VkCommandBuffer cmdBufs[3];
int numCmdBufs = 0;
VkFence fenceToTrigger = VK_NULL_HANDLE;
diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.cpp b/Common/GPU/Vulkan/VulkanFramebuffer.cpp
new file mode 100644
index 0000000000..ec50e3f7a4
--- /dev/null
+++ b/Common/GPU/Vulkan/VulkanFramebuffer.cpp
@@ -0,0 +1,536 @@
+#include "Common/StringUtils.h"
+#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
+#include "Common/GPU/Vulkan/VulkanQueueRunner.h"
+
+VkSampleCountFlagBits MultiSampleLevelToFlagBits(int count) {
+ // TODO: Check hardware support here, or elsewhere?
+ // Some hardware only supports 4x.
+ switch (count) {
+ case 0: return VK_SAMPLE_COUNT_1_BIT;
+ case 1: return VK_SAMPLE_COUNT_2_BIT;
+ case 2: return VK_SAMPLE_COUNT_4_BIT; // The only non-1 level supported on some mobile chips.
+ case 3: return VK_SAMPLE_COUNT_8_BIT;
+ case 4: return VK_SAMPLE_COUNT_16_BIT; // rare but exists, on Intel for example
+ default:
+ _assert_(false);
+ return VK_SAMPLE_COUNT_1_BIT;
+ }
+}
+
+void VKRImage::Delete(VulkanContext *vulkan) {
+ // Get rid of the views first, feels cleaner (but in reality doesn't matter).
+ if (rtView)
+ vulkan->Delete().QueueDeleteImageView(rtView);
+ if (texAllLayersView)
+ vulkan->Delete().QueueDeleteImageView(texAllLayersView);
+ for (int i = 0; i < 2; i++) {
+ if (texLayerViews[i]) {
+ vulkan->Delete().QueueDeleteImageView(texLayerViews[i]);
+ }
+ }
+
+ if (image) {
+ _dbg_assert_(alloc);
+ vulkan->Delete().QueueDeleteImageAllocation(image, alloc);
+ }
+}
+
+VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, int _multiSampleLevel, bool createDepthStencilBuffer, const char *tag)
+ : vulkan_(vk), tag_(tag), width(_width), height(_height), numLayers(_numLayers) {
+
+ _dbg_assert_(tag);
+
+ CreateImage(vulkan_, initCmd, color, width, height, numLayers, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag);
+ if (createDepthStencilBuffer) {
+ CreateImage(vulkan_, initCmd, depth, width, height, numLayers, VK_SAMPLE_COUNT_1_BIT, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
+ }
+
+ if (_multiSampleLevel > 0) {
+ sampleCount = MultiSampleLevelToFlagBits(_multiSampleLevel);
+
+ // TODO: Create a different tag for these?
+ CreateImage(vulkan_, initCmd, msaaColor, width, height, numLayers, sampleCount, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag);
+ if (createDepthStencilBuffer) {
+ CreateImage(vulkan_, initCmd, msaaDepth, width, height, numLayers, sampleCount, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
+ }
+ } else {
+ sampleCount = VK_SAMPLE_COUNT_1_BIT;
+ }
+
+ UpdateTag(tag);
+
+ // We create the actual framebuffer objects on demand, because some combinations might not make sense.
+ // Framebuffer objects are just pointers to a set of images, so no biggie.
+}
+
+void VKRFramebuffer::UpdateTag(const char *newTag) {
+ char name[128];
+ snprintf(name, sizeof(name), "fb_color_%s", tag_.c_str());
+ vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, name);
+ vulkan_->SetDebugName(color.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
+ if (depth.image) {
+ snprintf(name, sizeof(name), "fb_depth_%s", tag_.c_str());
+ vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, name);
+ vulkan_->SetDebugName(depth.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
+ }
+ for (size_t rpType = 0; rpType < (size_t)RenderPassType::TYPE_COUNT; rpType++) {
+ if (framebuf[rpType]) {
+ snprintf(name, sizeof(name), "fb_%s", tag_.c_str());
+ vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, name);
+ }
+ }
+}
+
+VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) {
+ bool multiview = RenderPassTypeHasMultiView(rpType);
+
+ if (framebuf[(int)rpType]) {
+ return framebuf[(int)rpType];
+ }
+
+ VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
+ VkImageView views[4]{};
+
+ bool hasDepth = RenderPassTypeHasDepth(rpType);
+ int attachmentCount = 0;
+ views[attachmentCount++] = color.rtView; // 2D array texture if multilayered.
+ if (hasDepth) {
+ if (!depth.rtView) {
+ WARN_LOG(G3D, "depth render type to non-depth fb: %p %p fmt=%d (%s %dx%d)", depth.image, depth.texAllLayersView, depth.format, tag_.c_str(), width, height);
+ // Will probably crash, depending on driver.
+ }
+ views[attachmentCount++] = depth.rtView;
+ }
+ if (rpType & RenderPassType::MULTISAMPLE) {
+ views[attachmentCount++] = msaaColor.rtView;
+ if (hasDepth) {
+ views[attachmentCount++] = msaaDepth.rtView;
+ }
+ }
+
+ fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType, sampleCount);
+ fbci.attachmentCount = attachmentCount;
+ fbci.pAttachments = views;
+ fbci.width = width;
+ fbci.height = height;
+ fbci.layers = 1; // With multiview, this should be set as 1.
+
+ VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)rpType]);
+ _assert_(res == VK_SUCCESS);
+
+ if (!tag_.empty() && vulkan_->Extensions().EXT_debug_utils) {
+ vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str());
+ }
+
+ return framebuf[(int)rpType];
+}
+
+VKRFramebuffer::~VKRFramebuffer() {
+ color.Delete(vulkan_);
+ depth.Delete(vulkan_);
+ msaaColor.Delete(vulkan_);
+ msaaDepth.Delete(vulkan_);
+
+ for (auto &fb : framebuf) {
+ if (fb) {
+ vulkan_->Delete().QueueDeleteFramebuffer(fb);
+ }
+ }
+}
+
+// NOTE: If numLayers > 1, it will create an array texture, rather than a normal 2D texture.
+// This requires a different sampling path!
+void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkSampleCountFlagBits sampleCount, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) {
+ // We don't support more exotic layer setups for now. Mono or stereo.
+ _dbg_assert_(numLayers == 1 || numLayers == 2);
+
+ VkImageCreateInfo ici{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+ ici.arrayLayers = numLayers;
+ ici.mipLevels = 1;
+ ici.extent.width = width;
+ ici.extent.height = height;
+ ici.extent.depth = 1;
+ ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ ici.imageType = VK_IMAGE_TYPE_2D;
+ ici.samples = sampleCount;
+ ici.tiling = VK_IMAGE_TILING_OPTIMAL;
+ ici.format = format;
+ ici.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ if (sampleCount == VK_SAMPLE_COUNT_1_BIT) {
+ ici.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
+ }
+ if (color) {
+ ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ if (sampleCount == VK_SAMPLE_COUNT_1_BIT) {
+ ici.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+ }
+ } else {
+ ici.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ }
+
+ VmaAllocationCreateInfo allocCreateInfo{};
+ allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+ VmaAllocationInfo allocInfo{};
+
+ VkResult res = vmaCreateImage(vulkan->Allocator(), &ici, &allocCreateInfo, &img.image, &img.alloc, &allocInfo);
+ _dbg_assert_(res == VK_SUCCESS);
+
+ VkImageAspectFlags aspects = color ? VK_IMAGE_ASPECT_COLOR_BIT : (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
+
+ VkImageViewCreateInfo ivci{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ ivci.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
+ ivci.format = ici.format;
+ ivci.image = img.image;
+ ivci.viewType = numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+ ivci.subresourceRange.aspectMask = aspects;
+ ivci.subresourceRange.layerCount = numLayers;
+ ivci.subresourceRange.levelCount = 1;
+ res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.rtView);
+ vulkan->SetDebugName(img.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, tag);
+ _dbg_assert_(res == VK_SUCCESS);
+
+ // Separate view for texture sampling all layers together.
+ if (!color) {
+ ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+ }
+
+ ivci.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; // layered for consistency, even if single image.
+ res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.texAllLayersView);
+ vulkan->SetDebugName(img.texAllLayersView, VK_OBJECT_TYPE_IMAGE_VIEW, tag);
+
+ // Create 2D views for both layers.
+ // Useful when multipassing shaders that don't yet exist in a single-pass-stereo version.
+ for (int i = 0; i < numLayers; i++) {
+ ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ ivci.subresourceRange.layerCount = 1;
+ ivci.subresourceRange.baseArrayLayer = i;
+ res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.texLayerViews[i]);
+ if (vulkan->DebugLayerEnabled()) {
+ char temp[128];
+ snprintf(temp, sizeof(temp), "%s_layer%d", tag, i);
+ vulkan->SetDebugName(img.texLayerViews[i], VK_OBJECT_TYPE_IMAGE_VIEW, temp);
+ }
+ _dbg_assert_(res == VK_SUCCESS);
+ }
+
+ VkPipelineStageFlags dstStage;
+ VkAccessFlagBits dstAccessMask;
+ switch (initialLayout) {
+ case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+ dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ break;
+ case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+ dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ break;
+ case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+ dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+ dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+ break;
+ default:
+ Crash();
+ return;
+ }
+
+ TransitionImageLayout2(cmd, img.image, 0, 1, numLayers, aspects,
+ VK_IMAGE_LAYOUT_UNDEFINED, initialLayout,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, dstStage,
+ 0, dstAccessMask);
+ img.layout = initialLayout;
+ img.format = format;
+ img.sampleCount = sampleCount;
+ img.tag = tag ? tag : "N/A";
+ img.numLayers = numLayers;
+}
+
+static VkAttachmentLoadOp ConvertLoadAction(VKRRenderPassLoadAction action) {
+ switch (action) {
+ case VKRRenderPassLoadAction::CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR;
+ case VKRRenderPassLoadAction::KEEP: return VK_ATTACHMENT_LOAD_OP_LOAD;
+ case VKRRenderPassLoadAction::DONT_CARE: return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ }
+ return VK_ATTACHMENT_LOAD_OP_DONT_CARE; // avoid compiler warning
+}
+
+static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
+ switch (action) {
+ case VKRRenderPassStoreAction::STORE: return VK_ATTACHMENT_STORE_OP_STORE;
+ case VKRRenderPassStoreAction::DONT_CARE: return VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ }
+ return VK_ATTACHMENT_STORE_OP_DONT_CARE; // avoid compiler warning
+}
+
+// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
+// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
+
+VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) {
+ bool selfDependency = RenderPassTypeHasInput(rpType);
+ bool isBackbuffer = rpType == RenderPassType::BACKBUFFER;
+ bool hasDepth = RenderPassTypeHasDepth(rpType);
+ bool multiview = RenderPassTypeHasMultiView(rpType);
+ bool multisample = RenderPassTypeHasMultisample(rpType);
+
+ if (multiview) {
+ // TODO: Assert that the device has multiview support enabled.
+ }
+
+ int colorAttachmentIndex = 0;
+ int depthAttachmentIndex = 1;
+
+ int attachmentCount = 0;
+ VkAttachmentDescription attachments[4]{};
+ attachments[attachmentCount].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
+ attachments[attachmentCount].samples = VK_SAMPLE_COUNT_1_BIT;
+ attachments[attachmentCount].loadOp = multisample ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : ConvertLoadAction(key.colorLoadAction);
+ attachments[attachmentCount].storeOp = ConvertStoreAction(key.colorStoreAction);
+ attachments[attachmentCount].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attachments[attachmentCount].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attachments[attachmentCount].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachments[attachmentCount].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachmentCount++;
+
+ if (hasDepth) {
+ attachments[attachmentCount].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
+ attachments[attachmentCount].samples = VK_SAMPLE_COUNT_1_BIT;
+ attachments[attachmentCount].loadOp = multisample ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : ConvertLoadAction(key.depthLoadAction);
+ attachments[attachmentCount].storeOp = ConvertStoreAction(key.depthStoreAction);
+ attachments[attachmentCount].stencilLoadOp = multisample ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : ConvertLoadAction(key.stencilLoadAction);
+ attachments[attachmentCount].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
+ attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachments[attachmentCount].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachmentCount++;
+ }
+
+ if (multisample) {
+ colorAttachmentIndex = attachmentCount;
+ attachments[attachmentCount].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
+ attachments[attachmentCount].samples = sampleCount;
+ attachments[attachmentCount].loadOp = ConvertLoadAction(key.colorLoadAction);
+ attachments[attachmentCount].storeOp = ConvertStoreAction(key.colorStoreAction);
+ attachments[attachmentCount].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attachments[attachmentCount].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachments[attachmentCount].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachmentCount++;
+
+ if (hasDepth) {
+ depthAttachmentIndex = attachmentCount;
+ attachments[attachmentCount].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
+ attachments[attachmentCount].samples = sampleCount;
+ attachments[attachmentCount].loadOp = ConvertLoadAction(key.depthLoadAction);
+ attachments[attachmentCount].storeOp = ConvertStoreAction(key.depthStoreAction);
+ attachments[attachmentCount].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction);
+ attachments[attachmentCount].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
+ attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachments[attachmentCount].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachmentCount++;
+ }
+ }
+
+ VkAttachmentReference colorReference{};
+ colorReference.attachment = colorAttachmentIndex;
+ colorReference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ VkAttachmentReference depthReference{};
+ depthReference.attachment = depthAttachmentIndex;
+ depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ VkSubpassDescription subpass{};
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.flags = 0;
+ if (selfDependency) {
+ subpass.inputAttachmentCount = 1;
+ subpass.pInputAttachments = &colorReference;
+ } else {
+ subpass.inputAttachmentCount = 0;
+ subpass.pInputAttachments = nullptr;
+ }
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &colorReference;
+
+ VkAttachmentReference colorResolveReference;
+ if (multisample) {
+ colorResolveReference.attachment = 0; // the non-msaa color buffer.
+ colorResolveReference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ subpass.pResolveAttachments = &colorResolveReference;
+ } else {
+ subpass.pResolveAttachments = nullptr;
+ }
+ if (hasDepth) {
+ subpass.pDepthStencilAttachment = &depthReference;
+ }
+ subpass.preserveAttachmentCount = 0;
+ subpass.pPreserveAttachments = nullptr;
+
+ // Not sure if this is really necessary.
+ VkSubpassDependency deps[2]{};
+ size_t numDeps = 0;
+
+ VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
+ rp.attachmentCount = attachmentCount;
+ rp.pAttachments = attachments;
+ rp.subpassCount = 1;
+ rp.pSubpasses = &subpass;
+
+ VkRenderPassMultiviewCreateInfoKHR mv{ VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO_KHR };
+ uint32_t viewMask = 0x3; // Must be outside the 'if (multiview)' scope!
+ int viewOffset = 0;
+ if (multiview) {
+ rp.pNext = &mv;
+ mv.subpassCount = 1;
+ mv.pViewMasks = &viewMask;
+ mv.dependencyCount = 0;
+ mv.pCorrelationMasks = &viewMask; // same masks
+ mv.correlationMaskCount = 1;
+ mv.pViewOffsets = &viewOffset;
+ }
+
+ if (isBackbuffer) {
+ deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL;
+ deps[numDeps].dstSubpass = 0;
+ deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].srcAccessMask = 0;
+ deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ numDeps++;
+ }
+
+ if (selfDependency) {
+ deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+ deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ deps[numDeps].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
+ deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ deps[numDeps].srcSubpass = 0;
+ deps[numDeps].dstSubpass = 0;
+ numDeps++;
+ }
+
+ if (numDeps > 0) {
+ rp.dependencyCount = (u32)numDeps;
+ rp.pDependencies = deps;
+ }
+
+ VkRenderPass pass;
+ VkResult res;
+
+ // We could always use renderpass2, but I think it'll get both paths better tested if we
+ // only use it with multisample enabled.
+ // if (vulkan->Extensions().KHR_create_renderpass2) {
+ if (multisample) {
+ // It's a bit unfortunate that we can't rely on vkCreateRenderPass2, because here we now have
+ // to do a bunch of struct conversion, just to not have to repeat the logic from above.
+ VkAttachmentDescription2KHR attachments2[4]{};
+ for (int i = 0; i < attachmentCount; i++) {
+ attachments2[i].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR;
+ attachments2[i].format = attachments[i].format;
+ attachments2[i].samples = attachments[i].samples;
+ attachments2[i].loadOp = attachments[i].loadOp;
+ attachments2[i].storeOp = attachments[i].storeOp;
+ attachments2[i].stencilLoadOp = attachments[i].stencilLoadOp;
+ attachments2[i].stencilStoreOp = attachments[i].stencilStoreOp;
+ attachments2[i].initialLayout = attachments[i].initialLayout;
+ attachments2[i].finalLayout = attachments[i].finalLayout;
+ }
+
+ VkAttachmentReference2KHR colorReference2{ VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR };
+ colorReference2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ colorReference2.attachment = colorReference.attachment;
+ colorReference2.layout = colorReference.layout;
+
+ VkAttachmentReference2KHR depthReference2{ VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR };
+ depthReference2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ depthReference2.attachment = depthReference.attachment;
+ depthReference2.layout = depthReference.layout;
+
+ VkSubpassDependency2KHR deps2[2]{};
+ for (int i = 0; i < numDeps; i++) {
+ deps2[i].sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2_KHR;
+ deps2[i].dependencyFlags = deps[i].dependencyFlags;
+ deps2[i].srcAccessMask = deps[i].srcAccessMask;
+ deps2[i].dstAccessMask = deps[i].dstAccessMask;
+ deps2[i].srcStageMask = deps[i].srcStageMask;
+ deps2[i].dstStageMask = deps[i].dstStageMask;
+ deps2[i].srcSubpass = deps[i].srcSubpass;
+ deps2[i].dstSubpass = deps[i].dstSubpass;
+ deps2[i].dependencyFlags = deps[i].dependencyFlags;
+ deps2[i].viewOffset = 0;
+ }
+
+ VkAttachmentReference2KHR colorResolveReference2{ VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR };
+
+ VkSubpassDescription2KHR subpass2{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR };
+ subpass2.colorAttachmentCount = subpass.colorAttachmentCount;
+ subpass2.flags = subpass.flags;
+ if (selfDependency) {
+ subpass2.inputAttachmentCount = subpass.inputAttachmentCount;
+ subpass2.pInputAttachments = &colorReference2;
+ }
+ subpass2.pColorAttachments = &colorReference2;
+ if (hasDepth) {
+ subpass2.pDepthStencilAttachment = &depthReference2;
+ }
+ subpass2.pipelineBindPoint = subpass.pipelineBindPoint;
+ subpass2.viewMask = multiview ? viewMask : 0;
+ if (multisample) {
+ colorResolveReference2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ colorResolveReference2.attachment = colorResolveReference.attachment; // the non-msaa color buffer.
+ colorResolveReference2.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ subpass2.pResolveAttachments = &colorResolveReference2;
+ } else {
+ subpass2.pResolveAttachments = nullptr;
+ }
+
+ VkAttachmentReference2KHR depthResolveReference2{ VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR };
+ VkSubpassDescriptionDepthStencilResolveKHR depthStencilResolve{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR };
+ if (hasDepth && multisample) {
+ subpass2.pNext = &depthStencilResolve;
+ depthResolveReference2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ depthResolveReference2.attachment = 1;
+ depthResolveReference2.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ // TODO: Some games might benefit from the other depth resolve modes when depth texturing.
+ depthStencilResolve.depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR;
+ depthStencilResolve.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR;
+ depthStencilResolve.pDepthStencilResolveAttachment = &depthResolveReference2;
+ }
+
+ VkRenderPassCreateInfo2KHR rp2{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR };
+ rp2.pAttachments = attachments2;
+ rp2.pDependencies = deps2;
+ rp2.attachmentCount = rp.attachmentCount;
+ rp2.dependencyCount = rp.dependencyCount;
+ rp2.correlatedViewMaskCount = multiview ? 1 : 0;
+ rp2.pCorrelatedViewMasks = multiview ? &viewMask : nullptr;
+ rp2.pSubpasses = &subpass2;
+ rp2.subpassCount = 1;
+ res = vkCreateRenderPass2KHR(vulkan->GetDevice(), &rp2, nullptr, &pass);
+ } else {
+ res = vkCreateRenderPass(vulkan->GetDevice(), &rp, nullptr, &pass);
+ }
+
+ _assert_(res == VK_SUCCESS);
+ _assert_(pass != VK_NULL_HANDLE);
+ return pass;
+}
+
+VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType, VkSampleCountFlagBits sampleCount) {
+ // When we create a render pass, we create all "types" of it immediately,
+ // practical later when referring to it. Could change to on-demand if it feels motivated
+ // but I think the render pass objects are cheap.
+
+ // WARNING: We don't include sampleCount in the key, there's only the distinction multisampled or not
+ // which comes from the rpType.
+ // So you CAN NOT mix and match different non-one sample counts.
+
+ _dbg_assert_(!((rpType & RenderPassType::MULTISAMPLE) && sampleCount == VK_SAMPLE_COUNT_1_BIT));
+
+ if (!pass[(int)rpType] || sampleCounts[(int)rpType] != sampleCount) {
+ if (pass[(int)rpType]) {
+ vulkan->Delete().QueueDeleteRenderPass(pass[(int)rpType]);
+ }
+ pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType, sampleCount);
+ sampleCounts[(int)rpType] = sampleCount;
+ }
+ return pass[(int)rpType];
+}
diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.h b/Common/GPU/Vulkan/VulkanFramebuffer.h
new file mode 100644
index 0000000000..fc584f4b8c
--- /dev/null
+++ b/Common/GPU/Vulkan/VulkanFramebuffer.h
@@ -0,0 +1,164 @@
+#pragma once
+
+#include "Common/Common.h"
+#include "Common/GPU/Vulkan/VulkanContext.h"
+
+class VKRRenderPass;
+
+// Pipelines need to be created for the right type of render pass.
+// TODO: Rename to RenderPassFlags?
+// When you add more flags, don't forget to update rpTypeDebugNames[].
+enum class RenderPassType {
+ DEFAULT = 0,
+ // These eight are organized so that bit 0 is DEPTH and bit 1 is INPUT and bit 2 is MULTIVIEW, so
+ // they can be OR-ed together in MergeRPTypes.
+ HAS_DEPTH = 1,
+ COLOR_INPUT = 2, // input attachment
+ MULTIVIEW = 4,
+ MULTISAMPLE = 8,
+
+ // This is the odd one out, and gets special handling in MergeRPTypes.
+ // If this flag is set, none of the other flags can be set.
+ // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible
+ // so we don't bother with a non-depth version.
+ BACKBUFFER = 16,
+
+ TYPE_COUNT = BACKBUFFER + 1,
+};
+ENUM_CLASS_BITOPS(RenderPassType);
+
+// Simple independent framebuffer image.
+struct VKRImage {
+ // These four are "immutable".
+ VkImage image;
+
+ VkImageView rtView; // Used for rendering to, and readbacks of stencil. 2D if single layer, 2D_ARRAY if multiple. Includes both depth and stencil if depth/stencil.
+
+ // This is for texturing all layers at once. If aspect is depth/stencil, does not include stencil.
+ VkImageView texAllLayersView;
+
+ // If it's a layered image (for stereo), this is two 2D views of it, to make it compatible with shaders that don't yet support stereo.
+ // If there's only one layer, layerViews[0] only is initialized.
+ VkImageView texLayerViews[2]{};
+
+ VmaAllocation alloc;
+ VkFormat format;
+ VkSampleCountFlagBits sampleCount;
+
+ // This one is used by QueueRunner's Perform functions to keep track. CANNOT be used anywhere else due to sync issues.
+ VkImageLayout layout;
+
+ int numLayers;
+
+ // For debugging.
+ std::string tag;
+
+ void Delete(VulkanContext *vulkan);
+};
+
+class VKRFramebuffer {
+public:
+ VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, int _multiSampleLevel, bool createDepthStencilBuffer, const char *tag);
+ ~VKRFramebuffer();
+
+ VkFramebuffer Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType);
+
+ int width = 0;
+ int height = 0;
+ int numLayers = 0;
+ VkSampleCountFlagBits sampleCount;
+
+ VKRImage color{}; // color.image is always there.
+ VKRImage depth{}; // depth.image is allowed to be VK_NULL_HANDLE.
+
+ // These are only initialized and used if numSamples > 1.
+ VKRImage msaaColor{};
+ VKRImage msaaDepth{};
+
+ const char *Tag() const {
+ return tag_.c_str();
+ }
+
+ void UpdateTag(const char *newTag);
+
+ bool HasDepth() const {
+ return depth.image != VK_NULL_HANDLE;
+ }
+
+ VkImageView GetRTView() {
+ if (sampleCount == VK_SAMPLE_COUNT_1_BIT) {
+ return color.rtView;
+ } else {
+ return msaaColor.rtView;
+ }
+ }
+
+ VulkanContext *Vulkan() const { return vulkan_; }
+private:
+ static void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkSampleCountFlagBits sampleCount, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag);
+
+ VkFramebuffer framebuf[(size_t)RenderPassType::TYPE_COUNT]{};
+
+ VulkanContext *vulkan_;
+ std::string tag_;
+};
+
+inline bool RenderPassTypeHasDepth(RenderPassType type) {
+ return (type & RenderPassType::HAS_DEPTH) || type == RenderPassType::BACKBUFFER;
+}
+
+inline bool RenderPassTypeHasInput(RenderPassType type) {
+ return (type & RenderPassType::COLOR_INPUT) != 0;
+}
+
+inline bool RenderPassTypeHasMultiView(RenderPassType type) {
+ return (type & RenderPassType::MULTIVIEW) != 0;
+}
+
+inline bool RenderPassTypeHasMultisample(RenderPassType type) {
+ return (type & RenderPassType::MULTISAMPLE) != 0;
+}
+
+VkSampleCountFlagBits MultiSampleLevelToFlagBits(int count);
+
+// Must be the same order as Draw::RPAction
+enum class VKRRenderPassLoadAction : uint8_t {
+ KEEP, // default. avoid when possible.
+ CLEAR,
+ DONT_CARE,
+};
+
+enum class VKRRenderPassStoreAction : uint8_t {
+ STORE, // default. avoid when possible.
+ DONT_CARE,
+};
+
+struct RPKey {
+ // Only render-pass-compatibility-volatile things can be here.
+ VKRRenderPassLoadAction colorLoadAction;
+ VKRRenderPassLoadAction depthLoadAction;
+ VKRRenderPassLoadAction stencilLoadAction;
+ VKRRenderPassStoreAction colorStoreAction;
+ VKRRenderPassStoreAction depthStoreAction;
+ VKRRenderPassStoreAction stencilStoreAction;
+};
+
+class VKRRenderPass {
+public:
+ VKRRenderPass(const RPKey &key) : key_(key) {}
+
+ VkRenderPass Get(VulkanContext *vulkan, RenderPassType rpType, VkSampleCountFlagBits sampleCount);
+ void Destroy(VulkanContext *vulkan) {
+ for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
+ if (pass[i]) {
+ vulkan->Delete().QueueDeleteRenderPass(pass[i]);
+ }
+ }
+ }
+
+private:
+ // TODO: Might be better off with a hashmap once the render pass type count grows really large..
+ VkRenderPass pass[(size_t)RenderPassType::TYPE_COUNT]{};
+ VkSampleCountFlagBits sampleCounts[(size_t)RenderPassType::TYPE_COUNT];
+ RPKey key_;
+};
diff --git a/Common/GPU/Vulkan/VulkanImage.cpp b/Common/GPU/Vulkan/VulkanImage.cpp
index 72eb339c12..8efcb69834 100644
--- a/Common/GPU/Vulkan/VulkanImage.cpp
+++ b/Common/GPU/Vulkan/VulkanImage.cpp
@@ -5,9 +5,15 @@
#include "Common/GPU/Vulkan/VulkanAlloc.h"
#include "Common/GPU/Vulkan/VulkanImage.h"
#include "Common/GPU/Vulkan/VulkanMemory.h"
+#include "Common/StringUtils.h"
using namespace PPSSPP_VK;
+VulkanTexture::VulkanTexture(VulkanContext *vulkan, const char *tag)
+ : vulkan_(vulkan) {
+ truncate_cpy(tag_, tag);
+}
+
void VulkanTexture::Wipe() {
if (view_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(view_);
@@ -80,14 +86,14 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int depth, i
VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_create_info, &allocCreateInfo, &image_, &allocation_, &allocInfo);
// Apply the tag
- vulkan_->SetDebugName(image_, VK_OBJECT_TYPE_IMAGE, tag_.c_str());
+ vulkan_->SetDebugName(image_, VK_OBJECT_TYPE_IMAGE, tag_);
// Write a command to transition the image to the requested layout, if it's not already that layout.
if (initialLayout != VK_IMAGE_LAYOUT_UNDEFINED && initialLayout != VK_IMAGE_LAYOUT_PREINITIALIZED) {
switch (initialLayout) {
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
case VK_IMAGE_LAYOUT_GENERAL:
- TransitionImageLayout2(cmd, image_, 0, numMips, VK_IMAGE_ASPECT_COLOR_BIT,
+ TransitionImageLayout2(cmd, image_, 0, numMips, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_UNDEFINED, initialLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, VK_ACCESS_TRANSFER_WRITE_BIT);
@@ -123,6 +129,16 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int depth, i
_assert_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
+ vulkan_->SetDebugName(view_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
+
+ // Additionally, create an array view, but only if it's a 2D texture.
+ if (view_info.viewType == VK_IMAGE_VIEW_TYPE_2D) {
+ view_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+ res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &arrayView_);
+ _assert_(res == VK_SUCCESS);
+ vulkan_->SetDebugName(arrayView_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
+ }
+
return true;
}
@@ -164,7 +180,7 @@ void VulkanTexture::GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bo
_assert_msg_(firstMipToGenerate < numMips_, "Can't generate levels beyond storage");
// Transition the pre-set levels to GENERAL.
- TransitionImageLayout2(cmd, image_, 0, firstMipToGenerate, VK_IMAGE_ASPECT_COLOR_BIT,
+ TransitionImageLayout2(cmd, image_, 0, firstMipToGenerate, 1, VK_IMAGE_ASPECT_COLOR_BIT,
fromCompute ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_GENERAL,
fromCompute ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT,
@@ -173,7 +189,7 @@ void VulkanTexture::GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bo
VK_ACCESS_TRANSFER_READ_BIT);
// Do the same with the uninitialized levels.
- TransitionImageLayout2(cmd, image_, firstMipToGenerate, numMips_ - firstMipToGenerate,
+ TransitionImageLayout2(cmd, image_, firstMipToGenerate, numMips_ - firstMipToGenerate, 1,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_GENERAL,
@@ -206,7 +222,7 @@ void VulkanTexture::GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bo
vkCmdBlitImage(cmd, image_, VK_IMAGE_LAYOUT_GENERAL, image_, VK_IMAGE_LAYOUT_GENERAL, 1, &blit, VK_FILTER_LINEAR);
- TransitionImageLayout2(cmd, image_, mip, 1, VK_IMAGE_ASPECT_COLOR_BIT,
+ TransitionImageLayout2(cmd, image_, mip, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
@@ -214,7 +230,7 @@ void VulkanTexture::GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bo
}
void VulkanTexture::EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelineStageFlags prevStage, VkImageLayout layout) {
- TransitionImageLayout2(cmd, image_, 0, numMips_,
+ TransitionImageLayout2(cmd, image_, 0, numMips_, 1,
VK_IMAGE_ASPECT_COLOR_BIT,
layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
prevStage, vertexTexture ? VK_PIPELINE_STAGE_VERTEX_SHADER_BIT : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
@@ -237,6 +253,7 @@ VkImageView VulkanTexture::CreateViewForMip(int mip) {
view_info.subresourceRange.layerCount = 1;
VkImageView view;
VkResult res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view);
+ vulkan_->SetDebugName(view, VK_OBJECT_TYPE_IMAGE_VIEW, "mipview");
_assert_(res == VK_SUCCESS);
return view;
}
@@ -245,6 +262,9 @@ void VulkanTexture::Destroy() {
if (view_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(view_);
}
+ if (arrayView_ != VK_NULL_HANDLE) {
+ vulkan_->Delete().QueueDeleteImageView(arrayView_);
+ }
if (image_ != VK_NULL_HANDLE) {
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);
diff --git a/Common/GPU/Vulkan/VulkanImage.h b/Common/GPU/Vulkan/VulkanImage.h
index 50d7e0c7f5..c088fdca5b 100644
--- a/Common/GPU/Vulkan/VulkanImage.h
+++ b/Common/GPU/Vulkan/VulkanImage.h
@@ -1,6 +1,5 @@
#pragma once
-#include
#include "VulkanLoader.h"
class VulkanContext;
@@ -12,9 +11,7 @@ VK_DEFINE_HANDLE(VmaAllocation);
// ALWAYS use an allocator when calling CreateDirect.
class VulkanTexture {
public:
- VulkanTexture(VulkanContext *vulkan)
- : vulkan_(vulkan) {
- }
+ VulkanTexture(VulkanContext *vulkan, const char *tag);
~VulkanTexture() {
Destroy();
}
@@ -37,13 +34,9 @@ public:
void Destroy();
- void SetTag(const char *tag) {
- tag_ = tag;
- }
- const std::string &Tag() const {
+ const char *Tag() const {
return tag_;
}
- void Touch() {}
// Used in image copies, etc.
VkImage GetImage() const { return image_; }
@@ -51,6 +44,9 @@ public:
// Used for sampling, generally.
VkImageView GetImageView() const { return view_; }
+ // For use with some shaders, we might want to view it as a single entry array for convenience.
+ VkImageView GetImageArrayView() const { return arrayView_; }
+
int32_t GetWidth() const { return width_; }
int32_t GetHeight() const { return height_; }
int32_t GetNumMips() const { return numMips_; }
@@ -62,6 +58,7 @@ private:
VulkanContext *vulkan_;
VkImage image_ = VK_NULL_HANDLE;
VkImageView view_ = VK_NULL_HANDLE;
+ VkImageView arrayView_ = VK_NULL_HANDLE;
VmaAllocation allocation_ = VK_NULL_HANDLE;
int16_t width_ = 0;
@@ -70,5 +67,5 @@ private:
int16_t depth_ = 1;
VkFormat format_ = VK_FORMAT_UNDEFINED;
- std::string tag_;
+ char tag_[64];
};
diff --git a/Common/GPU/Vulkan/VulkanLoader.cpp b/Common/GPU/Vulkan/VulkanLoader.cpp
index b0c2ab370b..94092b2166 100644
--- a/Common/GPU/Vulkan/VulkanLoader.cpp
+++ b/Common/GPU/Vulkan/VulkanLoader.cpp
@@ -223,6 +223,7 @@ PFN_vkGetBufferMemoryRequirements2KHR vkGetBufferMemoryRequirements2KHR;
PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR;
PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR;
PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR;
+PFN_vkCreateRenderPass2KHR vkCreateRenderPass2KHR;
} // namespace PPSSPP_VK
using namespace PPSSPP_VK;
@@ -239,7 +240,6 @@ static VulkanLibraryHandle vulkanLibrary;
typedef void *VulkanLibraryHandle;
static VulkanLibraryHandle vulkanLibrary;
#endif
-const char *VulkanResultToString(VkResult res);
bool g_vulkanAvailabilityChecked = false;
bool g_vulkanMayBeAvailable = false;
@@ -274,6 +274,8 @@ static VulkanLibraryHandle VulkanLoadLibrary(const char *logname) {
#if PPSSPP_PLATFORM(SWITCH)
// Always unavailable, for now.
return nullptr;
+#elif PPSSPP_PLATFORM(UWP)
+ return nullptr;
#elif PPSSPP_PLATFORM(WINDOWS)
return LoadLibrary(L"vulkan-1.dll");
#else
@@ -308,9 +310,8 @@ void VulkanSetAvailable(bool available) {
}
bool VulkanMayBeAvailable() {
-
- //unsupported in VR at the moment
- if (IsVRBuild()) {
+ // Unsupported in VR at the moment
+ if (IsVREnabled()) {
return false;
}
@@ -720,8 +721,58 @@ void VulkanLoadDeviceFunctions(VkDevice device, const VulkanExtensions &enabledE
LOAD_DEVICE_FUNC(device, vkGetBufferMemoryRequirements2KHR);
LOAD_DEVICE_FUNC(device, vkGetImageMemoryRequirements2KHR);
}
+ if (enabledExtensions.KHR_create_renderpass2) {
+ LOAD_DEVICE_FUNC(device, vkCreateRenderPass2KHR);
+ }
}
void VulkanFree() {
VulkanFreeLibrary(vulkanLibrary);
}
+
+const char *VulkanResultToString(VkResult res) {
+ static char temp[128]{};
+ switch (res) {
+ case VK_NOT_READY: return "VK_NOT_READY";
+ case VK_TIMEOUT: return "VK_TIMEOUT";
+ case VK_EVENT_SET: return "VK_EVENT_SET";
+ case VK_EVENT_RESET: return "VK_EVENT_RESET";
+ case VK_INCOMPLETE: return "VK_INCOMPLETE";
+ case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY";
+ case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
+ case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED";
+ case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST";
+ case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED";
+ case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT";
+ case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT";
+ case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT";
+ case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER";
+ case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS";
+ case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED";
+ case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR";
+ case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR";
+ case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR";
+ case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
+ case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
+ case VK_ERROR_OUT_OF_POOL_MEMORY: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR";
+ case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR";
+ case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL";
+ case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN (-13)";
+ case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION";
+ case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS";
+ case VK_PIPELINE_COMPILE_REQUIRED: return "VK_PIPELINE_COMPILE_REQUIRED";
+ case VK_ERROR_VALIDATION_FAILED_EXT: return "VK_ERROR_VALIDATION_FAILED_EXT";
+ case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV";
+ case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
+ case VK_ERROR_NOT_PERMITTED_KHR: return "VK_ERROR_NOT_PERMITTED_KHR";
+ case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
+ case VK_THREAD_IDLE_KHR: return "VK_THREAD_IDLE_KHR";
+ case VK_THREAD_DONE_KHR: return "VK_THREAD_DONE_KHR";
+ case VK_OPERATION_DEFERRED_KHR: return "VK_OPERATION_DEFERRED_KHR";
+ case VK_OPERATION_NOT_DEFERRED_KHR: return "VK_OPERATION_NOT_DEFERRED_KHR";
+ default:
+ // This isn't thread safe, but this should be rare, and at worst, we'll get a jumble of two messages.
+ snprintf(temp, sizeof(temp), "VK_ERROR_???: 0x%08x", (u32)res);
+ return temp;
+ }
+}
diff --git a/Common/GPU/Vulkan/VulkanLoader.h b/Common/GPU/Vulkan/VulkanLoader.h
index b0c14570da..caaef0d597 100644
--- a/Common/GPU/Vulkan/VulkanLoader.h
+++ b/Common/GPU/Vulkan/VulkanLoader.h
@@ -224,6 +224,7 @@ extern PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR;
extern PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesEXT;
extern PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR;
extern PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR;
+extern PFN_vkCreateRenderPass2KHR vkCreateRenderPass2KHR;
} // namespace PPSSPP_VK
// For fast extension-enabled checks.
@@ -254,3 +255,5 @@ bool VulkanLoad();
void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanExtensions &enabledExtensions);
void VulkanLoadDeviceFunctions(VkDevice device, const VulkanExtensions &enabledExtensions);
void VulkanFree();
+
+const char *VulkanResultToString(VkResult res);
diff --git a/Common/GPU/Vulkan/VulkanMemory.cpp b/Common/GPU/Vulkan/VulkanMemory.cpp
index 8206c80c91..d8184098a5 100644
--- a/Common/GPU/Vulkan/VulkanMemory.cpp
+++ b/Common/GPU/Vulkan/VulkanMemory.cpp
@@ -153,7 +153,7 @@ void VulkanDescSetPool::Create(VulkanContext *vulkan, const VkDescriptorPoolCrea
_assert_msg_(res == VK_SUCCESS, "Could not create VulkanDescSetPool %s", tag_);
}
-VkDescriptorSet VulkanDescSetPool::Allocate(int n, const VkDescriptorSetLayout *layouts) {
+VkDescriptorSet VulkanDescSetPool::Allocate(int n, const VkDescriptorSetLayout *layouts, const char *tag) {
if (descPool_ == VK_NULL_HANDLE || usage_ + n >= info_.maxSets) {
// Missing or out of space, need to recreate.
VkResult res = Recreate(grow_);
@@ -180,9 +180,12 @@ VkDescriptorSet VulkanDescSetPool::Allocate(int n, const VkDescriptorSetLayout *
_assert_msg_(result == VK_SUCCESS, "Ran out of descriptor space (frag?) and failed to allocate after recreating a descriptor pool. res=%d", (int)result);
}
- if (result == VK_SUCCESS)
- return desc;
- return VK_NULL_HANDLE;
+ if (result != VK_SUCCESS) {
+ return VK_NULL_HANDLE;
+ }
+
+ vulkan_->SetDebugName(desc, VK_OBJECT_TYPE_DESCRIPTOR_SET, tag);
+ return desc;
}
void VulkanDescSetPool::Reset() {
diff --git a/Common/GPU/Vulkan/VulkanMemory.h b/Common/GPU/Vulkan/VulkanMemory.h
index 79a71d4ce6..275ca4bb78 100644
--- a/Common/GPU/Vulkan/VulkanMemory.h
+++ b/Common/GPU/Vulkan/VulkanMemory.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
#include
#include
@@ -121,6 +122,15 @@ public:
return writePtr_ + off;
}
+ template
+ void PushUBOData(const T &data, VkDescriptorBufferInfo *info) {
+ uint32_t bindOffset;
+ void *ptr = PushAligned(sizeof(T), &bindOffset, &info->buffer, vulkan_->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment);
+ memcpy(ptr, &data, sizeof(T));
+ info->offset = bindOffset;
+ info->range = sizeof(T);
+ }
+
size_t GetTotalSize() const;
private:
@@ -153,7 +163,7 @@ public:
void Create(VulkanContext *vulkan, const VkDescriptorPoolCreateInfo &info, const std::vector &sizes);
// Allocate a new set, which may resize and empty the current sets.
// Use only for the current frame, unless in a cache cleared by clear_.
- VkDescriptorSet Allocate(int n, const VkDescriptorSetLayout *layouts);
+ VkDescriptorSet Allocate(int n, const VkDescriptorSetLayout *layouts, const char *tag);
void Reset();
void Destroy();
diff --git a/Common/GPU/Vulkan/VulkanProfiler.cpp b/Common/GPU/Vulkan/VulkanProfiler.cpp
index fccea843b8..6e3a959f30 100644
--- a/Common/GPU/Vulkan/VulkanProfiler.cpp
+++ b/Common/GPU/Vulkan/VulkanProfiler.cpp
@@ -40,7 +40,7 @@ void VulkanProfiler::BeginFrame(VulkanContext *vulkan, VkCommandBuffer firstComm
// Log it all out.
for (auto &scope : scopes_) {
if (scope.endQueryId == -1) {
- WARN_LOG(G3D, "Unclosed scope: %s", scope.name.c_str());
+ WARN_LOG(G3D, "Unclosed scope: %s", scope.name);
continue;
}
uint64_t startTime = results[scope.startQueryId];
@@ -50,7 +50,7 @@ void VulkanProfiler::BeginFrame(VulkanContext *vulkan, VkCommandBuffer firstComm
double milliseconds = (double)delta * timestampConversionFactor;
- INFO_LOG(G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name.c_str(), milliseconds);
+ INFO_LOG(G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name, milliseconds);
}
scopes_.clear();
@@ -73,14 +73,11 @@ void VulkanProfiler::Begin(VkCommandBuffer cmdBuf, VkPipelineStageFlagBits stage
return;
}
+ ProfilerScope scope;
va_list args;
va_start(args, fmt);
- char temp[512];
- vsnprintf(temp, sizeof(temp), fmt, args);
+ vsnprintf(scope.name, sizeof(scope.name), fmt, args);
va_end(args);
-
- ProfilerScope scope;
- scope.name = temp;
scope.startQueryId = numQueries_;
scope.endQueryId = -1;
scope.level = (int)scopeStack_.size();
diff --git a/Common/GPU/Vulkan/VulkanProfiler.h b/Common/GPU/Vulkan/VulkanProfiler.h
index d7b08d6738..e3d21c2021 100644
--- a/Common/GPU/Vulkan/VulkanProfiler.h
+++ b/Common/GPU/Vulkan/VulkanProfiler.h
@@ -13,7 +13,7 @@
// Put the whole thing in a FrameData to allow for overlap.
struct ProfilerScope {
- std::string name;
+ char name[52]; // to make a struct size of 64, just because
int startQueryId;
int endQueryId;
int level;
diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp
index b085adcef7..53f5615931 100644
--- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp
+++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp
@@ -3,6 +3,7 @@
#include "Common/GPU/DataFormat.h"
#include "Common/GPU/Vulkan/VulkanQueueRunner.h"
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
+#include "Common/VR/PPSSPPVR.h"
#include "Common/Log.h"
#include "Common/TimeUtil.h"
@@ -10,7 +11,7 @@ using namespace PPSSPP_VK;
// Debug help: adb logcat -s DEBUG PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState
-static void MergeRenderAreaRectInto(VkRect2D *dest, VkRect2D &src) {
+static void MergeRenderAreaRectInto(VkRect2D *dest, const VkRect2D &src) {
if (dest->offset.x > src.offset.x) {
dest->extent.width += (dest->offset.x - src.offset.x);
dest->offset.x = src.offset.x;
@@ -31,11 +32,13 @@ static void MergeRenderAreaRectInto(VkRect2D *dest, VkRect2D &src) {
RenderPassType MergeRPTypes(RenderPassType a, RenderPassType b) {
// Either both are backbuffer type, or neither are.
// These can't merge with other renderpasses
- if (a == RP_TYPE_BACKBUFFER || b == RP_TYPE_BACKBUFFER) {
+ if (a == RenderPassType::BACKBUFFER || b == RenderPassType::BACKBUFFER) {
_dbg_assert_(a == b);
return a;
}
+ _dbg_assert_((a & RenderPassType::MULTIVIEW) == (b & RenderPassType::MULTIVIEW));
+
// The rest we can just OR together to get the maximum feature set.
return (RenderPassType)((u32)a | (u32)b);
}
@@ -165,7 +168,7 @@ bool VulkanQueueRunner::CreateSwapchain(VkCommandBuffer cmdInit) {
color_image_view.subresourceRange.baseMipLevel = 0;
color_image_view.subresourceRange.levelCount = 1;
color_image_view.subresourceRange.baseArrayLayer = 0;
- color_image_view.subresourceRange.layerCount = 1;
+ color_image_view.subresourceRange.layerCount = 1; // TODO: Investigate hw-assisted stereo.
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
color_image_view.flags = 0;
color_image_view.image = sc_buffer.image;
@@ -175,6 +178,7 @@ bool VulkanQueueRunner::CreateSwapchain(VkCommandBuffer cmdInit) {
// Also, turns out it's illegal to transition un-acquired images, thanks Hans-Kristian. See #11417.
res = vkCreateImageView(vulkan_->GetDevice(), &color_image_view, nullptr, &sc_buffer.view);
+ vulkan_->SetDebugName(sc_buffer.view, VK_OBJECT_TYPE_IMAGE_VIEW, "swapchain_view");
swapchainImages_.push_back(sc_buffer);
_dbg_assert_(res == VK_SUCCESS);
}
@@ -194,7 +198,7 @@ bool VulkanQueueRunner::InitBackbufferFramebuffers(int width, int height) {
VkImageView attachments[2] = { VK_NULL_HANDLE, depth_.view };
VkFramebufferCreateInfo fb_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
- fb_info.renderPass = GetCompatibleRenderPass()->Get(vulkan_, RP_TYPE_BACKBUFFER);
+ fb_info.renderPass = GetCompatibleRenderPass()->Get(vulkan_, RenderPassType::BACKBUFFER, VK_SAMPLE_COUNT_1_BIT);
fb_info.attachmentCount = 2;
fb_info.pAttachments = attachments;
fb_info.width = width;
@@ -248,7 +252,7 @@ bool VulkanQueueRunner::InitDepthStencilBuffer(VkCommandBuffer cmd) {
vulkan_->SetDebugName(depth_.image, VK_OBJECT_TYPE_IMAGE, "BackbufferDepth");
- TransitionImageLayout2(cmd, depth_.image, 0, 1,
+ TransitionImageLayout2(cmd, depth_.image, 0, 1, 1,
aspectMask,
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
@@ -273,6 +277,7 @@ bool VulkanQueueRunner::InitDepthStencilBuffer(VkCommandBuffer cmd) {
VkDevice device = vulkan_->GetDevice();
res = vkCreateImageView(device, &depth_view_info, NULL, &depth_.view);
+ vulkan_->SetDebugName(depth_.view, VK_OBJECT_TYPE_IMAGE_VIEW, "depth_stencil_backbuffer");
_dbg_assert_(res == VK_SUCCESS);
if (res != VK_SUCCESS)
return false;
@@ -304,133 +309,6 @@ void VulkanQueueRunner::DestroyBackBuffers() {
INFO_LOG(G3D, "Backbuffers destroyed");
}
-static VkAttachmentLoadOp ConvertLoadAction(VKRRenderPassLoadAction action) {
- switch (action) {
- case VKRRenderPassLoadAction::CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR;
- case VKRRenderPassLoadAction::KEEP: return VK_ATTACHMENT_LOAD_OP_LOAD;
- case VKRRenderPassLoadAction::DONT_CARE: return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- }
- return VK_ATTACHMENT_LOAD_OP_DONT_CARE; // avoid compiler warning
-}
-
-static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
- switch (action) {
- case VKRRenderPassStoreAction::STORE: return VK_ATTACHMENT_STORE_OP_STORE;
- case VKRRenderPassStoreAction::DONT_CARE: return VK_ATTACHMENT_STORE_OP_DONT_CARE;
- }
- return VK_ATTACHMENT_STORE_OP_DONT_CARE; // avoid compiler warning
-}
-
-// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
-// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
-
-VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) {
- bool selfDependency = rpType == RP_TYPE_COLOR_INPUT || rpType == RP_TYPE_COLOR_DEPTH_INPUT;
- bool isBackbuffer = rpType == RP_TYPE_BACKBUFFER;
- bool hasDepth = rpType == RP_TYPE_BACKBUFFER || rpType == RP_TYPE_COLOR_DEPTH || rpType == RP_TYPE_COLOR_DEPTH_INPUT;
-
- VkAttachmentDescription attachments[2] = {};
- attachments[0].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
- attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
- attachments[0].loadOp = ConvertLoadAction(key.colorLoadAction);
- attachments[0].storeOp = ConvertStoreAction(key.colorStoreAction);
- attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
- attachments[0].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- attachments[0].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- attachments[0].flags = 0;
-
- if (hasDepth) {
- attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
- attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
- attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction);
- attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction);
- attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction);
- attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
- attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- attachments[1].flags = 0;
- }
-
- VkAttachmentReference color_reference{};
- color_reference.attachment = 0;
- color_reference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
- VkAttachmentReference depth_reference{};
- depth_reference.attachment = 1;
- depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-
- VkSubpassDescription subpass{};
- subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- subpass.flags = 0;
- if (selfDependency) {
- subpass.inputAttachmentCount = 1;
- subpass.pInputAttachments = &color_reference;
- } else {
- subpass.inputAttachmentCount = 0;
- subpass.pInputAttachments = nullptr;
- }
- subpass.colorAttachmentCount = 1;
- subpass.pColorAttachments = &color_reference;
- subpass.pResolveAttachments = nullptr;
- if (hasDepth) {
- subpass.pDepthStencilAttachment = &depth_reference;
- }
- subpass.preserveAttachmentCount = 0;
- subpass.pPreserveAttachments = nullptr;
-
- // Not sure if this is really necessary.
- VkSubpassDependency deps[2]{};
- size_t numDeps = 0;
-
- VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
- rp.attachmentCount = hasDepth ? 2 : 1;
- rp.pAttachments = attachments;
- rp.subpassCount = 1;
- rp.pSubpasses = &subpass;
-
- if (isBackbuffer) {
- deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL;
- deps[numDeps].dstSubpass = 0;
- deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- deps[numDeps].srcAccessMask = 0;
- deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- numDeps++;
- }
-
- if (selfDependency) {
- deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
- deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- deps[numDeps].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
- deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
- deps[numDeps].srcSubpass = 0;
- deps[numDeps].dstSubpass = 0;
- numDeps++;
- }
-
- if (numDeps > 0) {
- rp.dependencyCount = (u32)numDeps;
- rp.pDependencies = deps;
- }
-
- VkRenderPass pass;
- VkResult res = vkCreateRenderPass(vulkan->GetDevice(), &rp, nullptr, &pass);
- _assert_(res == VK_SUCCESS);
- _assert_(pass != VK_NULL_HANDLE);
- return pass;
-}
-
-VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType) {
- // When we create a render pass, we create all "types" of it immediately,
- // practical later when referring to it. Could change to on-demand if it feels motivated
- // but I think the render pass objects are cheap.
- if (!pass[(int)rpType]) {
- pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType);
- }
- return pass[(int)rpType];
-}
// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
@@ -456,6 +334,7 @@ void VulkanQueueRunner::SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags
img.image,
0,
1,
+ img.numLayers,
aspect,
VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_GENERAL,
@@ -546,7 +425,7 @@ void VulkanQueueRunner::PreprocessSteps(std::vector &steps) {
}
}
-void VulkanQueueRunner::RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared) {
+void VulkanQueueRunner::RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps) {
QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr;
if (profile)
@@ -624,12 +503,13 @@ void VulkanQueueRunner::RunSteps(std::vector &steps, FrameData &frame
// Deleting all in one go should be easier on the instruction cache than deleting
// them as we go - and easier to debug because we can look backwards in the frame.
- for (auto step : steps) {
- delete step;
+ if (!keepSteps) {
+ for (auto step : steps) {
+ delete step;
+ }
+ steps.clear();
}
- steps.clear();
-
if (profile)
profile->cpuEndTime = time_now_d();
}
@@ -873,6 +753,26 @@ const char *AspectToString(VkImageAspectFlags aspect) {
}
}
+static const char *rpTypeDebugNames[] = {
+ "RENDER",
+ "RENDER_DEPTH",
+ "RENDER_INPUT",
+ "RENDER_DEPTH_INPUT",
+ "MV_RENDER",
+ "MV_RENDER_DEPTH",
+ "MV_RENDER_INPUT",
+ "MV_RENDER_DEPTH_INPUT",
+ "MS_RENDER",
+ "MS_RENDER_DEPTH",
+ "MS_RENDER_INPUT",
+ "MS_RENDER_DEPTH_INPUT",
+ "MS_MV_RENDER",
+ "MS_MV_RENDER_DEPTH",
+ "MS_MV_RENDER_INPUT",
+ "MS_MV_RENDER_DEPTH_INPUT",
+ "BACKBUF",
+};
+
std::string VulkanQueueRunner::StepToString(const VKRStep &step) const {
char buffer[256];
switch (step.stepType) {
@@ -882,15 +782,7 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const {
int h = step.render.framebuffer ? step.render.framebuffer->height : vulkan_->GetBackbufferHeight();
int actual_w = step.render.renderArea.extent.width;
int actual_h = step.render.renderArea.extent.height;
- const char *renderCmd;
- switch (step.render.renderPassType) {
- case RP_TYPE_BACKBUFFER: renderCmd = "BACKBUF"; break;
- case RP_TYPE_COLOR: renderCmd = "RENDER"; break;
- case RP_TYPE_COLOR_DEPTH: renderCmd = "RENDER_DEPTH"; break;
- case RP_TYPE_COLOR_INPUT: renderCmd = "RENDER_INPUT"; break;
- case RP_TYPE_COLOR_DEPTH_INPUT: renderCmd = "RENDER_DEPTH_INPUT"; break;
- default: renderCmd = "N/A";
- }
+ const char *renderCmd = rpTypeDebugNames[(size_t)step.render.renderPassType];
snprintf(buffer, sizeof(buffer), "%s %s %s (draws: %d, %dx%d/%dx%d)", renderCmd, step.tag, step.render.framebuffer ? step.render.framebuffer->Tag() : "", step.render.numDraws, actual_w, actual_h, w, h);
break;
}
@@ -1113,6 +1005,12 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass, bool verbose) {
case VKRRenderCommand::PUSH_CONSTANTS:
INFO_LOG(G3D, " PushConstants(%d)", cmd.push.size);
break;
+ case VKRRenderCommand::DEBUG_ANNOTATION:
+ INFO_LOG(G3D, " DebugAnnotation(%s)", cmd.debugAnnotation.annotation);
+ break;
+ case VKRRenderCommand::BIND_DESCRIPTOR_SET:
+ INFO_LOG(G3D, " BindDescSet(%d)", cmd.bindDescSet.setNumber);
+ break;
case VKRRenderCommand::NUM_RENDER_COMMANDS:
break;
@@ -1140,7 +1038,7 @@ void VulkanQueueRunner::LogReadbackImage(const VKRStep &step) {
INFO_LOG(G3D, "%s", StepToString(step).c_str());
}
-void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout colorLayout, VkImage depthStencilImage, VkImageLayout depthStencilLayout, VulkanBarrier *recordBarrier) {
+void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout colorLayout, VkImage depthStencilImage, VkImageLayout depthStencilLayout, int numLayers, VulkanBarrier *recordBarrier) {
if (colorLayout != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
VkPipelineStageFlags srcStageMask = 0;
VkAccessFlags srcAccessMask = 0;
@@ -1173,7 +1071,7 @@ void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout
break;
}
recordBarrier->TransitionImage(
- colorImage, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
+ colorImage, 0, 1, numLayers, VK_IMAGE_ASPECT_COLOR_BIT,
colorLayout,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
srcAccessMask,
@@ -1209,7 +1107,7 @@ void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout
break;
}
recordBarrier->TransitionImage(
- depthStencilImage, 0, 1, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
+ depthStencilImage, 0, 1, numLayers, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
depthStencilLayout,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
srcAccessMask,
@@ -1219,7 +1117,7 @@ void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout
}
}
-void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout colorLayout, VkImage depthStencilImage, VkImageLayout depthStencilLayout) {
+void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout colorLayout, VkImage depthStencilImage, int numLayers, VkImageLayout depthStencilLayout) {
VkPipelineStageFlags srcStageMask = 0;
VkPipelineStageFlags dstStageMask = 0;
@@ -1261,7 +1159,7 @@ void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayou
barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier[0].subresourceRange.baseMipLevel = 0;
barrier[0].subresourceRange.levelCount = 1;
- barrier[0].subresourceRange.layerCount = 1;
+ barrier[0].subresourceRange.layerCount = numLayers;
barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrierCount++;
@@ -1300,7 +1198,7 @@ void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayou
barrier[barrierCount].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
barrier[barrierCount].subresourceRange.baseMipLevel = 0;
barrier[barrierCount].subresourceRange.levelCount = 1;
- barrier[barrierCount].subresourceRange.layerCount = 1;
+ barrier[barrierCount].subresourceRange.layerCount = numLayers;
barrier[barrierCount].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier[barrierCount].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrierCount++;
@@ -1318,6 +1216,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
iter.fb->color.image,
0,
1,
+ iter.fb->numLayers,
VK_IMAGE_ASPECT_COLOR_BIT,
iter.fb->color.layout,
iter.targetLayout
@@ -1328,6 +1227,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
iter.fb->depth.image,
0,
1,
+ iter.fb->numLayers,
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
iter.fb->depth.layout,
iter.targetLayout
@@ -1357,6 +1257,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
step.render.framebuffer->color.image,
0,
1,
+ step.render.framebuffer->numLayers,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
@@ -1371,6 +1272,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
step.render.framebuffer->depth.image,
0,
1,
+ step.render.framebuffer->numLayers,
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
@@ -1404,13 +1306,16 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
VkPipeline lastPipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
+ bool pipelineOK = false;
+
int lastStencilWriteMask = -1;
int lastStencilCompareMask = -1;
int lastStencilReference = -1;
const RenderPassType rpType = step.render.renderPassType;
- for (const auto &c : commands) {
+ for (size_t i = 0; i < commands.size(); i++) {
+ const VkRenderData &c = commands[i];
switch (c.cmd) {
case VKRRenderCommand::REMOVED:
break;
@@ -1419,26 +1324,39 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
{
VKRGraphicsPipeline *graphicsPipeline = c.graphics_pipeline.pipeline;
if (graphicsPipeline != lastGraphicsPipeline) {
- if (!graphicsPipeline->pipeline[rpType]) {
+ VkSampleCountFlagBits fbSampleCount = fb ? fb->sampleCount : VK_SAMPLE_COUNT_1_BIT;
+
+ if (RenderPassTypeHasMultisample(rpType) && fbSampleCount != graphicsPipeline->SampleCount()) {
+ // should have been invalidated.
+ _assert_msg_(graphicsPipeline->SampleCount() == VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM,
+ "expected %d sample count, got %d", fbSampleCount, graphicsPipeline->SampleCount());
+ }
+
+ if (!graphicsPipeline->pipeline[(size_t)rpType]) {
// NOTE: If render steps got merged, it can happen that, as they ended during recording,
// they didn't know their final render pass type so they created the wrong pipelines in EndCurRenderStep().
// Unfortunately I don't know if we can fix it in any more sensible place than here.
// Maybe a middle pass. But let's try to just block and compile here for now, this doesn't
// happen all that much.
- graphicsPipeline->pipeline[rpType] = Promise::CreateEmpty();
- graphicsPipeline->Create(vulkan_, renderPass->Get(vulkan_, rpType), rpType);
+ graphicsPipeline->pipeline[(size_t)rpType] = Promise::CreateEmpty();
+ graphicsPipeline->Create(vulkan_, renderPass->Get(vulkan_, rpType, fbSampleCount), rpType, fbSampleCount);
}
- VkPipeline pipeline = graphicsPipeline->pipeline[rpType]->BlockUntilReady();
+ VkPipeline pipeline = graphicsPipeline->pipeline[(size_t)rpType]->BlockUntilReady();
+
if (pipeline != VK_NULL_HANDLE) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
pipelineLayout = c.pipeline.pipelineLayout;
lastGraphicsPipeline = graphicsPipeline;
- // Reset dynamic state so it gets refreshed with the new pipeline.
- lastStencilWriteMask = -1;
- lastStencilCompareMask = -1;
- lastStencilReference = -1;
+ pipelineOK = true;
+ } else {
+ pipelineOK = false;
}
+
+ // Reset dynamic state so it gets refreshed with the new pipeline.
+ lastStencilWriteMask = -1;
+ lastStencilCompareMask = -1;
+ lastStencilReference = -1;
}
break;
}
@@ -1503,14 +1421,22 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
case VKRRenderCommand::SELF_DEPENDENCY_BARRIER:
{
_assert_(step.render.pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT);
+ _assert_(fb);
VulkanBarrier barrier;
- SelfDependencyBarrier(step.render.framebuffer->color, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
+ if (fb->sampleCount != VK_SAMPLE_COUNT_1_BIT) {
+ // Rendering is happening to the multisample buffer, not the color buffer.
+ SelfDependencyBarrier(fb->msaaColor, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
+ } else {
+ SelfDependencyBarrier(fb->color, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
+ }
barrier.Flush(cmd);
break;
}
case VKRRenderCommand::PUSH_CONSTANTS:
- vkCmdPushConstants(cmd, pipelineLayout, c.push.stages, c.push.offset, c.push.size, c.push.data);
+ if (pipelineOK) {
+ vkCmdPushConstants(cmd, pipelineLayout, c.push.stages, c.push.offset, c.push.size, c.push.data);
+ }
break;
case VKRRenderCommand::STENCIL:
@@ -1529,21 +1455,23 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
break;
case VKRRenderCommand::DRAW_INDEXED:
- {
- vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &c.drawIndexed.ds, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets);
- vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, (VkIndexType)c.drawIndexed.indexType);
- VkDeviceSize voffset = c.drawIndexed.voffset;
- vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset);
- vkCmdDrawIndexed(cmd, c.drawIndexed.count, c.drawIndexed.instances, 0, 0, 0);
+ if (pipelineOK) {
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &c.drawIndexed.ds, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets);
+ vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, (VkIndexType)c.drawIndexed.indexType);
+ VkDeviceSize voffset = c.drawIndexed.voffset;
+ vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset);
+ vkCmdDrawIndexed(cmd, c.drawIndexed.count, c.drawIndexed.instances, 0, 0, 0);
+ }
break;
- }
case VKRRenderCommand::DRAW:
- vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &c.draw.ds, c.draw.numUboOffsets, c.draw.uboOffsets);
- if (c.draw.vbuffer) {
- vkCmdBindVertexBuffers(cmd, 0, 1, &c.draw.vbuffer, &c.draw.voffset);
+ if (pipelineOK) {
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &c.draw.ds, c.draw.numUboOffsets, c.draw.uboOffsets);
+ if (c.draw.vbuffer) {
+ vkCmdBindVertexBuffers(cmd, 0, 1, &c.draw.vbuffer, &c.draw.voffset);
+ }
+ vkCmdDraw(cmd, c.draw.count, 1, c.draw.offset, 0);
}
- vkCmdDraw(cmd, c.draw.count, 1, c.draw.offset, 0);
break;
case VKRRenderCommand::CLEAR:
@@ -1552,7 +1480,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
int numAttachments = 0;
VkClearRect rc{};
rc.baseArrayLayer = 0;
- rc.layerCount = 1;
+ rc.layerCount = 1; // In multiview mode, 1 means to replicate to all the active layers.
rc.rect.extent.width = (uint32_t)curWidth;
rc.rect.extent.height = (uint32_t)curHeight;
VkClearAttachment attachments[2]{};
@@ -1588,15 +1516,20 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
}
break;
+ case VKRRenderCommand::BIND_DESCRIPTOR_SET:
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, c.bindDescSet.pipelineLayout, c.bindDescSet.setNumber, 1, &c.bindDescSet.set, 0, nullptr);
+ break;
+
default:
ERROR_LOG(G3D, "Unimpl queue command");
+ break;
}
}
vkCmdEndRenderPass(cmd);
if (fb) {
// If the desired final layout aren't the optimal layout for rendering, transition.
- TransitionFromOptimal(cmd, fb->color.image, step.render.finalColorLayout, fb->depth.image, step.render.finalDepthStencilLayout);
+ TransitionFromOptimal(cmd, fb->color.image, step.render.finalColorLayout, fb->depth.image, fb->numLayers, step.render.finalDepthStencilLayout);
fb->color.layout = step.render.finalColorLayout;
fb->depth.layout = step.render.finalDepthStencilLayout;
@@ -1606,13 +1539,15 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step, VkCommandBuffer cmd) {
VKRRenderPass *renderPass;
int numClearVals = 0;
- VkClearValue clearVal[2]{};
+ VkClearValue clearVal[4]{};
VkFramebuffer framebuf;
int w;
int h;
bool hasDepth = RenderPassTypeHasDepth(step.render.renderPassType);
+ VkSampleCountFlagBits sampleCount;
+
if (step.render.framebuffer) {
_dbg_assert_(step.render.finalColorLayout != VK_IMAGE_LAYOUT_UNDEFINED);
_dbg_assert_(step.render.finalDepthStencilLayout != VK_IMAGE_LAYOUT_UNDEFINED);
@@ -1625,6 +1560,8 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
VKRFramebuffer *fb = step.render.framebuffer;
framebuf = fb->Get(renderPass, step.render.renderPassType);
+ sampleCount = fb->sampleCount;
+ _dbg_assert_(framebuf != VK_NULL_HANDLE);
w = fb->width;
h = fb->height;
@@ -1636,7 +1573,7 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
step.render.colorLoad == VKRRenderPassLoadAction::CLEAR &&
vulkan_->GetPhysicalDeviceProperties().properties.driverVersion == 0xaa9c4b29;
if (maliBugWorkaround) {
- recordBarrier_.TransitionImage(step.render.framebuffer->color.image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
+ recordBarrier_.TransitionImage(fb->color.image, 0, 1, fb->numLayers, VK_IMAGE_ASPECT_COLOR_BIT,
fb->color.layout, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
@@ -1644,20 +1581,27 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
fb->color.layout = VK_IMAGE_LAYOUT_GENERAL;
}
- TransitionToOptimal(cmd, fb->color.image, fb->color.layout, fb->depth.image, fb->depth.layout, &recordBarrier_);
+ TransitionToOptimal(cmd, fb->color.image, fb->color.layout, fb->depth.image, fb->depth.layout, fb->numLayers, &recordBarrier_);
// The transition from the optimal format happens after EndRenderPass, now that we don't
// do it as part of the renderpass itself anymore.
+ if (sampleCount != VK_SAMPLE_COUNT_1_BIT) {
+ // We don't initialize values for these.
+ numClearVals = hasDepth ? 2 : 1; // Skip the resolve buffers, don't need to clear those.
+ }
if (step.render.colorLoad == VKRRenderPassLoadAction::CLEAR) {
- Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor);
- numClearVals = 1;
+ Uint8x4ToFloat4(clearVal[numClearVals].color.float32, step.render.clearColor);
}
- if (hasDepth && (step.render.depthLoad == VKRRenderPassLoadAction::CLEAR || step.render.stencilLoad == VKRRenderPassLoadAction::CLEAR)) {
- clearVal[1].depthStencil.depth = step.render.clearDepth;
- clearVal[1].depthStencil.stencil = step.render.clearStencil;
- numClearVals = 2;
+ numClearVals++;
+ if (hasDepth) {
+ if (step.render.depthLoad == VKRRenderPassLoadAction::CLEAR || step.render.stencilLoad == VKRRenderPassLoadAction::CLEAR) {
+ clearVal[numClearVals].depthStencil.depth = step.render.clearDepth;
+ clearVal[numClearVals].depthStencil.stencil = step.render.clearStencil;
+ }
+ numClearVals++;
}
+ _dbg_assert_(numClearVals != 3);
} else {
RPKey key{
VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR,
@@ -1665,7 +1609,11 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
};
renderPass = GetRenderPass(key);
- framebuf = backbuffer_;
+ if (IsVREnabled()) {
+ framebuf = (VkFramebuffer)BindVRFramebuffer();
+ } else {
+ framebuf = backbuffer_;
+ }
// Raw, rotated backbuffer size.
w = vulkan_->GetBackbufferWidth();
@@ -1675,10 +1623,11 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
numClearVals = hasDepth ? 2 : 1; // We might do depth-less backbuffer in the future, though doubtful of the value.
clearVal[1].depthStencil.depth = 0.0f;
clearVal[1].depthStencil.stencil = 0;
+ sampleCount = VK_SAMPLE_COUNT_1_BIT;
}
VkRenderPassBeginInfo rp_begin = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
- rp_begin.renderPass = renderPass->Get(vulkan_, step.render.renderPassType);
+ rp_begin.renderPass = renderPass->Get(vulkan_, step.render.renderPassType, sampleCount);
rp_begin.framebuffer = framebuf;
VkRect2D rc = step.render.renderArea;
@@ -1710,54 +1659,79 @@ void VulkanQueueRunner::PerformCopy(const VKRStep &step, VkCommandBuffer cmd) {
VKRFramebuffer *src = step.copy.src;
VKRFramebuffer *dst = step.copy.dst;
- VkImageCopy copy{};
- copy.srcOffset.x = step.copy.srcRect.offset.x;
- copy.srcOffset.y = step.copy.srcRect.offset.y;
- copy.srcOffset.z = 0;
- copy.srcSubresource.mipLevel = 0;
- copy.srcSubresource.layerCount = 1;
- copy.dstOffset.x = step.copy.dstPos.x;
- copy.dstOffset.y = step.copy.dstPos.y;
- copy.dstOffset.z = 0;
- copy.dstSubresource.mipLevel = 0;
- copy.dstSubresource.layerCount = 1;
- copy.extent.width = step.copy.srcRect.extent.width;
- copy.extent.height = step.copy.srcRect.extent.height;
- copy.extent.depth = 1;
+ int layerCount = std::min(step.copy.src->numLayers, step.copy.dst->numLayers);
+ _dbg_assert_(step.copy.src->numLayers >= step.copy.dst->numLayers);
+
+ // TODO: If dst covers exactly the whole destination, we can set up a UNDEFINED->TRANSFER_DST_OPTIMAL transition,
+ // which can potentially be more efficient.
// First source barriers.
if (step.copy.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
- if (src->color.layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
- SetupTransitionToTransferSrc(src->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
- }
- if (dst->color.layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
- SetupTransitionToTransferDst(dst->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
- }
+ SetupTransitionToTransferSrc(src->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
+ SetupTransitionToTransferDst(dst->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
}
// We can't copy only depth or only stencil unfortunately - or can we?.
if (step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
_dbg_assert_(src->depth.image != VK_NULL_HANDLE);
- if (src->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
- SetupTransitionToTransferSrc(src->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
- }
+ SetupTransitionToTransferSrc(src->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
if (dst->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
SetupTransitionToTransferDst(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
_dbg_assert_(dst->depth.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
} else {
- // Kingdom Hearts: Subsequent copies to the same depth buffer without any other use.
+ // Kingdom Hearts: Subsequent copies twice to the same depth buffer without any other use.
// Not super sure how that happens, but we need a barrier to pass sync validation.
SetupTransferDstWriteAfterWrite(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
}
}
+ recordBarrier_.Flush(cmd);
+
+ bool multisampled = src->sampleCount != VK_SAMPLE_COUNT_1_BIT && dst->sampleCount != VK_SAMPLE_COUNT_1_BIT;
+ if (multisampled) {
+ // If both the targets are multisampled, copy the msaa targets too.
+ // For that, we need to transition them from their normally permanent VK_*_ATTACHMENT_OPTIMAL layouts, and then back.
+ if (step.copy.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
+ SetupTransitionToTransferSrc(src->msaaColor, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
+ recordBarrier_.Flush(cmd);
+ SetupTransitionToTransferDst(dst->msaaColor, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
+ recordBarrier_.Flush(cmd);
+ }
+ if (step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ // Kingdom Hearts: Subsequent copies to the same depth buffer without any other use.
+ // Not super sure how that happens, but we need a barrier to pass sync validation.
+ SetupTransitionToTransferSrc(src->msaaDepth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
+ recordBarrier_.Flush(cmd);
+ SetupTransitionToTransferDst(dst->msaaDepth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
+ recordBarrier_.Flush(cmd);
+ }
+ }
recordBarrier_.Flush(cmd);
+ VkImageCopy copy{};
+ copy.srcOffset.x = step.copy.srcRect.offset.x;
+ copy.srcOffset.y = step.copy.srcRect.offset.y;
+ copy.srcOffset.z = 0;
+ copy.srcSubresource.mipLevel = 0;
+ copy.srcSubresource.layerCount = layerCount;
+ copy.dstOffset.x = step.copy.dstPos.x;
+ copy.dstOffset.y = step.copy.dstPos.y;
+ copy.dstOffset.z = 0;
+ copy.dstSubresource.mipLevel = 0;
+ copy.dstSubresource.layerCount = layerCount;
+ copy.extent.width = step.copy.srcRect.extent.width;
+ copy.extent.height = step.copy.srcRect.extent.height;
+ copy.extent.depth = 1;
+
if (step.copy.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
copy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
vkCmdCopyImage(cmd, src->color.image, src->color.layout, dst->color.image, dst->color.layout, 1, ©);
+
+ if (multisampled) {
+ vkCmdCopyImage(cmd, src->msaaColor.image, src->msaaColor.layout, dst->msaaColor.image, dst->msaaColor.layout, 1, ©);
+ }
}
if (step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
_dbg_assert_(src->depth.image != VK_NULL_HANDLE);
@@ -1765,6 +1739,75 @@ void VulkanQueueRunner::PerformCopy(const VKRStep &step, VkCommandBuffer cmd) {
copy.srcSubresource.aspectMask = step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
copy.dstSubresource.aspectMask = step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
vkCmdCopyImage(cmd, src->depth.image, src->depth.layout, dst->depth.image, dst->depth.layout, 1, ©);
+
+ if (multisampled) {
+ vkCmdCopyImage(cmd, src->msaaDepth.image, src->msaaDepth.layout, dst->msaaDepth.image, dst->msaaDepth.layout, 1, ©);
+ }
+ }
+
+ if (multisampled) {
+ // Transition the MSAA surfaces back to optimal.
+ if (step.copy.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
+ recordBarrier_.TransitionImage(
+ src->msaaColor.image,
+ 0,
+ 1,
+ src->msaaColor.numLayers,
+ VK_IMAGE_ASPECT_COLOR_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ VK_ACCESS_TRANSFER_READ_BIT,
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
+ );
+ recordBarrier_.TransitionImage(
+ dst->msaaColor.image,
+ 0,
+ 1,
+ dst->msaaColor.numLayers,
+ VK_IMAGE_ASPECT_COLOR_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
+ );
+ src->msaaColor.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ dst->msaaColor.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ }
+ if (step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ recordBarrier_.TransitionImage(
+ src->msaaDepth.image,
+ 0,
+ 1,
+ src->msaaDepth.numLayers,
+ VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ VK_ACCESS_TRANSFER_READ_BIT,
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
+ );
+ recordBarrier_.TransitionImage(
+ dst->msaaDepth.image,
+ 0,
+ 1,
+ dst->msaaDepth.numLayers,
+ VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
+ );
+ src->msaaDepth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ dst->msaaDepth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ }
+ recordBarrier_.Flush(cmd);
}
}
@@ -1772,9 +1815,29 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) {
// The barrier code doesn't handle this case. We'd need to transition to GENERAL to do an intra-image copy.
_dbg_assert_(step.blit.src != step.blit.dst);
+ int layerCount = std::min(step.blit.src->numLayers, step.blit.dst->numLayers);
+ _dbg_assert_(step.blit.src->numLayers >= step.blit.dst->numLayers);
+
VKRFramebuffer *src = step.blit.src;
VKRFramebuffer *dst = step.blit.dst;
+ // First source barriers.
+ if (step.blit.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
+ SetupTransitionToTransferSrc(src->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
+ SetupTransitionToTransferDst(dst->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
+ }
+
+ // We can't copy only depth or only stencil unfortunately.
+ if (step.blit.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ _dbg_assert_(src->depth.image != VK_NULL_HANDLE);
+ _dbg_assert_(dst->depth.image != VK_NULL_HANDLE);
+
+ SetupTransitionToTransferSrc(src->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
+ SetupTransitionToTransferDst(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
+ }
+
+ recordBarrier_.Flush(cmd);
+
// If any validation needs to be performed here, it should probably have been done
// already when the blit was queued. So don't validate here.
VkImageBlit blit{};
@@ -1785,7 +1848,7 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) {
blit.srcOffsets[1].y = step.blit.srcRect.offset.y + step.blit.srcRect.extent.height;
blit.srcOffsets[1].z = 1;
blit.srcSubresource.mipLevel = 0;
- blit.srcSubresource.layerCount = 1;
+ blit.srcSubresource.layerCount = layerCount;
blit.dstOffsets[0].x = step.blit.dstRect.offset.x;
blit.dstOffsets[0].y = step.blit.dstRect.offset.y;
blit.dstOffsets[0].z = 0;
@@ -1793,38 +1856,7 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) {
blit.dstOffsets[1].y = step.blit.dstRect.offset.y + step.blit.dstRect.extent.height;
blit.dstOffsets[1].z = 1;
blit.dstSubresource.mipLevel = 0;
- blit.dstSubresource.layerCount = 1;
-
- VkPipelineStageFlags srcStage = 0;
- VkPipelineStageFlags dstStage = 0;
-
- int srcCount = 0;
- int dstCount = 0;
-
- // First source barriers.
- if (step.blit.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
- if (src->color.layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
- SetupTransitionToTransferSrc(src->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
- }
- if (dst->color.layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
- SetupTransitionToTransferDst(dst->color, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
- }
- }
-
- // We can't copy only depth or only stencil unfortunately.
- if (step.blit.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
- _dbg_assert_(src->depth.image != VK_NULL_HANDLE);
- _dbg_assert_(dst->depth.image != VK_NULL_HANDLE);
-
- if (src->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
- SetupTransitionToTransferSrc(src->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
- }
- if (dst->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
- SetupTransitionToTransferDst(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
- }
- }
-
- recordBarrier_.Flush(cmd);
+ blit.dstSubresource.layerCount = layerCount;
if (step.blit.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
@@ -1850,6 +1882,9 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) {
}
void VulkanQueueRunner::SetupTransitionToTransferSrc(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier) {
+ if (img.layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
+ return;
+ }
VkImageAspectFlags imageAspect = aspect;
VkAccessFlags srcAccessMask = 0;
VkPipelineStageFlags srcStageMask = 0;
@@ -1886,6 +1921,7 @@ void VulkanQueueRunner::SetupTransitionToTransferSrc(VKRImage &img, VkImageAspec
img.image,
0,
1,
+ img.numLayers,
imageAspect,
img.layout,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
@@ -1898,6 +1934,9 @@ void VulkanQueueRunner::SetupTransitionToTransferSrc(VKRImage &img, VkImageAspec
}
void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier) {
+ if (img.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+ return;
+ }
VkImageAspectFlags imageAspect = aspect;
VkAccessFlags srcAccessMask = 0;
VkPipelineStageFlags srcStageMask = 0;
@@ -1934,6 +1973,7 @@ void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageAspec
img.image,
0,
1,
+ img.numLayers,
aspect,
img.layout,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
@@ -1963,6 +2003,7 @@ void VulkanQueueRunner::SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAs
img.image,
0,
1,
+ img.numLayers,
aspect,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
@@ -1991,7 +2032,8 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd
if (step.readback.src == nullptr) {
// We only take screenshots after the main render pass (anything else would be stupid) so we need to transition out of PRESENT,
// and then back into it.
- TransitionImageLayout2(cmd, backbufferImage_, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
+ // Regarding layers, backbuffer currently only has one layer.
+ TransitionImageLayout2(cmd, backbufferImage_, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, VK_ACCESS_TRANSFER_READ_BIT);
@@ -2025,7 +2067,8 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd
if (step.readback.src == nullptr) {
// We only take screenshots after the main render pass (anything else would be stupid) so we need to transition out of PRESENT,
// and then back into it.
- TransitionImageLayout2(cmd, backbufferImage_, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
+ // Regarding layers, backbuffer currently only has one layer.
+ TransitionImageLayout2(cmd, backbufferImage_, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_ACCESS_TRANSFER_READ_BIT, 0);
@@ -2038,6 +2081,7 @@ void VulkanQueueRunner::PerformReadbackImage(const VKRStep &step, VkCommandBuffe
VKRImage srcImage{};
srcImage.image = step.readback_image.image;
srcImage.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ srcImage.numLayers = 1;
SetupTransitionToTransferSrc(srcImage, VK_IMAGE_ASPECT_COLOR_BIT, &recordBarrier_);
recordBarrier_.Flush(cmd);
@@ -2056,7 +2100,7 @@ void VulkanQueueRunner::PerformReadbackImage(const VKRStep &step, VkCommandBuffe
vkCmdCopyImageToBuffer(cmd, step.readback_image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readbackBuffer_, 1, ®ion);
// Now transfer it back to a texture.
- TransitionImageLayout2(cmd, step.readback_image.image, 0, 1,
+ TransitionImageLayout2(cmd, step.readback_image.image, 0, 1, 1, // I don't think we have any multilayer cases for regular textures. Above in PerformReadback, though..
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h
index 396a37edae..0d5f5053ae 100644
--- a/Common/GPU/Vulkan/VulkanQueueRunner.h
+++ b/Common/GPU/Vulkan/VulkanQueueRunner.h
@@ -9,6 +9,7 @@
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanBarrier.h"
#include "Common/GPU/Vulkan/VulkanFrameData.h"
+#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Data/Collections/TinySet.h"
#include "Common/GPU/DataFormat.h"
@@ -39,42 +40,21 @@ enum class VKRRenderCommand : uint8_t {
PUSH_CONSTANTS,
SELF_DEPENDENCY_BARRIER,
DEBUG_ANNOTATION,
+ BIND_DESCRIPTOR_SET,
NUM_RENDER_COMMANDS,
};
-enum class PipelineFlags {
+enum class PipelineFlags : u8 {
NONE = 0,
USES_BLEND_CONSTANT = (1 << 1),
USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers.
USES_INPUT_ATTACHMENT = (1 << 3),
USES_GEOMETRY_SHADER = (1 << 4),
+ USES_MULTIVIEW = (1 << 5), // Inherited from the render pass it was created with.
+ USES_DISCARD = (1 << 6),
};
ENUM_CLASS_BITOPS(PipelineFlags);
-// Pipelines need to be created for the right type of render pass.
-enum RenderPassType {
- // These four are organized so that bit 0 is DEPTH and bit 1 is INPUT, so
- // they can be OR-ed together in MergeRPTypes.
- RP_TYPE_COLOR,
- RP_TYPE_COLOR_DEPTH,
- RP_TYPE_COLOR_INPUT,
- RP_TYPE_COLOR_DEPTH_INPUT,
-
- // This is the odd one out, and gets special handling in MergeRPTypes.
- RP_TYPE_BACKBUFFER, // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible.
-
- // Later will add pure-color render passes.
- RP_TYPE_COUNT,
-};
-
-inline bool RenderPassTypeHasDepth(RenderPassType type) {
- return type == RP_TYPE_BACKBUFFER || type == RP_TYPE_COLOR_DEPTH || type == RP_TYPE_COLOR_DEPTH_INPUT;
-}
-
-inline bool RenderPassTypeHasInput(RenderPassType type) {
- return type == RP_TYPE_COLOR_INPUT || type == RP_TYPE_COLOR_DEPTH_INPUT;
-}
-
struct VkRenderData {
VKRRenderCommand cmd;
union {
@@ -103,7 +83,7 @@ struct VkRenderData {
VkDescriptorSet ds;
int numUboOffsets;
uint32_t uboOffsets[3];
- VkBuffer vbuffer; // might need to increase at some point
+ VkBuffer vbuffer;
VkBuffer ibuffer;
uint32_t voffset;
uint32_t ioffset;
@@ -140,6 +120,11 @@ struct VkRenderData {
struct {
const char *annotation;
} debugAnnotation;
+ struct {
+ int setNumber;
+ VkDescriptorSet set;
+ VkPipelineLayout pipelineLayout;
+ } bindDescSet;
};
};
@@ -152,18 +137,6 @@ enum class VKRStepType : uint8_t {
READBACK_IMAGE,
};
-// Must be the same order as Draw::RPAction
-enum class VKRRenderPassLoadAction : uint8_t {
- KEEP, // default. avoid when possible.
- CLEAR,
- DONT_CARE,
-};
-
-enum class VKRRenderPassStoreAction : uint8_t {
- STORE, // default. avoid when possible.
- DONT_CARE,
-};
-
struct TransitionRequest {
VKRFramebuffer *fb;
VkImageAspectFlags aspect; // COLOR or DEPTH
@@ -236,34 +209,6 @@ struct VKRStep {
};
};
-struct RPKey {
- // Only render-pass-compatibility-volatile things can be here.
- VKRRenderPassLoadAction colorLoadAction;
- VKRRenderPassLoadAction depthLoadAction;
- VKRRenderPassLoadAction stencilLoadAction;
- VKRRenderPassStoreAction colorStoreAction;
- VKRRenderPassStoreAction depthStoreAction;
- VKRRenderPassStoreAction stencilStoreAction;
-};
-
-class VKRRenderPass {
-public:
- VKRRenderPass(const RPKey &key) : key_(key) {}
-
- VkRenderPass Get(VulkanContext *vulkan, RenderPassType rpType);
- void Destroy(VulkanContext *vulkan) {
- for (int i = 0; i < RP_TYPE_COUNT; i++) {
- if (pass[i]) {
- vulkan->Delete().QueueDeleteRenderPass(pass[i]);
- }
- }
- }
-
-private:
- VkRenderPass pass[RP_TYPE_COUNT]{};
- RPKey key_;
-};
-
// These are enqueued from the main thread,
// and the render thread pops them off
struct VKRRenderThreadTask {
@@ -282,7 +227,7 @@ public:
}
void PreprocessSteps(std::vector &steps);
- void RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared);
+ void RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps = false);
void LogSteps(const std::vector &steps, bool verbose);
std::string StepToString(const VKRStep &step) const;
diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp
index da43a85d7e..6eb24de9ef 100644
--- a/Common/GPU/Vulkan/VulkanRenderManager.cpp
+++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp
@@ -12,6 +12,7 @@
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
#include "Common/Thread/ThreadUtil.h"
+#include "Common/VR/PPSSPPVR.h"
#if 0 // def _DEBUG
#define VLOG(...) NOTICE_LOG(G3D, __VA_ARGS__)
@@ -26,7 +27,16 @@
using namespace PPSSPP_VK;
// renderPass is an example of the "compatibility class" or RenderPassType type.
-bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType) {
+bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType, VkSampleCountFlagBits sampleCount) {
+ bool multisample = RenderPassTypeHasMultisample(rpType);
+ if (multisample) {
+ if (sampleCount_ != VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM) {
+ _assert_(sampleCount == sampleCount_);
+ } else {
+ sampleCount_ = sampleCount;
+ }
+ }
+
// Fill in the last part of the desc since now it's time to block.
VkShaderModule vs = desc->vertexShader->BlockUntilReady();
VkShaderModule fs = desc->fragmentShader->BlockUntilReady();
@@ -38,6 +48,12 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR
return false;
}
+ if (!compatibleRenderPass) {
+ ERROR_LOG(G3D, "Failed creating graphics pipeline - compatible render pass was null");
+ // We're kinda screwed here?
+ return false;
+ }
+
uint32_t stageCount = 2;
VkPipelineShaderStageCreateInfo ss[3]{};
ss[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
@@ -68,13 +84,24 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR
pipe.pDepthStencilState = &desc->dss;
pipe.pRasterizationState = &desc->rs;
+ VkPipelineMultisampleStateCreateInfo ms{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
+ ms.rasterizationSamples = multisample ? sampleCount : VK_SAMPLE_COUNT_1_BIT;
+ if (multisample && (flags_ & PipelineFlags::USES_DISCARD)) {
+ // Extreme quality
+ ms.sampleShadingEnable = true;
+ ms.minSampleShading = 1.0f;
+ }
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
+ inputAssembly.topology = desc->topology;
+
// We will use dynamic viewport state.
pipe.pVertexInputState = &desc->vis;
pipe.pViewportState = &desc->views;
pipe.pTessellationState = nullptr;
pipe.pDynamicState = &desc->ds;
- pipe.pInputAssemblyState = &desc->inputAssembly;
- pipe.pMultisampleState = &desc->ms;
+ pipe.pInputAssemblyState = &inputAssembly;
+ pipe.pMultisampleState = &ms;
pipe.layout = desc->pipelineLayout;
pipe.basePipelineHandle = VK_NULL_HANDLE;
pipe.basePipelineIndex = 0;
@@ -83,8 +110,13 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR
double start = time_now_d();
VkPipeline vkpipeline;
VkResult result = vkCreateGraphicsPipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &pipe, nullptr, &vkpipeline);
+ double taken_ms = (time_now_d() - start) * 1000.0;
- INFO_LOG(G3D, "Pipeline creation time: %0.2f ms", (time_now_d() - start) * 1000.0);
+ if (taken_ms < 0.1) {
+ DEBUG_LOG(G3D, "Pipeline creation time: %0.2f ms (fast) rpType: %08x sampleBits: %d\n(%s)", taken_ms, (u32)rpType, (u32)sampleCount, tag_.c_str());
+ } else {
+ INFO_LOG(G3D, "Pipeline creation time: %0.2f ms rpType: %08x sampleBits: %d\n(%s)", taken_ms, (u32)rpType, (u32)sampleCount, tag_.c_str());
+ }
bool success = true;
if (result == VK_INCOMPLETE) {
@@ -92,30 +124,45 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR
// Would really like to log more here, we could probably attach more info to desc.
//
// At least create a null placeholder to avoid creating over and over if something is broken.
- pipeline[rpType]->Post(VK_NULL_HANDLE);
+ pipeline[(size_t)rpType]->Post(VK_NULL_HANDLE);
+ ERROR_LOG(G3D, "Failed creating graphics pipeline! VK_INCOMPLETE");
+ LogCreationFailure();
success = false;
} else if (result != VK_SUCCESS) {
- pipeline[rpType]->Post(VK_NULL_HANDLE);
+ pipeline[(size_t)rpType]->Post(VK_NULL_HANDLE);
ERROR_LOG(G3D, "Failed creating graphics pipeline! result='%s'", VulkanResultToString(result));
+ LogCreationFailure();
success = false;
} else {
// Success!
- if (!tag.empty()) {
- vulkan->SetDebugName(vkpipeline, VK_OBJECT_TYPE_PIPELINE, tag.c_str());
+ if (!tag_.empty()) {
+ vulkan->SetDebugName(vkpipeline, VK_OBJECT_TYPE_PIPELINE, tag_.c_str());
}
- pipeline[rpType]->Post(vkpipeline);
+ pipeline[(size_t)rpType]->Post(vkpipeline);
}
return success;
}
-void VKRGraphicsPipeline::QueueForDeletion(VulkanContext *vulkan) {
- for (int i = 0; i < RP_TYPE_COUNT; i++) {
- if (!pipeline[i])
+void VKRGraphicsPipeline::DestroyVariants(VulkanContext *vulkan, bool msaaOnly) {
+ for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
+ if (!this->pipeline[i])
continue;
+ if (msaaOnly && (i & (int)RenderPassType::MULTISAMPLE) == 0)
+ continue;
+
VkPipeline pipeline = this->pipeline[i]->BlockUntilReady();
- vulkan->Delete().QueueDeletePipeline(pipeline);
+ // pipeline can be nullptr here, if it failed to compile before.
+ if (pipeline) {
+ vulkan->Delete().QueueDeletePipeline(pipeline);
+ }
+ this->pipeline[i] = nullptr;
}
+ sampleCount_ = VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM;
+}
+
+void VKRGraphicsPipeline::QueueForDeletion(VulkanContext *vulkan) {
+ DestroyVariants(vulkan, false);
vulkan->Delete().QueueCallback([](void *p) {
VKRGraphicsPipeline *pipeline = (VKRGraphicsPipeline *)p;
delete pipeline;
@@ -124,7 +171,7 @@ void VKRGraphicsPipeline::QueueForDeletion(VulkanContext *vulkan) {
u32 VKRGraphicsPipeline::GetVariantsBitmask() const {
u32 bitmask = 0;
- for (int i = 0; i < RP_TYPE_COUNT; i++) {
+ for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
if (pipeline[i]) {
bitmask |= 1 << i;
}
@@ -132,6 +179,16 @@ u32 VKRGraphicsPipeline::GetVariantsBitmask() const {
return bitmask;
}
+void VKRGraphicsPipeline::LogCreationFailure() const {
+ ERROR_LOG(G3D, "vs: %s\n[END VS]", desc->vertexShaderSource.c_str());
+ ERROR_LOG(G3D, "fs: %s\n[END FS]", desc->fragmentShaderSource.c_str());
+ if (desc->geometryShader) {
+ ERROR_LOG(G3D, "gs: %s\n[END GS]", desc->geometryShaderSource.c_str());
+ }
+ // TODO: Maybe log various other state?
+ ERROR_LOG(G3D, "======== END OF PIPELINE ==========");
+}
+
bool VKRComputePipeline::Create(VulkanContext *vulkan) {
if (!desc) {
// Already failed to create this one.
@@ -154,173 +211,6 @@ bool VKRComputePipeline::Create(VulkanContext *vulkan) {
return success;
}
-VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, bool createDepthStencilBuffer, const char *tag) : vulkan_(vk), tag_(tag) {
- width = _width;
- height = _height;
-
- _dbg_assert_(tag);
-
- CreateImage(vulkan_, initCmd, color, width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag);
- if (createDepthStencilBuffer) {
- CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
- }
-
- UpdateTag(tag);
-
- // We create the actual framebuffer objects on demand, because some combinations might not make sense.
- // Framebuffer objects are just pointers to a set of images, so no biggie.
-}
-
-void VKRFramebuffer::UpdateTag(const char *newTag) {
- char name[128];
- snprintf(name, sizeof(name), "fb_color_%s", tag_.c_str());
- vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, name);
- vulkan_->SetDebugName(color.imageView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
- if (depth.image) {
- snprintf(name, sizeof(name), "fb_depth_%s", tag_.c_str());
- vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, name);
- vulkan_->SetDebugName(depth.imageView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
- }
- for (int rpType = 0; rpType < RP_TYPE_COUNT; rpType++) {
- if (framebuf[rpType]) {
- snprintf(name, sizeof(name), "fb_%s", tag_.c_str());
- vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, name);
- }
- }
-}
-
-VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) {
- if (framebuf[(int)rpType]) {
- return framebuf[(int)rpType];
- }
-
- VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
- VkImageView views[2]{};
-
- bool hasDepth = rpType == RP_TYPE_BACKBUFFER || rpType == RP_TYPE_COLOR_DEPTH || rpType == RP_TYPE_COLOR_DEPTH_INPUT;
-
- views[0] = color.imageView;
- if (hasDepth) {
- _dbg_assert_(depth.imageView != VK_NULL_HANDLE);
- views[1] = depth.imageView;
- }
- fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType);
- fbci.attachmentCount = hasDepth ? 2 : 1;
- fbci.pAttachments = views;
- fbci.width = width;
- fbci.height = height;
- fbci.layers = 1;
-
- VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)rpType]);
- _assert_(res == VK_SUCCESS);
-
- if (!tag_.empty() && vulkan_->Extensions().EXT_debug_utils) {
- vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str());
- }
-
- return framebuf[(int)rpType];
-}
-
-VKRFramebuffer::~VKRFramebuffer() {
- if (color.imageView)
- vulkan_->Delete().QueueDeleteImageView(color.imageView);
- if (depth.imageView)
- vulkan_->Delete().QueueDeleteImageView(depth.imageView);
- if (color.image) {
- _dbg_assert_(color.alloc);
- vulkan_->Delete().QueueDeleteImageAllocation(color.image, color.alloc);
- }
- if (depth.image) {
- _dbg_assert_(depth.alloc);
- vulkan_->Delete().QueueDeleteImageAllocation(depth.image, depth.alloc);
- }
- if (depth.depthSampleView)
- vulkan_->Delete().QueueDeleteImageView(depth.depthSampleView);
- for (auto &fb : framebuf) {
- if (fb)
- vulkan_->Delete().QueueDeleteFramebuffer(fb);
- }
-}
-
-void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) {
- VkImageCreateInfo ici{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
- ici.arrayLayers = 1;
- ici.mipLevels = 1;
- ici.extent.width = width;
- ici.extent.height = height;
- ici.extent.depth = 1;
- ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
- ici.imageType = VK_IMAGE_TYPE_2D;
- ici.samples = VK_SAMPLE_COUNT_1_BIT;
- ici.tiling = VK_IMAGE_TILING_OPTIMAL;
- ici.format = format;
- // Strictly speaking we don't yet need VK_IMAGE_USAGE_SAMPLED_BIT for depth buffers since we do not yet sample depth buffers.
- ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
- if (color) {
- ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
- } else {
- ici.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
- }
-
- VmaAllocationCreateInfo allocCreateInfo{};
- allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
- VmaAllocationInfo allocInfo{};
-
- VkResult res = vmaCreateImage(vulkan->Allocator(), &ici, &allocCreateInfo, &img.image, &img.alloc, &allocInfo);
- _dbg_assert_(res == VK_SUCCESS);
-
- VkImageAspectFlags aspects = color ? VK_IMAGE_ASPECT_COLOR_BIT : (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
-
- VkImageViewCreateInfo ivci{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
- ivci.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
- ivci.format = ici.format;
- ivci.image = img.image;
- ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
- ivci.subresourceRange.aspectMask = aspects;
- ivci.subresourceRange.layerCount = 1;
- ivci.subresourceRange.levelCount = 1;
- res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.imageView);
- _dbg_assert_(res == VK_SUCCESS);
-
- // Separate view for texture sampling that only exposes depth.
- if (!color) {
- ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
- res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.depthSampleView);
- _dbg_assert_(res == VK_SUCCESS);
- } else {
- img.depthSampleView = VK_NULL_HANDLE;
- }
-
- VkPipelineStageFlags dstStage;
- VkAccessFlagBits dstAccessMask;
- switch (initialLayout) {
- case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
- dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- break;
- case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
- dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
- dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
- break;
- case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
- dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
- dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
- break;
- default:
- Crash();
- return;
- }
-
- TransitionImageLayout2(cmd, img.image, 0, 1, aspects,
- VK_IMAGE_LAYOUT_UNDEFINED, initialLayout,
- VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, dstStage,
- 0, dstAccessMask);
- img.layout = initialLayout;
-
- img.format = format;
- img.tag = tag ? tag : "N/A";
-}
-
VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan)
: vulkan_(vulkan), queueRunner_(vulkan),
initTimeMs_("initTimeMs"),
@@ -472,23 +362,26 @@ void VulkanRenderManager::CompileThreadFunc() {
break;
}
- if (!toCompile.empty()) {
- INFO_LOG(G3D, "Compilation thread has %d pipelines to create", (int)toCompile.size());
- }
-
+ double time = time_now_d();
// TODO: Here we can sort the pending pipelines by vertex and fragment shaders,
// and split up further.
// Those with the same pairs of shaders should be on the same thread.
for (auto &entry : toCompile) {
switch (entry.type) {
case CompileQueueEntry::Type::GRAPHICS:
- entry.graphics->Create(vulkan_, entry.compatibleRenderPass, entry.renderPassType);
+ entry.graphics->Create(vulkan_, entry.compatibleRenderPass, entry.renderPassType, entry.sampleCount);
break;
case CompileQueueEntry::Type::COMPUTE:
entry.compute->Create(vulkan_);
break;
}
}
+
+ double delta = time_now_d() - time;
+ if (delta > 0.005f) {
+ INFO_LOG(G3D, "CompileThreadFunc: Creating %d pipelines took %0.3f ms", (int)toCompile.size(), delta * 1000.0f);
+ }
+
queueRunner_.NotifyCompileDone();
}
}
@@ -611,10 +504,8 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
// Must be after the fence - this performs deletes.
VLOG("PUSH: BeginFrame %d", curFrame);
- vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE);
-
insideFrame_ = true;
- renderStepOffset_ = 0;
+ vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE);
frameData.profile.timestampDescriptions.clear();
if (frameData.profilingEnabled_) {
@@ -633,12 +524,12 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() {
return frameData_[curFrame].GetInitCmd(vulkan_);
}
-VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag) {
- VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline();
+VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, VkSampleCountFlagBits sampleCount, const char *tag) {
+ VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline(pipelineFlags, tag);
_dbg_assert_(desc->vertexShader);
_dbg_assert_(desc->fragmentShader);
pipeline->desc = desc;
- pipeline->tag = tag;
+ pipeline->desc->AddRef();
if (curRenderStep_) {
// The common case
pipelinesToCheck_.push_back(pipeline);
@@ -655,27 +546,28 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe
VKRRenderPass *compatibleRenderPass = queueRunner_.GetRenderPass(key);
compileMutex_.lock();
bool needsCompile = false;
- for (int i = 0; i < RP_TYPE_COUNT; i++) {
+ for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
if (!(variantBitmask & (1 << i)))
continue;
RenderPassType rpType = (RenderPassType)i;
// Sanity check - don't compile incompatible types (could be caused by corrupt caches, changes in data structures, etc).
- if (pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) {
- if (!RenderPassTypeHasDepth(rpType)) {
- WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type");
- continue;
- }
+ if ((pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) && !RenderPassTypeHasDepth(rpType)) {
+ WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type");
+ continue;
}
- if (pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) {
- if (!RenderPassTypeHasInput(rpType)) {
- WARN_LOG(G3D, "Not compiling pipeline that requires input attachment, for non input renderpass type");
- continue;
- }
+ if ((pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) && !RenderPassTypeHasInput(rpType)) {
+ WARN_LOG(G3D, "Not compiling pipeline that requires input attachment, for non input renderpass type");
+ continue;
+ }
+ // Shouldn't hit this, these should have been filtered elsewhere. However, still a good check to do.
+ if (sampleCount == VK_SAMPLE_COUNT_1_BIT && RenderPassTypeHasMultisample(rpType)) {
+ WARN_LOG(G3D, "Not compiling single sample pipeline for a multisampled render pass type");
+ continue;
}
- pipeline->pipeline[rpType] = Promise::CreateEmpty();
- compileQueue_.push_back(CompileQueueEntry(pipeline, compatibleRenderPass->Get(vulkan_, rpType), rpType));
+ pipeline->pipeline[i] = Promise::CreateEmpty();
+ compileQueue_.push_back(CompileQueueEntry(pipeline, compatibleRenderPass->Get(vulkan_, rpType, sampleCount), rpType, sampleCount));
needsCompile = true;
}
if (needsCompile)
@@ -707,24 +599,43 @@ void VulkanRenderManager::EndCurRenderStep() {
// We'll often be able to avoid loading/saving the depth/stencil buffer.
curRenderStep_->render.pipelineFlags = curPipelineFlags_;
bool depthStencil = (curPipelineFlags_ & PipelineFlags::USES_DEPTH_STENCIL) != 0;
- RenderPassType rpType = depthStencil ? RP_TYPE_COLOR_DEPTH : RP_TYPE_COLOR;
+ RenderPassType rpType = depthStencil ? RenderPassType::HAS_DEPTH : RenderPassType::DEFAULT;
+
+ if (curRenderStep_->render.framebuffer && (rpType & RenderPassType::HAS_DEPTH) && !curRenderStep_->render.framebuffer->HasDepth()) {
+ WARN_LOG(G3D, "Trying to render with a depth-writing pipeline to a framebuffer without depth: %s", curRenderStep_->render.framebuffer->Tag());
+ rpType = RenderPassType::DEFAULT;
+ }
+
if (!curRenderStep_->render.framebuffer) {
- rpType = RP_TYPE_BACKBUFFER;
- } else if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) {
- // Not allowed on backbuffers.
- rpType = depthStencil ? RP_TYPE_COLOR_DEPTH_INPUT : RP_TYPE_COLOR_INPUT;
+ rpType = RenderPassType::BACKBUFFER;
+ } else {
+ if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) {
+ // Not allowed on backbuffers.
+ rpType = depthStencil ? (RenderPassType::HAS_DEPTH | RenderPassType::COLOR_INPUT) : RenderPassType::COLOR_INPUT;
+ }
+ // Framebuffers can be stereo, and if so, will control the render pass type to match.
+ // Pipelines can be mono and render fine to stereo etc, so not checking them here.
+ // Note that we don't support rendering to just one layer of a multilayer framebuffer!
+ if (curRenderStep_->render.framebuffer->numLayers > 1) {
+ rpType = (RenderPassType)(rpType | RenderPassType::MULTIVIEW);
+ }
+
+ if (curRenderStep_->render.framebuffer->sampleCount != VK_SAMPLE_COUNT_1_BIT) {
+ rpType = (RenderPassType)(rpType | RenderPassType::MULTISAMPLE);
+ }
}
- // TODO: Also add render pass types for depth/stencil-less.
VKRRenderPass *renderPass = queueRunner_.GetRenderPass(key);
curRenderStep_->render.renderPassType = rpType;
+ VkSampleCountFlagBits sampleCount = curRenderStep_->render.framebuffer ? curRenderStep_->render.framebuffer->sampleCount : VK_SAMPLE_COUNT_1_BIT;
+
compileMutex_.lock();
bool needsCompile = false;
for (VKRGraphicsPipeline *pipeline : pipelinesToCheck_) {
- if (!pipeline->pipeline[rpType]) {
- pipeline->pipeline[rpType] = Promise::CreateEmpty();
- compileQueue_.push_back(CompileQueueEntry(pipeline, renderPass->Get(vulkan_, rpType), rpType));
+ if (!pipeline->pipeline[(size_t)rpType]) {
+ pipeline->pipeline[(size_t)rpType] = Promise::CreateEmpty();
+ compileQueue_.push_back(CompileQueueEntry(pipeline, renderPass->Get(vulkan_, rpType, sampleCount), rpType, sampleCount));
needsCompile = true;
}
}
@@ -889,6 +800,10 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR
data.clear.clearMask = lateClearMask;
curRenderStep_->commands.push_back(data);
}
+
+ if (invalidationCallback_) {
+ invalidationCallback_(InvalidationCallbackFlags::RENDER_PASS_STATE);
+ }
}
bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag) {
@@ -1037,6 +952,7 @@ void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearSten
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
if (!clearMask)
return;
+
// If this is the first drawing command or clears everything, merge it into the pass.
int allAspects = VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
if (curRenderStep_->render.numDraws == 0 || clearMask == allAspects) {
@@ -1048,7 +964,11 @@ void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearSten
curRenderStep_->render.stencilLoad = (clearMask & VK_IMAGE_ASPECT_STENCIL_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP;
if (clearMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
- curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
+ if (curRenderStep_->render.framebuffer && !curRenderStep_->render.framebuffer->HasDepth()) {
+ WARN_LOG(G3D, "Trying to clear depth/stencil on a non-depth framebuffer: %s", curRenderStep_->render.framebuffer->Tag());
+ } else {
+ curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
+ }
}
// In case there were commands already.
@@ -1175,7 +1095,7 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect,
steps_.push_back(step);
}
-VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int attachment) {
+VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int layer) {
_dbg_assert_(curRenderStep_ != nullptr);
// We don't support texturing from stencil, neither do we support texturing from depth|stencil together (nonsensical).
@@ -1210,7 +1130,12 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in
// Add this pretransition unless we already have it.
TransitionRequest rq{ fb, aspectBit, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
curRenderStep_->preTransitions.insert(rq); // Note that insert avoids inserting duplicates.
- return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.imageView : fb->depth.depthSampleView;
+
+ if (layer == -1) {
+ return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.texAllLayersView : fb->depth.texAllLayersView;
+ } else {
+ return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.texLayerViews[layer] : fb->depth.texLayerViews[layer];
+ }
}
// Called on main thread.
@@ -1278,7 +1203,16 @@ void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
if (task.steps.empty() && !frameData.hasAcquired)
frameData.skipSwap = true;
//queueRunner_.LogSteps(stepsOnThread, false);
- queueRunner_.RunSteps(task.steps, frameData, frameDataShared_);
+ if (IsVREnabled()) {
+ int passes = GetVRPassesCount();
+ for (int i = 0; i < passes; i++) {
+ PreVRFrameRender(i);
+ queueRunner_.RunSteps(task.steps, frameData, frameDataShared_, i < passes - 1);
+ PostVRFrameRender();
+ }
+ } else {
+ queueRunner_.RunSteps(task.steps, frameData, frameDataShared_);
+ }
switch (task.runType) {
case VKRRunType::PRESENT:
@@ -1328,7 +1262,9 @@ void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
// Called from main thread.
void VulkanRenderManager::FlushSync() {
- renderStepOffset_ += (int)steps_.size();
+ if (invalidationCallback_) {
+ invalidationCallback_(InvalidationCallbackFlags::COMMAND_BUFFER_STATE);
+ }
int curFrame = vulkan_->GetCurFrame();
FrameData &frameData = frameData_[curFrame];
diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h
index a96dd96875..064f1684c9 100644
--- a/Common/GPU/Vulkan/VulkanRenderManager.h
+++ b/Common/GPU/Vulkan/VulkanRenderManager.h
@@ -19,55 +19,14 @@
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Math/math_util.h"
#include "Common/GPU/DataFormat.h"
+#include "Common/GPU/MiscTypes.h"
#include "Common/GPU/Vulkan/VulkanQueueRunner.h"
+#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
+#include "Common/GPU/thin3d.h"
// Forward declaration
VK_DEFINE_HANDLE(VmaAllocation);
-// Simple independent framebuffer image. Gets its own allocation, we don't have that many framebuffers so it's fine
-// to let them have individual non-pooled allocations. Until it's not fine. We'll see.
-struct VKRImage {
- // These four are "immutable".
- VkImage image;
- VkImageView imageView;
- VkImageView depthSampleView;
- VmaAllocation alloc;
- VkFormat format;
-
- // This one is used by QueueRunner's Perform functions to keep track. CANNOT be used anywhere else due to sync issues.
- VkImageLayout layout;
-
- // For debugging.
- std::string tag;
-};
-void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag);
-
-class VKRFramebuffer {
-public:
- VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, bool createDepthStencilBuffer, const char *tag);
- ~VKRFramebuffer();
-
- VkFramebuffer Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType);
-
- int width = 0;
- int height = 0;
- VKRImage color{}; // color.image is always there.
- VKRImage depth{}; // depth.image is allowed to be VK_NULL_HANDLE.
-
- const char *Tag() const {
- return tag_.c_str();
- }
-
- void UpdateTag(const char *newTag);
-
- // TODO: Hide.
- VulkanContext *vulkan_;
-private:
- VkFramebuffer framebuf[RP_TYPE_COUNT]{};
-
- std::string tag_;
-};
-
struct BoundingRect {
int x1;
int y1;
@@ -116,7 +75,8 @@ struct BoundingRect {
};
// All the data needed to create a graphics pipeline.
-struct VKRGraphicsPipelineDesc {
+// TODO: Compress this down greatly.
+struct VKRGraphicsPipelineDesc : Draw::RefCountedObject {
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
VkPipelineColorBlendStateCreateInfo cbs{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO };
VkPipelineColorBlendAttachmentState blend0{};
@@ -124,14 +84,19 @@ struct VKRGraphicsPipelineDesc {
VkDynamicState dynamicStates[6]{};
VkPipelineDynamicStateCreateInfo ds{ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
- VkPipelineMultisampleStateCreateInfo ms{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
// Replaced the ShaderStageInfo with promises here so we can wait for compiles to finish.
Promise *vertexShader = nullptr;
Promise *fragmentShader = nullptr;
Promise *geometryShader = nullptr;
- VkPipelineInputAssemblyStateCreateInfo inputAssembly{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
+ // These are for pipeline creation failure logging.
+ // TODO: Store pointers to the string instead? Feels iffy but will probably work.
+ std::string vertexShaderSource;
+ std::string fragmentShaderSource;
+ std::string geometryShaderSource;
+
+ VkPrimitiveTopology topology;
VkVertexInputAttributeDescription attrs[8]{};
VkVertexInputBindingDescription ibd{};
VkPipelineVertexInputStateCreateInfo vis{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
@@ -152,22 +117,34 @@ struct VKRComputePipelineDesc {
// Wrapped pipeline. Doesn't own desc.
struct VKRGraphicsPipeline {
+ VKRGraphicsPipeline(PipelineFlags flags, const char *tag) : flags_(flags), tag_(tag) {}
~VKRGraphicsPipeline() {
- for (int i = 0; i < RP_TYPE_COUNT; i++) {
+ for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
delete pipeline[i];
}
+ if (desc)
+ desc->Release();
}
- bool Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType);
+ bool Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType, VkSampleCountFlagBits sampleCount);
+
+ void DestroyVariants(VulkanContext *vulkan, bool msaaOnly);
// This deletes the whole VKRGraphicsPipeline, you must remove your last pointer to it when doing this.
void QueueForDeletion(VulkanContext *vulkan);
u32 GetVariantsBitmask() const;
- VKRGraphicsPipelineDesc *desc = nullptr; // not owned!
- Promise *pipeline[RP_TYPE_COUNT]{};
- std::string tag;
+ void LogCreationFailure() const;
+
+ VKRGraphicsPipelineDesc *desc = nullptr;
+ Promise *pipeline[(size_t)RenderPassType::TYPE_COUNT]{};
+
+ VkSampleCountFlagBits SampleCount() const { return sampleCount_; }
+private:
+ std::string tag_;
+ PipelineFlags flags_;
+ VkSampleCountFlagBits sampleCount_ = VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM;
};
struct VKRComputePipeline {
@@ -185,9 +162,9 @@ struct VKRComputePipeline {
};
struct CompileQueueEntry {
- CompileQueueEntry(VKRGraphicsPipeline *p, VkRenderPass _compatibleRenderPass, RenderPassType _renderPassType)
- : type(Type::GRAPHICS), graphics(p), compatibleRenderPass(_compatibleRenderPass), renderPassType(_renderPassType) {}
- CompileQueueEntry(VKRComputePipeline *p) : type(Type::COMPUTE), compute(p), renderPassType(RP_TYPE_COLOR_DEPTH) {}
+ CompileQueueEntry(VKRGraphicsPipeline *p, VkRenderPass _compatibleRenderPass, RenderPassType _renderPassType, VkSampleCountFlagBits _sampleCount)
+ : type(Type::GRAPHICS), graphics(p), compatibleRenderPass(_compatibleRenderPass), renderPassType(_renderPassType), sampleCount(_sampleCount) {}
+ CompileQueueEntry(VKRComputePipeline *p) : type(Type::COMPUTE), compute(p), renderPassType(RenderPassType::HAS_DEPTH), sampleCount(VK_SAMPLE_COUNT_1_BIT) {} // renderpasstype here shouldn't matter
enum class Type {
GRAPHICS,
COMPUTE,
@@ -197,6 +174,7 @@ struct CompileQueueEntry {
RenderPassType renderPassType;
VKRGraphicsPipeline *graphics = nullptr;
VKRComputePipeline *compute = nullptr;
+ VkSampleCountFlagBits sampleCount;
};
class VulkanRenderManager {
@@ -211,7 +189,11 @@ public:
// Zaps queued up commands. Use if you know there's a risk you've queued up stuff that has already been deleted. Can happen during in-game shutdown.
void Wipe();
- // This starts a new step containing a render pass.
+ void SetInvalidationCallback(InvalidationCallback callback) {
+ invalidationCallback_ = callback;
+ }
+
+ // This starts a new step containing a render pass (unless it can be trivially merged into the previous one, which is pretty common).
//
// After a "CopyFramebuffer" or the other functions that start "steps", you need to call this beforce
// making any new render state changes or draw calls.
@@ -230,7 +212,9 @@ public:
// Returns an ImageView corresponding to a framebuffer. Is called BindFramebufferAsTexture to maintain a similar interface
// as the other backends, even though there's no actual binding happening here.
- VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int attachment);
+ // For layer, we use the same convention as thin3d, where layer = -1 means all layers together. For texturing, that means that you
+ // get an array texture view.
+ VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int layer);
void BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits);
@@ -245,7 +229,7 @@ public:
// We delay creating pipelines until the end of the current render pass, so we can create the right type immediately.
// Unless a variantBitmask is passed in, in which case we can just go ahead.
// WARNING: desc must stick around during the lifetime of the pipeline! It's not enough to build it on the stack and drop it.
- VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag);
+ VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, VkSampleCountFlagBits sampleCount, const char *tag);
VKRComputePipeline *CreateComputePipeline(VKRComputePipelineDesc *desc);
void NudgeCompilerThread() {
@@ -254,6 +238,16 @@ public:
compileMutex_.unlock();
}
+ // Mainly used to bind the frame-global desc set.
+ // Can be done before binding a pipeline, so not asserting on that.
+ void BindDescriptorSet(int setNumber, VkDescriptorSet set, VkPipelineLayout pipelineLayout) {
+ VkRenderData data{ VKRRenderCommand::BIND_DESCRIPTOR_SET };
+ data.bindDescSet.setNumber = setNumber;
+ data.bindDescSet.set = set;
+ data.bindDescSet.pipelineLayout = pipelineLayout;
+ curRenderStep_->commands.push_back(data);
+ }
+
void BindPipeline(VKRGraphicsPipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr);
@@ -261,6 +255,10 @@ public:
pipelinesToCheck_.push_back(pipeline);
data.graphics_pipeline.pipeline = pipeline;
data.graphics_pipeline.pipelineLayout = pipelineLayout;
+ // This can be used to debug cases where depth/stencil rendering is used on color-only framebuffers.
+ // if ((flags & PipelineFlags::USES_DEPTH_STENCIL) && curRenderStep_->render.framebuffer && !curRenderStep_->render.framebuffer->HasDepth()) {
+ // DebugBreak();
+ // }
curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
}
@@ -435,12 +433,6 @@ public:
VkCommandBuffer GetInitCmd();
- // Gets a frame-unique ID of the current step being recorded. Can be used to figure out
- // when the current step has changed, which means the caller will need to re-record its state.
- int GetCurrentStepId() const {
- return renderStepOffset_ + (int)steps_.size();
- }
-
bool CreateBackbuffers();
void DestroyBackbuffers();
@@ -470,6 +462,8 @@ public:
return outOfDateFrames_ > VulkanContext::MAX_INFLIGHT_FRAMES;
}
+ void Invalidate(InvalidationFlags flags);
+
void ResetStats();
private:
@@ -480,7 +474,6 @@ private:
void DrainCompileQueue();
void Run(VKRRenderThreadTask &task);
- void BeginSubmitFrame(int frame);
// Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot).
void FlushSync();
@@ -508,7 +501,6 @@ private:
bool run_ = false;
// This is the offset within this frame, in case of a mid-frame sync.
- int renderStepOffset_ = 0;
VKRStep *curRenderStep_ = nullptr;
bool curStepHasViewport_ = false;
bool curStepHasScissor_ = false;
@@ -547,4 +539,6 @@ private:
SimpleStat initTimeMs_;
SimpleStat totalGPUTimeMs_;
SimpleStat renderCPUTimeMs_;
+
+ std::function invalidationCallback_;
};
diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp
index 7d3b0e8218..fbb5fbb3e8 100644
--- a/Common/GPU/Vulkan/thin3d_vulkan.cpp
+++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp
@@ -35,15 +35,22 @@
#include "Core/Config.h"
-// We use a simple descriptor set for all rendering: 1 sampler, 1 texture, 1 UBO binding point.
-// binding 0 - uniform data
-// binding 1 - sampler
-// binding 2 - sampler
-//
-// Vertex data lives in a separate namespace (location = 0, 1, etc)
-
#include "Common/GPU/Vulkan/VulkanLoader.h"
+// We support a frame-global descriptor set, which can be optionally used by other code,
+// but is not directly used by thin3d. It has to be defined here though, be in set 0
+// and specified in every pipeline layout, otherwise it can't sit undisturbed when other
+// descriptor sets are bound on top.
+
+// For descriptor set 1, we use a simple descriptor set for all thin3d rendering: 1 UBO binding point, 3 combined texture/samples.
+//
+// binding 0 - uniform buffer
+// binding 1 - texture/sampler
+// binding 2 - texture/sampler
+// binding 3 - texture/sampler
+//
+// Vertex data lives in a separate namespace (location = 0, 1, etc).
+
using namespace PPSSPP_VK;
namespace Draw {
@@ -196,7 +203,10 @@ public:
DEBUG_LOG(G3D, "Queueing %s (shmodule %p) for release", tag_.c_str(), module_);
VkShaderModule shaderModule = module_->BlockUntilReady();
vulkan_->Delete().QueueDeleteShaderModule(shaderModule);
- delete module_;
+ vulkan_->Delete().QueueCallback([](void *m) {
+ auto module = (Promise *)m;
+ delete module;
+ }, module_);
}
}
Promise *Get() const { return module_; }
@@ -235,7 +245,7 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con
#endif
VkShaderModule shaderModule = VK_NULL_HANDLE;
- if (vulkan->CreateShaderModule(spirv, &shaderModule, vkstage_ == VK_SHADER_STAGE_VERTEX_BIT ? "thin3d_vs" : "thin3d_fs")) {
+ if (vulkan->CreateShaderModule(spirv, &shaderModule, tag_.c_str())) {
module_ = Promise::AlreadyDone(shaderModule);
ok_ = true;
} else {
@@ -257,6 +267,7 @@ public:
VKPipeline(VulkanContext *vulkan, size_t size, PipelineFlags _flags, const char *tag) : vulkan_(vulkan), flags(_flags), tag_(tag) {
uboSize_ = (int)size;
ubo_ = new uint8_t[uboSize_];
+ vkrDesc = new VKRGraphicsPipelineDesc();
}
~VKPipeline() {
DEBUG_LOG(G3D, "Queueing %s (pipeline) for release", tag_.c_str());
@@ -267,9 +278,11 @@ public:
dep->Release();
}
delete[] ubo_;
+ vkrDesc->Release();
}
void SetDynamicUniformData(const void *data, size_t size) {
+ _dbg_assert_(size <= uboSize_);
memcpy(ubo_, data, size);
}
@@ -283,7 +296,7 @@ public:
}
VKRGraphicsPipeline *pipeline = nullptr;
- VKRGraphicsPipelineDesc vkrDesc;
+ VKRGraphicsPipelineDesc *vkrDesc = nullptr;
PipelineFlags flags;
std::vector deps;
@@ -335,12 +348,16 @@ public:
VkImageView GetImageView() {
if (vkTex_) {
- vkTex_->Touch();
return vkTex_->GetImageView();
- } else {
- // This would be bad.
- return VK_NULL_HANDLE;
}
+ return VK_NULL_HANDLE; // This would be bad.
+ }
+
+ VkImageView GetImageArrayView() {
+ if (vkTex_) {
+ return vkTex_->GetImageArrayView();
+ }
+ return VK_NULL_HANDLE; // This would be bad.
}
private:
@@ -365,7 +382,7 @@ class VKFramebuffer;
class VKContext : public DrawContext {
public:
VKContext(VulkanContext *vulkan);
- virtual ~VKContext();
+ ~VKContext();
void DebugAnnotate(const char *annotation) override;
@@ -405,10 +422,7 @@ public:
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
- Framebuffer *GetCurrentRenderTarget() override {
- return (Framebuffer *)curFramebuffer_.ptr;
- }
- void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
+ void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override;
void BindCurrentFramebufferForColorInput() override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@@ -419,7 +433,7 @@ public:
void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) override;
void BindSamplerStates(int start, int count, SamplerState **state) override;
- void BindTextures(int start, int count, Texture **textures) override;
+ void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int sampler, void *nativeTexture) override;
void BindPipeline(Pipeline *pipeline) override {
@@ -487,14 +501,14 @@ public:
void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override;
- int GetCurrentStepId() const override {
- return renderManager_.GetCurrentStepId();
- }
-
- void InvalidateCachedState() override;
+ void Invalidate(InvalidationFlags flags) override;
void InvalidateFramebuffer(FBInvalidationStage stage, uint32_t channels) override;
+ void SetInvalidationCallback(InvalidationCallback callback) override {
+ renderManager_.SetInvalidationCallback(callback);
+ }
+
private:
VulkanTexture *GetNullTexture();
VulkanContext *vulkan_ = nullptr;
@@ -510,6 +524,7 @@ private:
int curIBufferOffset_ = 0;
VkDescriptorSetLayout descriptorSetLayout_ = VK_NULL_HANDLE;
+ VkDescriptorSetLayout frameDescSetLayout_ = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
VkPipelineCache pipelineCache_ = VK_NULL_HANDLE;
AutoRef curFramebuffer_;
@@ -524,6 +539,7 @@ private:
AutoRef boundTextures_[MAX_BOUND_TEXTURES];
AutoRef boundSamplers_[MAX_BOUND_TEXTURES];
VkImageView boundImageView_[MAX_BOUND_TEXTURES]{};
+ TextureBindFlags boundTextureFlags_[MAX_BOUND_TEXTURES];
struct FrameData {
FrameData() : descriptorPool("VKContext", false) {
@@ -579,6 +595,8 @@ static int GetBpp(VkFormat format) {
static VkFormat DataFormatToVulkan(DataFormat format) {
switch (format) {
case DataFormat::D16: return VK_FORMAT_D16_UNORM;
+ case DataFormat::D16_S8: return VK_FORMAT_D16_UNORM_S8_UINT;
+ case DataFormat::D24_S8: return VK_FORMAT_D24_UNORM_S8_UINT;
case DataFormat::D32F: return VK_FORMAT_D32_SFLOAT;
case DataFormat::D32F_S8: return VK_FORMAT_D32_SFLOAT_S8_UINT;
case DataFormat::S8: return VK_FORMAT_S8_UINT;
@@ -637,8 +655,7 @@ static inline VkSamplerAddressMode AddressModeToVulkan(Draw::TextureAddressMode
VulkanTexture *VKContext::GetNullTexture() {
if (!nullTexture_) {
VkCommandBuffer cmdInit = renderManager_.GetInitCmd();
- nullTexture_ = new VulkanTexture(vulkan_);
- nullTexture_->SetTag("Null");
+ nullTexture_ = new VulkanTexture(vulkan_, "Null");
int w = 8;
int h = 8;
nullTexture_->CreateDirect(cmdInit, w, h, 1, 1, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
@@ -654,8 +671,6 @@ VulkanTexture *VKContext::GetNullTexture() {
}
nullTexture_->UploadMip(cmdInit, 0, w, h, 0, bindBuf, bindOffset, w);
nullTexture_->EndCreate(cmdInit, false, VK_PIPELINE_STAGE_TRANSFER_BIT);
- } else {
- nullTexture_->Touch();
}
return nullTexture_;
}
@@ -668,6 +683,7 @@ public:
s.addressModeV = AddressModeToVulkan(desc.wrapV);
s.addressModeW = AddressModeToVulkan(desc.wrapW);
s.anisotropyEnable = desc.maxAniso > 1.0f;
+ s.maxAnisotropy = desc.maxAniso;
s.magFilter = desc.magFilter == TextureFilter::LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
s.minFilter = desc.minFilter == TextureFilter::LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
s.mipmapMode = desc.mipFilter == TextureFilter::LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST;
@@ -721,10 +737,7 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur
width_ = desc.width;
height_ = desc.height;
depth_ = desc.depth;
- vkTex_ = new VulkanTexture(vulkan_);
- if (desc.tag) {
- vkTex_->SetTag(desc.tag);
- }
+ vkTex_ = new VulkanTexture(vulkan_, desc.tag);
VkFormat vulkanFormat = DataFormatToVulkan(format_);
int bpp = GetBpp(vulkanFormat);
int bytesPerPixel = bpp / 8;
@@ -771,34 +784,58 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur
return true;
}
+static DataFormat DataFormatFromVulkanDepth(VkFormat fmt) {
+ switch (fmt) {
+ case VK_FORMAT_D24_UNORM_S8_UINT:
+ return DataFormat::D24_S8;
+ case VK_FORMAT_D16_UNORM:
+ return DataFormat::D16;
+ case VK_FORMAT_D32_SFLOAT:
+ return DataFormat::D32F;
+ case VK_FORMAT_D32_SFLOAT_S8_UINT:
+ return DataFormat::D32F_S8;
+ case VK_FORMAT_D16_UNORM_S8_UINT:
+ return DataFormat::D16_S8;
+ default:
+ break;
+ }
+
+ return DataFormat::UNDEFINED;
+}
+
VKContext::VKContext(VulkanContext *vulkan)
: vulkan_(vulkan), renderManager_(vulkan) {
shaderLanguageDesc_.Init(GLSL_VULKAN);
VkFormat depthStencilFormat = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
- caps_.anisoSupported = vulkan->GetDeviceFeatures().enabled.samplerAnisotropy != 0;
- caps_.geometryShaderSupported = vulkan->GetDeviceFeatures().enabled.geometryShader != 0;
- caps_.tesselationShaderSupported = vulkan->GetDeviceFeatures().enabled.tessellationShader != 0;
- caps_.multiViewport = vulkan->GetDeviceFeatures().enabled.multiViewport != 0;
- caps_.dualSourceBlend = vulkan->GetDeviceFeatures().enabled.dualSrcBlend != 0;
- caps_.depthClampSupported = vulkan->GetDeviceFeatures().enabled.depthClamp != 0;
- caps_.clipDistanceSupported = vulkan->GetDeviceFeatures().enabled.shaderClipDistance != 0;
- caps_.cullDistanceSupported = vulkan->GetDeviceFeatures().enabled.shaderCullDistance != 0;
+ caps_.anisoSupported = vulkan->GetDeviceFeatures().enabled.standard.samplerAnisotropy != 0;
+ caps_.geometryShaderSupported = vulkan->GetDeviceFeatures().enabled.standard.geometryShader != 0;
+ caps_.tesselationShaderSupported = vulkan->GetDeviceFeatures().enabled.standard.tessellationShader != 0;
+ caps_.dualSourceBlend = vulkan->GetDeviceFeatures().enabled.standard.dualSrcBlend != 0;
+ caps_.depthClampSupported = vulkan->GetDeviceFeatures().enabled.standard.depthClamp != 0;
+ caps_.clipDistanceSupported = vulkan->GetDeviceFeatures().enabled.standard.shaderClipDistance != 0;
+ caps_.cullDistanceSupported = vulkan->GetDeviceFeatures().enabled.standard.shaderCullDistance != 0;
caps_.framebufferBlitSupported = true;
caps_.framebufferCopySupported = true;
caps_.framebufferDepthBlitSupported = vulkan->GetDeviceInfo().canBlitToPreferredDepthStencilFormat;
caps_.framebufferStencilBlitSupported = caps_.framebufferDepthBlitSupported;
caps_.framebufferDepthCopySupported = true; // Will pretty much always be the case.
caps_.framebufferSeparateDepthCopySupported = true; // Will pretty much always be the case.
- caps_.preferredDepthBufferFormat = DataFormat::D24_S8; // TODO: Ask vulkan.
+ // This doesn't affect what depth/stencil format is actually used, see VulkanQueueRunner.
+ caps_.preferredDepthBufferFormat = DataFormatFromVulkanDepth(vulkan->GetDeviceInfo().preferredDepthStencilFormat);
caps_.texture3DSupported = true;
caps_.textureDepthSupported = true;
caps_.fragmentShaderInt32Supported = true;
caps_.textureNPOTFullySupported = true;
caps_.fragmentShaderDepthWriteSupported = true;
+ caps_.fragmentShaderStencilWriteSupported = vulkan->Extensions().EXT_shader_stencil_export;
caps_.blendMinMaxSupported = true;
- caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.logicOp != 0;
+ caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.standard.logicOp != 0;
+ caps_.multiViewSupported = vulkan->GetDeviceFeatures().enabled.multiview.multiview != 0;
+ caps_.sampleRateShadingSupported = vulkan->GetDeviceFeatures().enabled.standard.sampleRateShading != 0;
+
+ const auto &limits = vulkan->GetPhysicalDeviceProperties().properties.limits;
auto deviceProps = vulkan->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDeviceIndex()).properties;
@@ -809,7 +846,47 @@ VKContext::VKContext(VulkanContext *vulkan)
case VULKAN_VENDOR_NVIDIA: caps_.vendor = GPUVendor::VENDOR_NVIDIA; break;
case VULKAN_VENDOR_QUALCOMM: caps_.vendor = GPUVendor::VENDOR_QUALCOMM; break;
case VULKAN_VENDOR_INTEL: caps_.vendor = GPUVendor::VENDOR_INTEL; break;
- default: caps_.vendor = GPUVendor::VENDOR_UNKNOWN; break;
+ case VULKAN_VENDOR_APPLE: caps_.vendor = GPUVendor::VENDOR_APPLE; break;
+ default:
+ WARN_LOG(G3D, "Unknown vendor ID %08x", deviceProps.vendorID);
+ caps_.vendor = GPUVendor::VENDOR_UNKNOWN;
+ break;
+ }
+
+ switch (caps_.vendor) {
+ case GPUVendor::VENDOR_ARM:
+ case GPUVendor::VENDOR_IMGTEC:
+ case GPUVendor::VENDOR_QUALCOMM:
+ caps_.isTilingGPU = true;
+ break;
+ default:
+ caps_.isTilingGPU = false;
+ break;
+ }
+
+ // Hide D3D9 when we know it likely won't work well.
+#if PPSSPP_PLATFORM(WINDOWS)
+ caps_.supportsD3D9 = true;
+ if (!strcmp(deviceProps.deviceName, "Intel(R) Iris(R) Xe Graphics")) {
+ caps_.supportsD3D9 = false;
+ }
+#endif
+
+ // VkSampleCountFlagBits is arranged correctly for our purposes.
+ // Only support MSAA levels that have support for all three of color, depth, stencil.
+ if (!caps_.isTilingGPU) {
+ // Check for depth stencil resolve. Without it, depth textures won't work, and we don't want that mess
+ // of compatibility reports, so we'll just disable multisampling in this case for now.
+ // There are potential workarounds for devices that don't support it, but those are nearly non-existent now.
+ const auto &resolveProperties = vulkan->GetPhysicalDeviceProperties().depthStencilResolve;
+ if (vulkan->Extensions().KHR_depth_stencil_resolve &&
+ ((resolveProperties.supportedDepthResolveModes & resolveProperties.supportedStencilResolveModes) & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT) != 0) {
+ caps_.multiSampleLevelsMask = (limits.framebufferColorSampleCounts & limits.framebufferDepthSampleCounts & limits.framebufferStencilSampleCounts);
+ } else {
+ caps_.multiSampleLevelsMask = 1;
+ }
+ } else {
+ caps_.multiSampleLevelsMask = 1;
}
if (caps_.vendor == GPUVendor::VENDOR_QUALCOMM) {
@@ -822,7 +899,9 @@ VKContext::VKContext(VulkanContext *vulkan)
}
// Color write mask not masking write in certain scenarios with a depth test, see #10421.
// Known still present on driver 0x80180000 and Adreno 5xx (possibly more.)
- bugs_.Infest(Bugs::COLORWRITEMASK_BROKEN_WITH_DEPTHTEST);
+ // Known working on driver 0x801EA000 and Adreno 620.
+ if (deviceProps.driverVersion < 0x801EA000 || deviceProps.deviceID < 0x06000000)
+ bugs_.Infest(Bugs::COLORWRITEMASK_BROKEN_WITH_DEPTHTEST);
// Trying to follow all the rules in https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
// and https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#renderpass-feedbackloop, but still it doesn't
@@ -916,13 +995,25 @@ VKContext::VKContext(VulkanContext *vulkan)
VkResult res = vkCreateDescriptorSetLayout(device_, &dsl, nullptr, &descriptorSetLayout_);
_assert_(VK_SUCCESS == res);
+ VkDescriptorSetLayoutBinding frameBindings[1]{};
+ frameBindings[0].descriptorCount = 1;
+ frameBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ frameBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+ frameBindings[0].binding = 0;
+
+ dsl.bindingCount = ARRAY_SIZE(frameBindings);
+ dsl.pBindings = frameBindings;
+ res = vkCreateDescriptorSetLayout(device_, &dsl, nullptr, &frameDescSetLayout_);
+ _dbg_assert_(VK_SUCCESS == res);
+
vulkan_->SetDebugName(descriptorSetLayout_, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, "thin3d_d_layout");
VkPipelineLayoutCreateInfo pl = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
pl.pPushConstantRanges = nullptr;
pl.pushConstantRangeCount = 0;
- pl.setLayoutCount = 1;
- pl.pSetLayouts = &descriptorSetLayout_;
+ VkDescriptorSetLayout setLayouts[2] = { frameDescSetLayout_, descriptorSetLayout_ };
+ pl.setLayoutCount = ARRAY_SIZE(setLayouts);
+ pl.pSetLayouts = setLayouts;
res = vkCreatePipelineLayout(device_, &pl, nullptr, &pipelineLayout_);
_assert_(VK_SUCCESS == res);
@@ -942,6 +1033,7 @@ VKContext::~VKContext() {
delete frame_[i].pushBuffer;
}
vulkan_->Delete().QueueDeleteDescriptorSetLayout(descriptorSetLayout_);
+ vulkan_->Delete().QueueDeleteDescriptorSetLayout(frameDescSetLayout_);
vulkan_->Delete().QueueDeletePipelineLayout(pipelineLayout_);
vulkan_->Delete().QueueDeletePipelineCache(pipelineCache_);
}
@@ -969,20 +1061,22 @@ void VKContext::EndFrame() {
push_ = nullptr;
// Unbind stuff, to avoid accidentally relying on it across frames (and provide some protection against forgotten unbinds of deleted things).
- InvalidateCachedState();
+ Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
}
-void VKContext::InvalidateCachedState() {
- curPipeline_ = nullptr;
+void VKContext::Invalidate(InvalidationFlags flags) {
+ if (flags & InvalidationFlags::CACHED_RENDER_STATE) {
+ curPipeline_ = nullptr;
- for (auto &view : boundImageView_) {
- view = VK_NULL_HANDLE;
- }
- for (auto &sampler : boundSamplers_) {
- sampler = nullptr;
- }
- for (auto &texture : boundTextures_) {
- texture = nullptr;
+ for (auto &view : boundImageView_) {
+ view = VK_NULL_HANDLE;
+ }
+ for (auto &sampler : boundSamplers_) {
+ sampler = nullptr;
+ }
+ for (auto &texture : boundTextures_) {
+ texture = nullptr;
+ }
}
}
@@ -996,7 +1090,11 @@ VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) {
FrameData *frame = &frame_[vulkan_->GetCurFrame()];
for (int i = 0; i < MAX_BOUND_TEXTURES; ++i) {
- key.imageViews_[i] = boundTextures_[i] ? boundTextures_[i]->GetImageView() : boundImageView_[i];
+ if (boundTextures_[i]) {
+ key.imageViews_[i] = (boundTextureFlags_[i] & TextureBindFlags::VULKAN_BIND_ARRAY) ? boundTextures_[i]->GetImageArrayView() : boundTextures_[i]->GetImageView();
+ } else {
+ key.imageViews_[i] = boundImageView_[i];
+ }
key.samplers_[i] = boundSamplers_[i];
}
key.buffer_ = buf;
@@ -1006,7 +1104,7 @@ VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) {
return iter->second;
}
- VkDescriptorSet descSet = frame->descriptorPool.Allocate(1, &descriptorSetLayout_);
+ VkDescriptorSet descSet = frame->descriptorPool.Allocate(1, &descriptorSetLayout_, "thin3d_descset");
if (descSet == VK_NULL_HANDLE) {
ERROR_LOG(G3D, "GetOrCreateDescriptorSet failed");
return VK_NULL_HANDLE;
@@ -1077,7 +1175,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), pipelineFlags, tag);
- VKRGraphicsPipelineDesc &gDesc = pipeline->vkrDesc;
+ VKRGraphicsPipelineDesc &gDesc = *pipeline->vkrDesc;
std::vector stages;
stages.resize(desc.shaders.size());
@@ -1097,16 +1195,12 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
}
}
- if (input) {
- for (int i = 0; i < (int)input->bindings.size(); i++) {
- pipeline->stride[i] = input->bindings[i].stride;
- }
- } else {
- pipeline->stride[0] = 0;
- }
- _dbg_assert_(input->bindings.size() == 1);
+ _dbg_assert_(input && input->bindings.size() == 1);
_dbg_assert_((int)input->attributes.size() == (int)input->visc.vertexAttributeDescriptionCount);
+ for (int i = 0; i < (int)input->bindings.size(); i++) {
+ pipeline->stride[i] = input->bindings[i].stride;
+ }
gDesc.ibd = input->bindings[0];
for (size_t i = 0; i < input->attributes.size(); i++) {
gDesc.attrs[i] = input->attributes[i];
@@ -1125,7 +1219,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
raster->ToVulkan(&gDesc.rs);
// Copy bindings from input layout.
- gDesc.inputAssembly.topology = primToVK[(int)desc.prim];
+ gDesc.topology = primToVK[(int)desc.prim];
// We treat the three stencil states as a unit in other places, so let's do that here too.
const VkDynamicState dynamics[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE, VK_DYNAMIC_STATE_STENCIL_WRITE_MASK };
@@ -1135,9 +1229,6 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
}
gDesc.ds.pDynamicStates = gDesc.dynamicStates;
- gDesc.ms.pSampleMask = nullptr;
- gDesc.ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
-
gDesc.views.viewportCount = 1;
gDesc.views.scissorCount = 1;
gDesc.views.pViewports = nullptr; // dynamic
@@ -1148,7 +1239,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
raster->ToVulkan(&gDesc.rs);
- pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, pipelineFlags, 1 << RP_TYPE_BACKBUFFER, tag ? tag : "thin3d");
+ pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, pipelineFlags, 1 << (size_t)RenderPassType::BACKBUFFER, VK_SAMPLE_COUNT_1_BIT, tag ? tag : "thin3d");
if (desc.uniformDesc) {
pipeline->dynamicUniformSize = (int)desc.uniformDesc->uniformBufferSize;
@@ -1278,7 +1369,7 @@ public:
VKBuffer(size_t size, uint32_t flags) : dataSize_(size) {
data_ = new uint8_t[size];
}
- ~VKBuffer() override {
+ ~VKBuffer() {
delete[] data_;
}
@@ -1298,15 +1389,32 @@ void VKContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset,
memcpy(buf->data_ + offset, data, size);
}
-void VKContext::BindTextures(int start, int count, Texture **textures) {
+void VKContext::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
+ _dbg_assert_(i >= 0 && i < MAX_BOUND_TEXTURES);
boundTextures_[i] = static_cast(textures[i - start]);
- boundImageView_[i] = boundTextures_[i] ? boundTextures_[i]->GetImageView() : GetNullTexture()->GetImageView();
+ boundTextureFlags_[i] = flags;
+ if (boundTextures_[i]) {
+ // If a texture is bound, we set these up in GetOrCreateDescriptorSet too.
+ // But we might need to set the view here anyway so it can be queried using GetNativeObject.
+ if (flags & TextureBindFlags::VULKAN_BIND_ARRAY) {
+ boundImageView_[i] = boundTextures_[i]->GetImageArrayView();
+ } else {
+ boundImageView_[i] = boundTextures_[i]->GetImageView();
+ }
+ } else {
+ if (flags & TextureBindFlags::VULKAN_BIND_ARRAY) {
+ boundImageView_[i] = GetNullTexture()->GetImageArrayView();
+ } else {
+ boundImageView_[i] = GetNullTexture()->GetImageView();
+ }
+ }
}
}
void VKContext::BindNativeTexture(int sampler, void *nativeTexture) {
+ _dbg_assert_(sampler >= 0 && sampler < MAX_BOUND_TEXTURES);
boundTextures_[sampler] = nullptr;
boundImageView_[sampler] = (VkImageView)nativeTexture;
}
@@ -1414,8 +1522,8 @@ void AddFeature(std::vector &features, const char *name, VkBool32 a
}
std::vector VKContext::GetFeatureList() const {
- const VkPhysicalDeviceFeatures &available = vulkan_->GetDeviceFeatures().available;
- const VkPhysicalDeviceFeatures &enabled = vulkan_->GetDeviceFeatures().enabled;
+ const VkPhysicalDeviceFeatures &available = vulkan_->GetDeviceFeatures().available.standard;
+ const VkPhysicalDeviceFeatures &enabled = vulkan_->GetDeviceFeatures().enabled.standard;
std::vector features;
AddFeature(features, "dualSrcBlend", available.dualSrcBlend, enabled.dualSrcBlend);
@@ -1434,6 +1542,9 @@ std::vector VKContext::GetFeatureList() const {
AddFeature(features, "occlusionQueryPrecise", available.occlusionQueryPrecise, enabled.occlusionQueryPrecise);
AddFeature(features, "multiDrawIndirect", available.multiDrawIndirect, enabled.multiDrawIndirect);
+ AddFeature(features, "multiview", vulkan_->GetDeviceFeatures().available.multiview.multiview, vulkan_->GetDeviceFeatures().enabled.multiview.multiview);
+ AddFeature(features, "multiviewGeometryShader", vulkan_->GetDeviceFeatures().available.multiview.multiviewGeometryShader, vulkan_->GetDeviceFeatures().enabled.multiview.multiviewGeometryShader);
+
features.emplace_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat));
return features;
@@ -1479,14 +1590,16 @@ uint32_t VKContext::GetDataFormatSupport(DataFormat fmt) const {
// use this frame's init command buffer.
class VKFramebuffer : public Framebuffer {
public:
- VKFramebuffer(VKRFramebuffer *fb) : buf_(fb) {
+ VKFramebuffer(VKRFramebuffer *fb, int multiSampleLevel) : buf_(fb) {
_assert_msg_(fb, "Null fb in VKFramebuffer constructor");
width_ = fb->width;
height_ = fb->height;
+ layers_ = fb->numLayers;
+ multiSampleLevel_ = multiSampleLevel;
}
~VKFramebuffer() {
_assert_msg_(buf_, "Null buf_ in VKFramebuffer - double delete?");
- buf_->vulkan_->Delete().QueueCallback([](void *fb) {
+ buf_->Vulkan()->Delete().QueueCallback([](void *fb) {
VKRFramebuffer *vfb = static_cast(fb);
delete vfb;
}, buf_);
@@ -1501,9 +1614,14 @@ private:
};
Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) {
+ _assert_(desc.multiSampleLevel >= 0);
+ _assert_(desc.numLayers > 0);
+ _assert_(desc.width > 0);
+ _assert_(desc.height > 0);
+
VkCommandBuffer cmd = renderManager_.GetInitCmd();
- VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.z_stencil, desc.tag);
- return new VKFramebuffer(vkrfb);
+ VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.numLayers, desc.multiSampleLevel, desc.z_stencil, desc.tag);
+ return new VKFramebuffer(vkrfb, desc.multiSampleLevel);
}
void VKContext::CopyFramebufferImage(Framebuffer *srcfb, int level, int x, int y, int z, Framebuffer *dstfb, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) {
@@ -1563,9 +1681,9 @@ void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPass
curFramebuffer_ = fb;
}
-void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) {
+void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) {
VKFramebuffer *fb = (VKFramebuffer *)fbo;
- _assert_(binding < MAX_BOUND_TEXTURES);
+ _assert_(binding >= 0 && binding < MAX_BOUND_TEXTURES);
// TODO: There are cases where this is okay, actually. But requires layout transitions and stuff -
// we're not ready for this.
@@ -1583,8 +1701,9 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChanne
_assert_(false);
break;
}
- boundTextures_[binding] = nullptr;
- boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, attachment);
+
+ boundTextures_[binding].clear();
+ boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, layer);
}
void VKContext::BindCurrentFramebufferForColorInput() {
@@ -1645,10 +1764,19 @@ uint64_t VKContext::GetNativeObject(NativeObject obj, void *srcObject) {
return (uint64_t)(uintptr_t)&renderManager_;
case NativeObject::NULL_IMAGEVIEW:
return (uint64_t)GetNullTexture()->GetImageView();
+ case NativeObject::NULL_IMAGEVIEW_ARRAY:
+ return (uint64_t)GetNullTexture()->GetImageArrayView();
case NativeObject::TEXTURE_VIEW:
return (uint64_t)(((VKTexture *)srcObject)->GetImageView());
- case NativeObject::BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW:
- return (uint64_t)curFramebuffer_->GetFB()->color.imageView;
+ case NativeObject::BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW_ALL_LAYERS:
+ return (uint64_t)curFramebuffer_->GetFB()->color.texAllLayersView;
+ case NativeObject::BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW_RT:
+ return (uint64_t)curFramebuffer_->GetFB()->GetRTView();
+ case NativeObject::FRAME_DATA_DESC_SET_LAYOUT:
+ return (uint64_t)frameDescSetLayout_;
+ case NativeObject::THIN3D_PIPELINE_LAYOUT:
+ return (uint64_t)pipelineLayout_;
+
default:
Crash();
return 0;
diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp
index ac2daa68c7..0ffd9e6b5b 100644
--- a/Common/GPU/thin3d.cpp
+++ b/Common/GPU/thin3d.cpp
@@ -46,6 +46,7 @@ size_t DataFormatSizeInBytes(DataFormat fmt) {
case DataFormat::S8: return 1;
case DataFormat::D16: return 2;
+ case DataFormat::D16_S8: return 3;
case DataFormat::D24_S8: return 4;
case DataFormat::D32F: return 4;
// Or maybe 8...
@@ -56,9 +57,32 @@ size_t DataFormatSizeInBytes(DataFormat fmt) {
}
}
+const char *DataFormatToString(DataFormat fmt) {
+ switch (fmt) {
+ case DataFormat::R8_UNORM: return "R8_UNORM";
+ case DataFormat::R8G8_UNORM: return "R8G8_UNORM";
+ case DataFormat::R8G8B8A8_UNORM: return "R8G8B8A8_UNORM";
+ case DataFormat::B8G8R8A8_UNORM: return "B8G8R8A8_UNORM";
+ case DataFormat::R16_UNORM: return "R16_UNORM";
+ case DataFormat::R16_FLOAT: return "R16_FLOAT";
+ case DataFormat::R32_FLOAT: return "R32_FLOAT";
+
+ case DataFormat::S8: return "S8";
+ case DataFormat::D16: return "D16";
+ case DataFormat::D16_S8: return "D16_S8";
+ case DataFormat::D24_S8: return "D24_S8";
+ case DataFormat::D32F: return "D32F";
+ case DataFormat::D32F_S8: return "D32F_S8";
+
+ default:
+ return "(N/A)";
+ }
+}
+
bool DataFormatIsDepthStencil(DataFormat fmt) {
switch (fmt) {
case DataFormat::D16:
+ case DataFormat::D16_S8:
case DataFormat::D24_S8:
case DataFormat::S8:
case DataFormat::D32F:
@@ -137,7 +161,7 @@ static const std::vector fsTexCol = {
"layout(location = 0) in vec4 oColor0;\n"
"layout(location = 1) in vec2 oTexCoord0;\n"
"layout(location = 0) out vec4 fragColor0;\n"
- "layout(set = 0, binding = 1) uniform sampler2D Sampler0;\n"
+ "layout(set = 1, binding = 1) uniform sampler2D Sampler0;\n"
"void main() { fragColor0 = texture(Sampler0, oTexCoord0) * oColor0; }\n"
}
};
@@ -181,7 +205,7 @@ static const std::vector fsTexColRBSwizzle = {
"layout(location = 0) in vec4 oColor0;\n"
"layout(location = 1) in vec2 oTexCoord0;\n"
"layout(location = 0) out vec4 fragColor0\n;"
- "layout(set = 0, binding = 1) uniform sampler2D Sampler0;\n"
+ "layout(set = 1, binding = 1) uniform sampler2D Sampler0;\n"
"void main() { fragColor0 = texture(Sampler0, oTexCoord0).bgra * oColor0; }\n"
}
};
@@ -270,7 +294,7 @@ static const std::vector vsCol = {
R"(#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
-layout (std140, set = 0, binding = 0) uniform bufferVals {
+layout (std140, set = 1, binding = 0) uniform bufferVals {
mat4 WorldViewProj;
vec2 TintSaturation;
} myBufferVals;
@@ -416,7 +440,7 @@ VS_OUTPUT main(VS_INPUT input) {
R"(#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
-layout (std140, set = 0, binding = 0) uniform bufferVals {
+layout (std140, set = 1, binding = 0) uniform bufferVals {
mat4 WorldViewProj;
vec2 TintSaturation;
} myBufferVals;
@@ -712,6 +736,8 @@ const char *Bugs::GetBugName(uint32_t bug) {
case RASPBERRY_SHADER_COMP_HANG: return "RASPBERRY_SHADER_COMP_HANG";
case MALI_CONSTANT_LOAD_BUG: return "MALI_CONSTANT_LOAD_BUG";
case SUBPASS_FEEDBACK_BROKEN: return "SUBPASS_FEEDBACK_BROKEN";
+ case GEOMETRY_SHADERS_SLOW_OR_BROKEN: return "GEOMETRY_SHADERS_SLOW_OR_BROKEN";
+ case ADRENO_RESOURCE_DEADLOCK: return "ADRENO_RESOURCE_DEADLOCK";
default: return "(N/A)";
}
}
diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h
index 8c440deb04..5d68541f3e 100644
--- a/Common/GPU/thin3d.h
+++ b/Common/GPU/thin3d.h
@@ -13,8 +13,10 @@
#include
#include
+#include "Common/Common.h"
#include "Common/GPU/DataFormat.h"
#include "Common/GPU/Shader.h"
+#include "Common/GPU/MiscTypes.h"
#include "Common/Data/Collections/Slice.h"
namespace Lin {
@@ -241,12 +243,16 @@ enum class NativeObject {
BACKBUFFER_DEPTH_TEX,
FEATURE_LEVEL,
INIT_COMMANDBUFFER,
- BOUND_TEXTURE0_IMAGEVIEW,
- BOUND_TEXTURE1_IMAGEVIEW,
- BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW,
+ BOUND_TEXTURE0_IMAGEVIEW, // Layer etc depends on how you bound it...
+ BOUND_TEXTURE1_IMAGEVIEW, // Layer etc depends on how you bound it...
+ BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW_ALL_LAYERS,
+ BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW_RT,
RENDER_MANAGER,
TEXTURE_VIEW,
NULL_IMAGEVIEW,
+ NULL_IMAGEVIEW_ARRAY,
+ FRAME_DATA_DESC_SET_LAYOUT,
+ THIN3D_PIPELINE_LAYOUT,
};
enum FBChannel {
@@ -293,7 +299,8 @@ struct FramebufferDesc {
int width;
int height;
int depth;
- int numColorAttachments;
+ int numLayers;
+ int multiSampleLevel; // 0 = 1xaa, 1 = 2xaa, and so on.
bool z_stencil;
const char *tag; // For graphics debuggers
};
@@ -334,6 +341,7 @@ public:
MALI_CONSTANT_LOAD_BUG = 9,
SUBPASS_FEEDBACK_BROKEN = 10,
GEOMETRY_SHADERS_SLOW_OR_BROKEN = 11,
+ ADRENO_RESOURCE_DEADLOCK = 12,
MAX_BUG,
};
@@ -388,6 +396,9 @@ struct AutoRef {
*this = p.ptr;
return *this;
}
+ bool operator !=(const AutoRef &p) const {
+ return ptr != p.ptr;
+ }
T *operator->() const {
return ptr;
@@ -395,6 +406,16 @@ struct AutoRef {
operator T *() {
return ptr;
}
+ operator bool() const {
+ return ptr != nullptr;
+ }
+
+ void clear() {
+ if (ptr) {
+ ptr->Release();
+ ptr = nullptr;
+ }
+ }
T *ptr = nullptr;
};
@@ -415,9 +436,12 @@ class Framebuffer : public RefCountedObject {
public:
int Width() { return width_; }
int Height() { return height_; }
+ int Layers() { return layers_; }
+ int MultiSampleLevel() { return multiSampleLevel_; }
+
virtual void UpdateTag(const char *tag) {}
protected:
- int width_ = -1, height_ = -1;
+ int width_ = -1, height_ = -1, layers_ = 1, multiSampleLevel_ = 0;
};
class Buffer : public RefCountedObject {
@@ -459,10 +483,7 @@ public:
virtual ShaderStage GetStage() const = 0;
};
-class Pipeline : public RefCountedObject {
-public:
- virtual ~Pipeline() {}
-};
+class Pipeline : public RefCountedObject { };
class RasterState : public RefCountedObject {};
@@ -534,7 +555,6 @@ struct DeviceCaps {
bool depthRangeMinusOneToOne; // OpenGL style depth
bool geometryShaderSupported;
bool tesselationShaderSupported;
- bool multiViewport;
bool dualSourceBlend;
bool logicOpSupported;
bool depthClampSupported;
@@ -551,9 +571,17 @@ struct DeviceCaps {
bool fragmentShaderInt32Supported;
bool textureNPOTFullySupported;
bool fragmentShaderDepthWriteSupported;
+ bool fragmentShaderStencilWriteSupported;
bool textureDepthSupported;
bool blendMinMaxSupported;
+ bool multiViewSupported;
+ bool isTilingGPU; // This means that it benefits from correct store-ops, msaa without backing memory, etc.
+ bool sampleRateShadingSupported;
+ // From the other backends, we can detect if D3D9 support is known bad (like on Xe) and disable it.
+ bool supportsD3D9;
+
+ u32 multiSampleLevelsMask; // Bit n is set if (1 << n) is a valid multisample level. Bit 0 is always set.
std::string deviceName; // The device name to use when creating the thin3d context, to get the same one.
};
@@ -593,6 +621,14 @@ struct RenderPassInfo {
const char *tag;
};
+const int ALL_LAYERS = -1;
+
+enum class TextureBindFlags {
+ NONE = 0,
+ VULKAN_BIND_ARRAY = 1,
+};
+ENUM_CLASS_BITOPS(TextureBindFlags);
+
class DrawContext {
public:
virtual ~DrawContext();
@@ -654,11 +690,11 @@ public:
// These functions should be self explanatory.
// Binding a zero render target means binding the backbuffer.
+ // If an fbo has two layers, we bind for stereo rendering ALWAYS. There's no rendering to one layer anymore.
virtual void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) = 0;
- virtual Framebuffer *GetCurrentRenderTarget() = 0;
// binding must be < MAX_TEXTURE_SLOTS (0, 1 are okay if it's 2).
- virtual void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) = 0;
+ virtual void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) = 0;
// Framebuffer fetch / input attachment support, needs to be explicit in Vulkan.
virtual void BindCurrentFramebufferForColorInput() {}
@@ -683,7 +719,7 @@ public:
virtual void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) = 0;
virtual void BindSamplerStates(int start, int count, SamplerState **state) = 0;
- virtual void BindTextures(int start, int count, Texture **textures) = 0;
+ virtual void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags = TextureBindFlags::NONE) = 0;
virtual void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) = 0;
virtual void BindIndexBuffer(Buffer *indexBuffer, int offset) = 0;
@@ -706,7 +742,7 @@ public:
// Clear state cached within thin3d. Must be called after directly calling API functions.
// Note that framebuffer state (which framebuffer is bounds) may not be cached.
// Must not actually perform any API calls itself since this can be called when no framebuffer is bound for rendering.
- virtual void InvalidateCachedState() = 0;
+ virtual void Invalidate(InvalidationFlags flags) = 0;
virtual void BindPipeline(Pipeline *pipeline) = 0;
@@ -740,7 +776,9 @@ public:
// This is called when we launch a new game, so any collected internal stats in the backends don't carry over.
virtual void ResetStats() {}
- virtual int GetCurrentStepId() const = 0;
+ // Used by the DrawEngines to know when they have to re-apply some state.
+ // Not very elegant, but more elegant than the old passId hack.
+ virtual void SetInvalidationCallback(InvalidationCallback callback) = 0;
protected:
ShaderModule *vsPresets_[VS_MAX_PRESET];
diff --git a/Common/Input/InputState.h b/Common/Input/InputState.h
index faa6416e30..f5d43e209d 100644
--- a/Common/Input/InputState.h
+++ b/Common/Input/InputState.h
@@ -33,6 +33,7 @@ enum {
DEVICE_ID_ACCELEROMETER = 30,
DEVICE_ID_XR_CONTROLLER_LEFT = 40,
DEVICE_ID_XR_CONTROLLER_RIGHT = 41,
+ DEVICE_ID_TOUCH = 42,
};
//number of contiguous generic joypad IDs
diff --git a/Common/Input/KeyCodes.h b/Common/Input/KeyCodes.h
index f00dbe8091..15f42c07c1 100644
--- a/Common/Input/KeyCodes.h
+++ b/Common/Input/KeyCodes.h
@@ -261,7 +261,14 @@ typedef enum _keycode_t {
NKCODE_EXT_MOUSEBUTTON_4 = 1006,
NKCODE_EXT_MOUSEBUTTON_5 = 1007,
NKCODE_EXT_MOUSEWHEEL_UP = 1008,
- NKCODE_EXT_MOUSEWHEEL_DOWN = 1009
+ NKCODE_EXT_MOUSEWHEEL_DOWN = 1009,
+
+ // Virtual reality controller motion
+ NKCODE_EXT_MOTION_UP = 1101,
+ NKCODE_EXT_MOTION_DOWN = 1102,
+ NKCODE_EXT_MOTION_LEFT = 1103,
+ NKCODE_EXT_MOTION_RIGHT = 1104,
+ NKCODE_EXT_MOTION_FORWARD = 1105,
} keycode_t;
enum AndroidJoystickAxis {
diff --git a/Common/LogManager.cpp b/Common/LogManager.cpp
index cdcb224667..13084098a4 100644
--- a/Common/LogManager.cpp
+++ b/Common/LogManager.cpp
@@ -56,7 +56,7 @@ void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char
if (instance) {
instance->Log(level, type, file, line, fmt, args);
} else {
- // Fall back to printf if we're before the log manager has been initialized.
+ // Fall back to printf or direct android logger with a small buffer if the log manager hasn't been initialized yet.
#if PPSSPP_PLATFORM(ANDROID)
char temp[512];
vsnprintf(temp, sizeof(temp), fmt, args);
@@ -200,7 +200,7 @@ void LogManager::SaveConfig(Section *section) {
}
}
-void LogManager::LoadConfig(Section *section, bool debugDefaults) {
+void LogManager::LoadConfig(const Section *section, bool debugDefaults) {
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; i++) {
bool enabled = false;
int level = 0;
diff --git a/Common/LogManager.h b/Common/LogManager.h
index 65423b6121..3ee1347f42 100644
--- a/Common/LogManager.h
+++ b/Common/LogManager.h
@@ -55,7 +55,7 @@ public:
FileLogListener(const char *filename);
~FileLogListener();
- void Log(const LogMessage &msg);
+ void Log(const LogMessage &msg) override;
bool IsValid() { if (!fp_) return false; else return true; }
bool IsEnabled() const { return m_enable; }
@@ -71,13 +71,12 @@ private:
class OutputDebugStringLogListener : public LogListener {
public:
- void Log(const LogMessage &msg);
+ void Log(const LogMessage &msg) override;
};
class RingbufferLogListener : public LogListener {
public:
- RingbufferLogListener() : curMessage_(0), count_(0), enabled_(false) {}
- void Log(const LogMessage &msg);
+ void Log(const LogMessage &msg) override;
bool IsEnabled() const { return enabled_; }
void SetEnabled(bool enable) { enabled_ = enable; }
@@ -89,9 +88,9 @@ public:
private:
enum { MAX_LOGS = 128 };
LogMessage messages_[MAX_LOGS];
- int curMessage_;
- int count_;
- bool enabled_;
+ int curMessage_ = 0;
+ int count_ = 0;
+ bool enabled_ = false;
};
// TODO: A simple buffered log that can be used to display the log in-window
@@ -184,5 +183,5 @@ public:
void ChangeFileLog(const char *filename);
void SaveConfig(Section *section);
- void LoadConfig(Section *section, bool debugDefaults);
+ void LoadConfig(const Section *section, bool debugDefaults);
};
diff --git a/Common/Math/expression_parser.cpp b/Common/Math/expression_parser.cpp
index 970c9c4a20..b591845bce 100644
--- a/Common/Math/expression_parser.cpp
+++ b/Common/Math/expression_parser.cpp
@@ -59,45 +59,54 @@ const ExpressionOpcode ExpressionOpcodes[] = {
{ "", 0, 0, 0, false } // EXOP_NONE
};
-bool parseNumber(char* str, int defaultrad, int len, uint32_t& result)
-{
+static int radixFromZeroPrefix(char c) {
+ switch (tolower(c)) {
+ case 'b': return 2;
+ case 'o': return 8;
+ case 'x': return 16;
+ // Inventing a prefix since we default to hex.
+ case 'd': return 10;
+ }
+
+ return -1;
+}
+
+static int radixFromSuffix(char c, int defaultrad) {
+ switch (tolower(c)) {
+ case 'o': return 8;
+ case 'h': return 16;
+ case 'i': return 10;
+ case 'u': return 10;
+ case 'b': return defaultrad == 16 ? -1 : 2;
+ }
+
+ return -1;
+}
+
+bool parseNumber(char *str, int defaultrad, int len, uint32_t &result) {
int val = 0;
int r = 0;
- if (len == 0) len = (int) strlen(str);
+ if (len == 0)
+ len = (int)strlen(str);
- if (str[0] == '0' && tolower(str[1]) == 'x')
- {
- r = 16;
- str+=2;
- len-=2;
- } else if (str[0] == '$')
- {
+ if (str[0] == '0' && radixFromZeroPrefix(str[1]) != -1) {
+ r = radixFromZeroPrefix(str[1]);
+ str += 2;
+ len -= 2;
+ } else if (str[0] == '$') {
r = 16;
str++;
len--;
- } else if (str[0] == '0' && tolower(str[1]) == 'o')
- {
- r = 8;
- str+=2;
- len-=2;
- } else {
- if (!(str[0] >= '0' && str[0] <= '9')) return false;
-
- if (tolower(str[len-1]) == 'b' && defaultrad != 16)
- {
- r = 2;
- len--;
- } else if (tolower(str[len-1]) == 'o')
- {
- r = 8;
- len--;
- } else if (tolower(str[len-1]) == 'h')
- {
- r = 16;
+ } else if (str[0] >= '0' && str[0] <= '9') {
+ int suffix = radixFromSuffix(str[len - 1], defaultrad);
+ if (suffix != -1) {
+ r = suffix;
len--;
} else {
r = defaultrad;
}
+ } else {
+ return false;
}
switch (r)
@@ -397,8 +406,8 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
size_t num = 0;
uint32_t opcode;
std::vector valueStack;
- unsigned int arg[5];
- float fArg[5];
+ unsigned int arg[5]{};
+ float fArg[5]{};
bool useFloat = false;
while (num < exp.size())
diff --git a/Common/Math/expression_parser.h b/Common/Math/expression_parser.h
index 3d39340a42..55de0a234a 100644
--- a/Common/Math/expression_parser.h
+++ b/Common/Math/expression_parser.h
@@ -15,6 +15,7 @@ enum ExpressionType
class IExpressionFunctions
{
public:
+ virtual ~IExpressionFunctions() {}
virtual bool parseReference(char* str, uint32_t& referenceIndex) = 0;
virtual bool parseSymbol(char* str, uint32_t& symbolValue) = 0;
virtual uint32_t getReferenceValue(uint32_t referenceIndex) = 0;
diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp
index fec31dabb8..bac18da12d 100644
--- a/Common/Net/HTTPClient.cpp
+++ b/Common/Net/HTTPClient.cpp
@@ -224,7 +224,7 @@ void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *prog
inbuffer->TakeLineCRLF(&line);
if (!line.size())
return;
- unsigned int chunkSize;
+ unsigned int chunkSize = 0;
sscanf(line.c_str(), "%x", &chunkSize);
if (chunkSize) {
std::string data;
diff --git a/Common/Net/URL.h b/Common/Net/URL.h
index 7fcaa0c455..e7f33e6d8a 100644
--- a/Common/Net/URL.h
+++ b/Common/Net/URL.h
@@ -15,6 +15,8 @@
struct UrlEncoder
{
+ virtual ~UrlEncoder() {}
+
UrlEncoder() : paramCount(0)
{
data.reserve(256);
@@ -129,8 +131,7 @@ struct MultipartFormDataEncoder : UrlEncoder
boundary = temp;
}
- virtual void Add(const std::string &key, const std::string &value)
- {
+ void Add(const std::string &key, const std::string &value) override {
Add(key, value, "", "");
}
@@ -158,13 +159,11 @@ struct MultipartFormDataEncoder : UrlEncoder
Add(key, std::string((const char *)&value[0], value.size()), filename, mimeType);
}
- virtual void Finish()
- {
+ void Finish() override {
data += "--" + boundary + "--";
}
- virtual std::string GetMimeType() const
- {
+ std::string GetMimeType() const override {
return "multipart/form-data; boundary=\"" + boundary + "\"";
}
diff --git a/Common/Render/DrawBuffer.cpp b/Common/Render/DrawBuffer.cpp
index 9b09e7675b..7a50f2844a 100644
--- a/Common/Render/DrawBuffer.cpp
+++ b/Common/Render/DrawBuffer.cpp
@@ -130,7 +130,7 @@ void DrawBuffer::RectOutline(float x, float y, float w, float h, uint32_t color,
vLine(x + w, y, y + h + pixel_in_dps_y, color);
}
-void DrawBuffer::MultiVGradient(float x, float y, float w, float h, GradientStop *stops, int numStops) {
+void DrawBuffer::MultiVGradient(float x, float y, float w, float h, const GradientStop *stops, int numStops) {
for (int i = 0; i < numStops - 1; i++) {
float t0 = stops[i].t, t1 = stops[i+1].t;
uint32_t c0 = stops[i].color, c1 = stops[i+1].color;
@@ -285,7 +285,9 @@ void DrawBuffer::DrawImageRotated(ImageID atlas_image, float x, float y, float s
{u1, image->v2},
};
for (int i = 0; i < 6; i++) {
- rot(v[i], angle, x, y);
+ if (angle != 0.0f) {
+ rot(v[i], angle, x, y);
+ }
V(v[i][0], v[i][1], 0, color, uv[i][0], uv[i][1]);
}
}
diff --git a/Common/Render/DrawBuffer.h b/Common/Render/DrawBuffer.h
index 4a29893c15..8c6262c7c7 100644
--- a/Common/Render/DrawBuffer.h
+++ b/Common/Render/DrawBuffer.h
@@ -88,7 +88,7 @@ public:
RectVGradient(x, y, w, h, colorTop, darkenColor(colorTop));
}
- void MultiVGradient(float x, float y, float w, float h, GradientStop *stops, int numStops);
+ void MultiVGradient(float x, float y, float w, float h, const GradientStop *stops, int numStops);
void RectCenter(float x, float y, float w, float h, uint32_t color) {
Rect(x - w/2, y - h/2, w, h, color);
diff --git a/UI/TextureUtil.cpp b/Common/Render/ManagedTexture.cpp
similarity index 83%
rename from UI/TextureUtil.cpp
rename to Common/Render/ManagedTexture.cpp
index 80dfb59e1a..771d4f8a92 100644
--- a/UI/TextureUtil.cpp
+++ b/Common/Render/ManagedTexture.cpp
@@ -14,8 +14,7 @@
#include "Common/File/VFS/VFS.h"
#include "Common/Log.h"
#include "Common/TimeUtil.h"
-#include "UI/TextureUtil.h"
-#include "UI/GameInfoCache.h"
+#include "Common/Render/ManagedTexture.h"
static Draw::DataFormat ZimToT3DFormat(int zim) {
switch (zim) {
@@ -217,38 +216,3 @@ std::unique_ptr CreateTextureFromFileData(Draw::DrawContext *dra
return std::unique_ptr();
}
}
-
-void GameIconView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
- w = textureWidth_;
- h = textureHeight_;
-}
-
-void GameIconView::Draw(UIContext &dc) {
- using namespace UI;
- std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, GAMEINFO_WANTBG | GAMEINFO_WANTSIZE);
-
- if (!info->icon.texture) {
- return;
- }
-
- textureWidth_ = info->icon.texture->Width() * scale_;
- textureHeight_ = info->icon.texture->Height() * scale_;
-
- // Fade icon with the backgrounds.
- double loadTime = info->icon.timeLoaded;
- auto pic = info->GetBGPic();
- if (pic) {
- loadTime = std::max(loadTime, pic->timeLoaded);
- }
- uint32_t color = whiteAlpha(ease((time_now_d() - loadTime) * 3));
-
- // Adjust size so we don't stretch the image vertically or horizontally.
- // Make sure it's not wider than 144 (like Doom Legacy homebrew), ugly in the grid mode.
- float nw = std::min(bounds_.h * textureWidth_ / textureHeight_, (float)bounds_.w);
-
- dc.Flush();
- dc.GetDrawContext()->BindTexture(0, info->icon.texture->GetTexture());
- dc.Draw()->Rect(bounds_.x, bounds_.y, nw, bounds_.h, color);
- dc.Flush();
- dc.RebindTexture();
-}
diff --git a/UI/TextureUtil.h b/Common/Render/ManagedTexture.h
similarity index 73%
rename from UI/TextureUtil.h
rename to Common/Render/ManagedTexture.h
index 19d9b40cb0..ed6e9918f6 100644
--- a/UI/TextureUtil.h
+++ b/Common/Render/ManagedTexture.h
@@ -43,18 +43,3 @@ private:
std::unique_ptr CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType fileType, bool generateMips);
std::unique_ptr CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, int size, ImageFileType fileType, bool generateMips, const char *name);
-class GameIconView : public UI::InertView {
-public:
- GameIconView(const Path &gamePath, float scale, UI::LayoutParams *layoutParams = 0)
- : InertView(layoutParams), gamePath_(gamePath), scale_(scale) {}
-
- void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
- void Draw(UIContext &dc) override;
- std::string DescribeText() const override { return ""; }
-
-private:
- Path gamePath_;
- float scale_ = 1.0f;
- int textureWidth_ = 0;
- int textureHeight_ = 0;
-};
diff --git a/Common/Render/Text/draw_text_win.cpp b/Common/Render/Text/draw_text_win.cpp
index d5af5af762..6cd9885fee 100644
--- a/Common/Render/Text/draw_text_win.cpp
+++ b/Common/Render/Text/draw_text_win.cpp
@@ -69,6 +69,7 @@ TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *draw) : TextDrawer(draw), ct
bmi.bmiHeader.biBitCount = 32;
ctx_->hbmBitmap = CreateDIBSection(ctx_->hDC, &bmi, DIB_RGB_COLORS, (VOID**)&ctx_->pBitmapBits, NULL, 0);
+ _assert_(ctx_->hbmBitmap != nullptr);
SetMapMode(ctx_->hDC, MM_TEXT);
SelectObject(ctx_->hDC, ctx_->hbmBitmap);
diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp
index 3f0d47499b..6849acbd19 100644
--- a/Common/RiscVEmitter.cpp
+++ b/Common/RiscVEmitter.cpp
@@ -615,7 +615,7 @@ const u8 *RiscVEmitter::AlignCode16() {
const u8 *RiscVEmitter::AlignCodePage() {
int page_size = GetMemoryProtectPageSize();
- int c = int((u64)code_ & (page_size - 1));
+ int c = int((intptr_t)code_ & ((intptr_t)page_size - 1));
if (c)
ReserveCodeSpace(page_size - c);
return code_;
@@ -747,7 +747,7 @@ void RiscVEmitter::SetRegToImmediate(RiscVReg rd, uint64_t value, RiscVReg temp)
if (SignReduce64(v, 32) == v || force) {
int32_t lower = (int32_t)SignReduce64(svalue, 12);
int32_t upper = ((v - lower) >> 12) << 12;
- _assert_msg_(force || upper + lower == v, "Upper + ADDI immediate math mistake?");
+ _assert_msg_(force || (int64_t)upper + lower == v, "Upper + ADDI immediate math mistake?");
// Should be fused on some processors.
(this->*upperOp)(rd, upper);
diff --git a/Common/RiscVEmitter.h b/Common/RiscVEmitter.h
index f2e512a32c..e1b8266a56 100644
--- a/Common/RiscVEmitter.h
+++ b/Common/RiscVEmitter.h
@@ -113,7 +113,7 @@ struct FixupBranch {
~FixupBranch();
const u8 *ptr = nullptr;
- FixupBranchType type;
+ FixupBranchType type = FixupBranchType::B;
};
class RiscVEmitter {
diff --git a/Common/Serialize/SerializeFuncs.h b/Common/Serialize/SerializeFuncs.h
index 3080749fa3..c2919de799 100644
--- a/Common/Serialize/SerializeFuncs.h
+++ b/Common/Serialize/SerializeFuncs.h
@@ -33,11 +33,7 @@ void Do(PointerWrap &p, tm &t);
// This makes it a compile error if you forget to define DoState() on non-POD.
// Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason...
-#ifdef _MSC_VER
-template::value, bool isPointer = std::is_pointer::value>
-#else
-template::value>
-#endif
+template::value && std::is_trivial::value, bool isPointer = std::is_pointer::value>
struct DoHelper_ {
static void DoArray(PointerWrap &p, T *x, int count) {
for (int i = 0; i < count; ++i)
@@ -89,7 +85,8 @@ template
void DoVector(PointerWrap &p, std::vector &x, T &default_val) {
u32 vec_size = (u32)x.size();
Do(p, vec_size);
- x.resize(vec_size, default_val);
+ if (vec_size != x.size())
+ x.resize(vec_size, default_val);
if (vec_size > 0)
DoArray(p, &x[0], vec_size);
}
diff --git a/Common/Serialize/SerializeMap.h b/Common/Serialize/SerializeMap.h
index 9190f5067e..4e5619bdbe 100644
--- a/Common/Serialize/SerializeMap.h
+++ b/Common/Serialize/SerializeMap.h
@@ -38,8 +38,8 @@ void DoMap(PointerWrap &p, M &x, typename M::mapped_type &default_val) {
x[first] = second;
--number;
}
+ break;
}
- break;
case PointerWrap::MODE_WRITE:
case PointerWrap::MODE_MEASURE:
case PointerWrap::MODE_VERIFY:
@@ -52,8 +52,10 @@ void DoMap(PointerWrap &p, M &x, typename M::mapped_type &default_val) {
--number;
++itr;
}
+ break;
}
- break;
+ case PointerWrap::MODE_NOOP:
+ break;
}
}
@@ -109,8 +111,8 @@ void DoMultimap(PointerWrap &p, M &x, typename M::mapped_type &default_val) {
x.insert(std::make_pair(first, second));
--number;
}
+ break;
}
- break;
case PointerWrap::MODE_WRITE:
case PointerWrap::MODE_MEASURE:
case PointerWrap::MODE_VERIFY:
@@ -122,8 +124,10 @@ void DoMultimap(PointerWrap &p, M &x, typename M::mapped_type &default_val) {
--number;
++itr;
}
+ break;
}
- break;
+ case PointerWrap::MODE_NOOP:
+ break;
}
}
diff --git a/Common/Serialize/SerializeSet.h b/Common/Serialize/SerializeSet.h
index 7e22f21e56..642b0dc6bb 100644
--- a/Common/Serialize/SerializeSet.h
+++ b/Common/Serialize/SerializeSet.h
@@ -46,9 +46,8 @@ void DoSet(PointerWrap &p, std::set &x) {
Do(p, *itr++);
}
break;
-
- default:
- ERROR_LOG(SAVESTATE, "Savestate error: invalid mode %d.", p.mode);
+ case PointerWrap::MODE_NOOP:
+ break;
}
}
diff --git a/Common/Serialize/Serializer.cpp b/Common/Serialize/Serializer.cpp
index 6b36fb2167..71f5168c3b 100644
--- a/Common/Serialize/Serializer.cpp
+++ b/Common/Serialize/Serializer.cpp
@@ -33,8 +33,27 @@ enum class SerializeCompressType {
static constexpr SerializeCompressType SAVE_TYPE = SerializeCompressType::ZSTD;
-PointerWrapSection PointerWrap::Section(const char *title, int ver) {
- return Section(title, ver, ver);
+void PointerWrap::RewindForWrite(u8 *writePtr) {
+ _assert_(mode == MODE_MEASURE);
+ // Switch to writing mode, save the size for later checking and start again.
+ measuredSize_ = Offset();
+ mode = MODE_WRITE;
+ *ptr = writePtr;
+ ptrStart_ = writePtr;
+}
+
+bool PointerWrap::CheckAfterWrite() {
+ _assert_(error != ERROR_NONE || mode == MODE_WRITE);
+ size_t offset = Offset();
+ if (measuredSize_ != 0 && offset != measuredSize_) {
+ WARN_LOG(SAVESTATE, "CheckAfterWrite: Size mismatch! %d but expected %d", (int)offset, (int)measuredSize_);
+ return false;
+ }
+ if (!checkpoints_.empty() && curCheckpoint_ != checkpoints_.size()) {
+ WARN_LOG(SAVESTATE, "Checkpoint count mismatch!");
+ return false;
+ }
+ return true;
}
PointerWrapSection PointerWrap::Section(const char *title, int minVer, int ver) {
@@ -44,6 +63,32 @@ PointerWrapSection PointerWrap::Section(const char *title, int minVer, int ver)
// This is strncpy because we rely on its weird non-null-terminating zero-filling truncation behaviour.
// Can't replace it with the more sensible truncate_cpy because that would break savestates.
strncpy(marker, title, sizeof(marker));
+
+ // Compare the measure and write passes. Sanity check to catch bugs, doesn't do anything for output.
+ size_t offset = Offset();
+ if (mode == MODE_MEASURE) {
+ checkpoints_.emplace_back(marker, offset);
+ } else if (mode == MODE_WRITE) {
+ if (!checkpoints_.empty()) {
+ if (checkpoints_.size() <= curCheckpoint_) {
+ WARN_LOG(SAVESTATE, "Write: Not enough checkpoints from measure pass (%d). cur section: %s", (int)checkpoints_.size(), title);
+ SetError(ERROR_FAILURE);
+ return PointerWrapSection(*this, -1, title);
+ }
+ if (!checkpoints_[curCheckpoint_].Matches(marker, offset)) {
+ WARN_LOG(SAVESTATE, "Checkpoint mismatch during write! Section %s but expected %s, offset %d but expected %d", title, marker, (int)offset, (int)checkpoints_[curCheckpoint_].offset);
+ if (curCheckpoint_ > 1) {
+ WARN_LOG(SAVESTATE, "Previous checkpoint: %s (%d)", checkpoints_[curCheckpoint_ - 1].title, (int)checkpoints_[curCheckpoint_ - 1].offset);
+ }
+ SetError(ERROR_FAILURE);
+ return PointerWrapSection(*this, -1, title);
+ }
+ } else {
+ WARN_LOG(SAVESTATE, "Writing savestate without checkpoints. This is OK but should be fixed.");
+ }
+ curCheckpoint_++;
+ }
+
if (!ExpectVoid(marker, sizeof(marker))) {
// Might be before we added name markers for safety.
if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion))) {
@@ -60,8 +105,10 @@ PointerWrapSection PointerWrap::Section(const char *title, int minVer, int ver)
if (!firstBadSectionTitle_) {
firstBadSectionTitle_ = title;
}
- WARN_LOG(SAVESTATE, "Savestate failure: wrong version %d found for section '%s'", foundVersion, title);
- SetError(ERROR_FAILURE);
+ if (mode != MODE_NOOP) {
+ WARN_LOG(SAVESTATE, "Savestate failure: wrong version %d found for section '%s'", foundVersion, title);
+ SetError(ERROR_FAILURE);
+ }
return PointerWrapSection(*this, -1, title);
}
return PointerWrapSection(*this, foundVersion, title);
@@ -72,8 +119,9 @@ void PointerWrap::SetError(Error error_) {
error = error_;
}
if (error > ERROR_WARNING) {
- // For the rest of this run, just measure.
- mode = PointerWrap::MODE_MEASURE;
+ // For the rest of this run, do nothing, to avoid running off the end of memory or something,
+ // and also not logspam like MEASURE will do in an error case.
+ mode = PointerWrap::MODE_NOOP;
}
}
@@ -123,6 +171,7 @@ void Do(PointerWrap &p, std::string &x) {
case PointerWrap::MODE_READ: x = (char*)*p.ptr; break;
case PointerWrap::MODE_WRITE: memcpy(*p.ptr, x.c_str(), stringLen); break;
case PointerWrap::MODE_MEASURE: break;
+ case PointerWrap::MODE_NOOP: break;
case PointerWrap::MODE_VERIFY: _dbg_assert_msg_(!strcmp(x.c_str(), (char*)*p.ptr), "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n", x.c_str(), (char *)*p.ptr, p.ptr); break;
}
(*p.ptr) += stringLen;
@@ -150,6 +199,7 @@ void Do(PointerWrap &p, std::wstring &x) {
case PointerWrap::MODE_READ: x = read(); break;
case PointerWrap::MODE_WRITE: memcpy(*p.ptr, x.c_str(), stringLen); break;
case PointerWrap::MODE_MEASURE: break;
+ case PointerWrap::MODE_NOOP: break;
case PointerWrap::MODE_VERIFY: _dbg_assert_msg_(x == read(), "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n", x.c_str(), read().c_str(), p.ptr); break;
}
(*p.ptr) += stringLen;
@@ -177,6 +227,7 @@ void Do(PointerWrap &p, std::u16string &x) {
case PointerWrap::MODE_READ: x = read(); break;
case PointerWrap::MODE_WRITE: memcpy(*p.ptr, x.c_str(), stringLen); break;
case PointerWrap::MODE_MEASURE: break;
+ case PointerWrap::MODE_NOOP: break;
case PointerWrap::MODE_VERIFY: _dbg_assert_msg_(x == read(), "Savestate verification failure: (at %p).\n", p.ptr); break;
}
(*p.ptr) += stringLen;
diff --git a/Common/Serialize/Serializer.h b/Common/Serialize/Serializer.h
index cdf8bd4f06..5169e4a120 100644
--- a/Common/Serialize/Serializer.h
+++ b/Common/Serialize/Serializer.h
@@ -30,6 +30,7 @@
// - Serialization code for anything complex has to be manually written.
#include
+#include
#include
#include
@@ -52,8 +53,7 @@ class PointerWrap;
class PointerWrapSection
{
public:
- PointerWrapSection(PointerWrap &p, int ver, const char *title) : p_(p), ver_(ver), title_(title) {
- }
+ PointerWrapSection(PointerWrap &p, int ver, const char *title) : p_(p), ver_(ver), title_(title) {}
~PointerWrapSection();
bool operator == (const int &v) const { return ver_ == v; }
@@ -73,15 +73,32 @@ private:
const char *title_;
};
+// For measure vs write detailed verification
+struct SerializeCheckpoint {
+ char title[17]; // 16-byte section header, plus a zero terminator for debug printing.
+ size_t offset;
+
+ SerializeCheckpoint(char _title[16], size_t off) {
+ memcpy(title, _title, 16);
+ title[16] = 0;
+ offset = off;
+ }
+
+ bool Matches(const char *_title, size_t off) const {
+ return memcmp(title, _title, 16) == 0 && off == offset;
+ }
+};
+
// Wrapper class
class PointerWrap
{
public:
enum Mode {
MODE_READ = 1, // load
- MODE_WRITE, // save
- MODE_MEASURE, // calculate size
- MODE_VERIFY, // compare
+ MODE_WRITE, // save
+ MODE_MEASURE, // calculate size
+ MODE_VERIFY, // compare
+ MODE_NOOP, // don't do anything. Useful to cleanly doing stuff once we've hit an error.
};
enum Error {
@@ -94,19 +111,26 @@ public:
Mode mode;
Error error = ERROR_NONE;
- PointerWrap(u8 **ptr_, Mode mode_) : ptr(ptr_), mode(mode_) {}
- PointerWrap(unsigned char **ptr_, int mode_) : ptr((u8**)ptr_), mode((Mode)mode_) {}
+ PointerWrap(u8 **ptr_, Mode mode_) : ptr(ptr_), ptrStart_(*ptr), mode(mode_) {
+ if (mode == MODE_MEASURE) {
+ checkpoints_.reserve(750);
+ }
+ }
- PointerWrapSection Section(const char *title, int ver);
+ void RewindForWrite(u8 *writePtr);
+ bool CheckAfterWrite();
// The returned object can be compared against the version that was loaded.
// This can be used to support versions as old as minVer.
// Version = 0 means the section was not found.
PointerWrapSection Section(const char *title, int minVer, int ver);
+ PointerWrapSection Section(const char *title, int ver) {
+ return Section(title, ver, ver);
+ }
- void SetMode(Mode mode_) {mode = mode_;}
- Mode GetMode() const {return mode;}
- u8 **GetPPtr() {return ptr;}
+ void SetMode(Mode mode_) { mode = mode_; }
+ Mode GetMode() const { return mode; }
+ u8 **GetPPtr() { return ptr; }
void SetError(Error error_);
const char *GetBadSectionTitle() const {
@@ -119,8 +143,14 @@ public:
void DoMarker(const char *prevName, u32 arbitraryNumber = 0x42);
+ size_t Offset() const { return *ptr - ptrStart_; }
+
private:
const char *firstBadSectionTitle_ = nullptr;
+ u8 *ptrStart_;
+ std::vector checkpoints_;
+ size_t curCheckpoint_ = 0;
+ size_t measuredSize_ = 0;
};
class CChunkFileReader
@@ -152,7 +182,7 @@ public:
template
static size_t MeasurePtr(T &_class)
{
- u8 *ptr = 0;
+ u8 *ptr = nullptr;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
_class.DoState(p);
return (size_t)ptr;
@@ -166,13 +196,39 @@ public:
PointerWrap p(&ptr, PointerWrap::MODE_WRITE);
_class.DoState(p);
- if (p.error != p.ERROR_FAILURE && (expected_end == ptr || expected_size == 0)) {
+ if (p.error != PointerWrap::ERROR_FAILURE && (expected_end == ptr || expected_size == 0)) {
return ERROR_NONE;
} else {
return ERROR_BROKEN_STATE;
}
}
+ template
+ static Error MeasureAndSavePtr(T &_class, u8 **saved, size_t *savedSize)
+ {
+ u8 *ptr = nullptr;
+ PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
+ _class.DoState(p);
+ _assert_(p.error == PointerWrap::ERROR_NONE);
+
+ size_t measuredSize = p.Offset();
+ u8 *data = (u8 *)malloc(measuredSize);
+ if (!data)
+ return ERROR_BAD_ALLOC;
+
+ p.RewindForWrite(data);
+ _class.DoState(p);
+
+ if (p.CheckAfterWrite()) {
+ *saved = data;
+ *savedSize = measuredSize;
+ return ERROR_NONE;
+ } else {
+ free(data);
+ return ERROR_BROKEN_STATE;
+ }
+ }
+
// Load file template
template
static Error Load(const Path &filename, std::string *gitVersion, T& _class, std::string *failureReason)
@@ -197,19 +253,16 @@ public:
template
static Error Save(const Path &filename, const std::string &title, const char *gitVersion, T& _class)
{
- // Get data
- size_t const sz = MeasurePtr(_class);
- u8 *buffer = (u8 *)malloc(sz);
- if (!buffer)
- return ERROR_BAD_ALLOC;
- Error error = SavePtr(buffer, _class, sz);
+ u8 *buffer;
+ size_t sz;
+ Error error = MeasureAndSavePtr(_class, &buffer, &sz);
- // SaveFile takes ownership of buffer
+ // SaveFile takes ownership of buffer (malloc/free)
if (error == ERROR_NONE)
error = SaveFile(filename, title, gitVersion, buffer, sz);
return error;
}
-
+
template
static Error Verify(T& _class)
{
diff --git a/Common/StringUtils.cpp b/Common/StringUtils.cpp
index 3a9e29c61a..3115e1f03c 100644
--- a/Common/StringUtils.cpp
+++ b/Common/StringUtils.cpp
@@ -286,6 +286,25 @@ void SplitString(const std::string& str, const char delim, std::vector& output)
{
size_t next = 0;
@@ -294,7 +313,7 @@ void GetQuotedStrings(const std::string& str, std::vector& output)
if (str[pos] == '\"' || str[pos] == '\'') {
if (even) {
//quoted text
- output.emplace_back(str.substr(next, pos - next));
+ output.emplace_back(ApplyHtmlEscapes(str.substr(next, pos - next)));
even = 0;
} else {
//non quoted text
diff --git a/Common/Thread/Channel.h b/Common/Thread/Channel.h
index b6b23fdebe..82772bb6f4 100644
--- a/Common/Thread/Channel.h
+++ b/Common/Thread/Channel.h
@@ -20,10 +20,11 @@ struct Mailbox {
std::mutex mutex_;
std::condition_variable condvar_;
T data_{};
+ bool dataReceived_ = false;
T Wait() {
std::unique_lock lock(mutex_);
- while (!data_) {
+ while (!dataReceived_) {
condvar_.wait(lock);
}
return data_;
@@ -31,7 +32,7 @@ struct Mailbox {
bool Poll(T *data) {
std::unique_lock lock(mutex_);
- if (data_) {
+ if (dataReceived_) {
*data = data_;
return true;
} else {
@@ -41,8 +42,9 @@ struct Mailbox {
bool Send(T data) {
std::unique_lock lock(mutex_);
- if (!data_) {
+ if (!dataReceived_) {
data_ = data;
+ dataReceived_ = true;
condvar_.notify_all();
return true;
} else {
diff --git a/Common/Thread/Promise.h b/Common/Thread/Promise.h
index 4145b4b6c1..96c36f9605 100644
--- a/Common/Thread/Promise.h
+++ b/Common/Thread/Promise.h
@@ -107,7 +107,7 @@ public:
}
}
- // For outside injection of data, when not using Spawn
+ // For outside injection of data, when not using Spawn.
void Post(T data) {
rx_->Send(data);
}
diff --git a/Common/Thread/ThreadManager.cpp b/Common/Thread/ThreadManager.cpp
index 5fca697635..0414ee5e79 100644
--- a/Common/Thread/ThreadManager.cpp
+++ b/Common/Thread/ThreadManager.cpp
@@ -113,7 +113,7 @@ bool ThreadManager::TeardownTask(Task *task, bool enqueue) {
if (task->Type() == TaskType::CPU_COMPUTE) {
global_->compute_queue.push_back(task);
global_->compute_queue_size++;
- } else if (task->Type() == TaskType::CPU_COMPUTE) {
+ } else if (task->Type() == TaskType::IO_BLOCKING) {
global_->io_queue.push_back(task);
global_->io_queue_size++;
} else {
@@ -125,7 +125,12 @@ bool ThreadManager::TeardownTask(Task *task, bool enqueue) {
static void WorkerThreadFunc(GlobalThreadContext *global, ThreadContext *thread) {
char threadName[16];
- snprintf(threadName, sizeof(threadName), "PoolWorker %d", thread->index);
+ if (thread->type == TaskType::CPU_COMPUTE) {
+ snprintf(threadName, sizeof(threadName), "PoolWorker %d", thread->index);
+ } else {
+ _assert_(thread->type == TaskType::IO_BLOCKING);
+ snprintf(threadName, sizeof(threadName), "PoolWorkerIO %d", thread->index);
+ }
SetCurrentThreadName(threadName);
const bool isCompute = thread->type == TaskType::CPU_COMPUTE;
@@ -199,8 +204,8 @@ void ThreadManager::Init(int numRealCores, int numLogicalCoresPerCpu) {
thread->cancelled.store(false);
thread->private_single.store(nullptr);
thread->type = i < numComputeThreads_ ? TaskType::CPU_COMPUTE : TaskType::IO_BLOCKING;
- thread->thread = std::thread(&WorkerThreadFunc, global_, thread);
thread->index = i;
+ thread->thread = std::thread(&WorkerThreadFunc, global_, thread);
global_->threads_.push_back(thread);
}
}
diff --git a/Common/Thread/ThreadManager.h b/Common/Thread/ThreadManager.h
index 5147ad21cc..bd71d8c775 100644
--- a/Common/Thread/ThreadManager.h
+++ b/Common/Thread/ThreadManager.h
@@ -4,7 +4,7 @@
// The new threadpool.
-// To help future smart scheduling.
+// To help smart scheduling.
enum class TaskType {
CPU_COMPUTE,
IO_BLOCKING,
diff --git a/Common/Thunk.cpp b/Common/Thunk.cpp
index 6248d087a0..581ce94b94 100644
--- a/Common/Thunk.cpp
+++ b/Common/Thunk.cpp
@@ -43,7 +43,7 @@ void ThunkManager::Init()
#endif
AllocCodeSpace(THUNK_ARENA_SIZE);
- BeginWrite();
+ BeginWrite(512);
save_regs = GetCodePtr();
#if PPSSPP_ARCH(AMD64)
for (int i = 2; i < ABI_GetNumXMMRegs(); i++)
@@ -151,7 +151,7 @@ const void *ThunkManager::ProtectFunction(const void *function, int num_params)
_assert_msg_(region != nullptr, "Can't protect functions before the emu is started.");
- BeginWrite();
+ BeginWrite(128);
const u8 *call_point = GetCodePtr();
Enter(this, true);
diff --git a/Common/TimeUtil.cpp b/Common/TimeUtil.cpp
index ad00e1e9a7..ad07fbd18c 100644
--- a/Common/TimeUtil.cpp
+++ b/Common/TimeUtil.cpp
@@ -21,8 +21,6 @@
// TODO: https://github.com/floooh/sokol/blob/9a6237fcdf213e6da48e4f9201f144bcb2dcb46f/sokol_time.h#L229-L248
-static double curtime = 0;
-
#ifdef _WIN32
static LARGE_INTEGER frequency;
@@ -33,7 +31,6 @@ double time_now_d() {
if (frequency.QuadPart == 0) {
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&startTime);
- curtime = 0.0;
frequencyMult = 1.0 / static_cast(frequency.QuadPart);
}
LARGE_INTEGER time;
diff --git a/Common/UI/AsyncImageFileView.cpp b/Common/UI/AsyncImageFileView.cpp
new file mode 100644
index 0000000000..925d1c9337
--- /dev/null
+++ b/Common/UI/AsyncImageFileView.cpp
@@ -0,0 +1,113 @@
+#include "Common/UI/View.h"
+#include "Common/UI/AsyncImageFileView.h"
+#include "Common/UI/Context.h"
+#include "Common/Render/DrawBuffer.h"
+
+AsyncImageFileView::AsyncImageFileView(const Path &filename, UI::ImageSizeMode sizeMode, UI::LayoutParams *layoutParams)
+ : UI::Clickable(layoutParams), canFocus_(true), filename_(filename), color_(0xFFFFFFFF), sizeMode_(sizeMode), textureFailed_(false), fixedSizeW_(0.0f), fixedSizeH_(0.0f) {}
+
+AsyncImageFileView::~AsyncImageFileView() {}
+
+static float DesiredSize(float sz, float contentSize, UI::MeasureSpec spec) {
+ float measured;
+ UI::MeasureBySpec(sz, contentSize, spec, &measured);
+ return measured;
+}
+
+void AsyncImageFileView::GetContentDimensionsBySpec(const UIContext &dc, UI::MeasureSpec horiz, UI::MeasureSpec vert, float &w, float &h) const {
+ if (texture_ && texture_->GetTexture()) {
+ float texw = (float)texture_->Width();
+ float texh = (float)texture_->Height();
+ float desiredW = DesiredSize(layoutParams_->width, w, horiz);
+ float desiredH = DesiredSize(layoutParams_->height, h, vert);
+ switch (sizeMode_) {
+ case UI::IS_FIXED:
+ w = fixedSizeW_;
+ h = fixedSizeH_;
+ break;
+ case UI::IS_KEEP_ASPECT:
+ w = texw;
+ h = texh;
+ if (desiredW != w || desiredH != h) {
+ float aspect = w / h;
+ // We need the other dimension based on the desired scale to find the best aspect.
+ float desiredWOther = DesiredSize(layoutParams_->height, h * (desiredW / w), vert);
+ float desiredHOther = DesiredSize(layoutParams_->width, w * (desiredH / h), horiz);
+
+ float diffW = fabsf(aspect - desiredW / desiredWOther);
+ float diffH = fabsf(aspect - desiredH / desiredHOther);
+ if (diffW < diffH) {
+ w = desiredW;
+ h = desiredWOther;
+ } else {
+ w = desiredHOther;
+ h = desiredH;
+ }
+ }
+ break;
+ case UI::IS_DEFAULT:
+ default:
+ w = texw;
+ h = texh;
+ break;
+ }
+ } else {
+ w = 16;
+ h = 16;
+ }
+}
+
+void AsyncImageFileView::SetFilename(const Path &filename) {
+ if (filename_ != filename) {
+ textureFailed_ = false;
+ filename_ = filename;
+ texture_.reset(nullptr);
+ }
+}
+
+void AsyncImageFileView::DeviceLost() {
+ if (texture_.get())
+ texture_->DeviceLost();
+}
+
+void AsyncImageFileView::DeviceRestored(Draw::DrawContext *draw) {
+ if (texture_.get())
+ texture_->DeviceRestored(draw);
+}
+
+void AsyncImageFileView::Draw(UIContext &dc) {
+ using namespace Draw;
+ if (!texture_ && !textureFailed_ && !filename_.empty()) {
+ texture_ = CreateTextureFromFile(dc.GetDrawContext(), filename_.c_str(), DETECT, true);
+ if (!texture_.get())
+ textureFailed_ = true;
+ }
+
+ if (HasFocus()) {
+ dc.FillRect(dc.theme->itemFocusedStyle.background, bounds_.Expand(3));
+ }
+
+ // TODO: involve sizemode
+ if (texture_ && texture_->GetTexture()) {
+ dc.Flush();
+ dc.GetDrawContext()->BindTexture(0, texture_->GetTexture());
+ dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, color_);
+ dc.Flush();
+ dc.RebindTexture();
+ if (!text_.empty()) {
+ dc.DrawText(text_.c_str(), bounds_.centerX() + 1, bounds_.centerY() + 1, 0x80000000, ALIGN_CENTER | FLAG_DYNAMIC_ASCII);
+ dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER | FLAG_DYNAMIC_ASCII);
+ }
+ } else {
+ if (!filename_.empty()) {
+ // draw a black rectangle to represent the missing screenshot.
+ dc.FillRect(UI::Drawable(0xFF000000), GetBounds());
+ } else {
+ // draw a dark gray rectangle to represent no save state.
+ dc.FillRect(UI::Drawable(0x50202020), GetBounds());
+ }
+ if (!text_.empty()) {
+ dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER | FLAG_DYNAMIC_ASCII);
+ }
+ }
+}
diff --git a/Common/UI/AsyncImageFileView.h b/Common/UI/AsyncImageFileView.h
new file mode 100644
index 0000000000..5acaca30a5
--- /dev/null
+++ b/Common/UI/AsyncImageFileView.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "Common/UI/View.h"
+#include "Common/Render/ManagedTexture.h"
+#include "Common/File/Path.h"
+
+class UIContext;
+
+// AsyncImageFileView loads a texture from a file, and reloads it as necessary.
+// TODO: Actually make async, doh.
+class AsyncImageFileView : public UI::Clickable {
+public:
+ AsyncImageFileView(const Path &filename, UI::ImageSizeMode sizeMode, UI::LayoutParams *layoutParams = 0);
+ ~AsyncImageFileView();
+
+ void GetContentDimensionsBySpec(const UIContext &dc, UI::MeasureSpec horiz, UI::MeasureSpec vert, float &w, float &h) const override;
+ void Draw(UIContext &dc) override;
+ std::string DescribeText() const override { return text_; }
+
+ void DeviceLost() override;
+ void DeviceRestored(Draw::DrawContext *draw) override;
+
+ void SetFilename(const Path &filename);
+ void SetColor(uint32_t color) { color_ = color; }
+ void SetOverlayText(std::string text) { text_ = text; }
+ void SetFixedSize(float fixW, float fixH) { fixedSizeW_ = fixW; fixedSizeH_ = fixH; }
+ void SetCanBeFocused(bool can) { canFocus_ = can; }
+
+ bool CanBeFocused() const override { return canFocus_; }
+
+ const Path &GetFilename() const { return filename_; }
+
+private:
+ bool canFocus_;
+ Path filename_;
+ std::string text_;
+ uint32_t color_;
+ UI::ImageSizeMode sizeMode_;
+
+ std::unique_ptr texture_;
+ bool textureFailed_;
+ float fixedSizeW_;
+ float fixedSizeH_;
+};
diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp
index 21c687bd2b..40bc95c824 100644
--- a/Common/UI/Context.cpp
+++ b/Common/UI/Context.cpp
@@ -10,9 +10,9 @@
#include "Common/UI/Context.h"
#include "Common/Render/DrawBuffer.h"
#include "Common/Render/Text/draw_text.h"
+#include "Common/Render/ManagedTexture.h"
#include "Common/Log.h"
#include "Common/LogReporting.h"
-#include "UI/TextureUtil.h"
UIContext::UIContext() {
fontStyle_ = new UI::FontStyle();
diff --git a/Common/UI/Context.h b/Common/UI/Context.h
index ac26ab0202..b7c6982176 100644
--- a/Common/UI/Context.h
+++ b/Common/UI/Context.h
@@ -71,7 +71,6 @@ public:
DrawBuffer *Draw() const { return uidrawbuffer_; }
DrawBuffer *DrawTop() const { return uidrawbufferTop_; }
- const UI::Theme *theme;
// Utility methods
TextDrawer *Text() const { return textDrawer_; }
@@ -94,6 +93,9 @@ public:
const Bounds &GetBounds() const { return bounds_; }
Bounds GetLayoutBounds() const;
Draw::DrawContext *GetDrawContext() { return draw_; }
+ const UI::Theme &GetTheme() const {
+ return *theme;
+ }
void SetCurZ(float curZ);
void PushTransform(const UITransform &transform);
@@ -106,6 +108,9 @@ public:
screenTag_ = tag;
}
+ // TODO: Move to private.
+ const UI::Theme *theme;
+
private:
Draw::DrawContext *draw_ = nullptr;
Bounds bounds_;
diff --git a/Common/UI/Root.cpp b/Common/UI/Root.cpp
index b02ae9ee55..ccd1f67352 100644
--- a/Common/UI/Root.cpp
+++ b/Common/UI/Root.cpp
@@ -33,9 +33,7 @@ std::atomic hasDispatchQueue;
std::deque g_dispatchQueue;
void EventTriggered(Event *e, EventParams params) {
- DispatchQueueItem item;
- item.e = e;
- item.params = params;
+ DispatchQueueItem item{ e, params };
std::unique_lock guard(eventMutex_);
// Set before adding so we lock and check the added value.
diff --git a/Common/UI/UI.h b/Common/UI/UI.h
index ccd6b50dbe..4357693d16 100644
--- a/Common/UI/UI.h
+++ b/Common/UI/UI.h
@@ -43,6 +43,7 @@ extern DrawBuffer ui_draw2d_front; // for things that need to be on top of the r
// Implement this interface to style your lists
class UIListAdapter {
public:
+ virtual ~UIListAdapter() {}
virtual size_t getCount() const = 0;
virtual void drawItem(int item, int x, int y, int w, int h, bool active) const = 0;
virtual float itemHeight(int itemIndex) const { return 64; }
@@ -52,8 +53,8 @@ public:
class StringVectorListAdapter : public UIListAdapter {
public:
StringVectorListAdapter(const std::vector *items) : items_(items) {}
- virtual size_t getCount() const { return items_->size(); }
- virtual void drawItem(int item, int x, int y, int w, int h, bool active) const;
+ size_t getCount() const override { return items_->size(); }
+ void drawItem(int item, int x, int y, int w, int h, bool active) const override;
private:
const std::vector *items_;
diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp
index ecebae3a81..d1623101d0 100644
--- a/Common/UI/UIScreen.cpp
+++ b/Common/UI/UIScreen.cpp
@@ -12,7 +12,6 @@
#include "Common/UI/Root.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Render/DrawBuffer.h"
-#include "Common/VR/PPSSPPVR.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
@@ -21,12 +20,17 @@ static const bool ClickDebug = false;
UIScreen::UIScreen()
: Screen() {
+ lastVertical_ = UseVerticalLayout();
}
UIScreen::~UIScreen() {
delete root_;
}
+bool UIScreen::UseVerticalLayout() const {
+ return dp_yres > dp_xres * 1.1f;
+}
+
void UIScreen::DoRecreateViews() {
if (recreateViews_) {
std::lock_guard guard(screenManager()->inputLock_);
@@ -61,6 +65,12 @@ void UIScreen::DoRecreateViews() {
}
void UIScreen::update() {
+ bool vertical = UseVerticalLayout();
+ if (vertical != lastVertical_) {
+ RecreateViews();
+ lastVertical_ = vertical;
+ }
+
DoRecreateViews();
if (root_) {
@@ -89,10 +99,6 @@ void UIScreen::preRender() {
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "UI");
screenManager()->getUIContext()->BeginFrame();
- if (IsVRBuild()) {
- BindVRFramebuffer();
- }
-
Draw::Viewport viewport;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
@@ -317,10 +323,6 @@ void PopupScreen::TriggerFinish(DialogResult result) {
}
}
-void PopupScreen::resized() {
- RecreateViews();
-}
-
void PopupScreen::CreateViews() {
using namespace UI;
UIContext &dc = *screenManager()->getUIContext();
@@ -341,7 +343,9 @@ void PopupScreen::CreateViews() {
box_->SetDropShadowExpand(std::max(dp_xres, dp_yres));
View *title = new PopupHeader(title_);
- box_->Add(title);
+ if (HasTitleBar()) {
+ box_->Add(title);
+ }
CreatePopupContents(box_);
root_->SetDefaultFocusView(box_);
@@ -408,6 +412,39 @@ UI::EventReturn ListPopupScreen::OnListChoice(UI::EventParams &e) {
namespace UI {
+PopupContextMenuScreen::PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCategory *category, UI::View *sourceView)
+ : PopupScreen("", "", ""), items_(items), itemCount_(itemCount), category_(category), sourceView_(sourceView)
+{
+ enabled_.resize(itemCount, true);
+ SetPopupOrigin(sourceView);
+}
+
+void PopupContextMenuScreen::CreatePopupContents(UI::ViewGroup *parent) {
+ for (size_t i = 0; i < itemCount_; i++) {
+ if (items_[i].imageID) {
+ Choice *choice = new Choice(category_->T(items_[i].text), ImageID(items_[i].imageID));
+ parent->Add(choice);
+ if (enabled_[i]) {
+ choice->OnClick.Add([=](EventParams &p) {
+ TriggerFinish(DR_OK);
+ p.a = (uint32_t)i;
+ OnChoice.Dispatch(p);
+ return EVENT_DONE;
+ });
+ }
+ else {
+ choice->SetEnabled(false);
+ }
+ }
+ }
+
+ // Hacky: Override the position to look like a popup menu.
+ AnchorLayoutParams *ap = (AnchorLayoutParams *)parent->GetLayoutParams();
+ ap->center = false;
+ ap->left = sourceView_->GetBounds().x;
+ ap->top = sourceView_->GetBounds().y2();
+}
+
std::string ChopTitle(const std::string &title) {
size_t pos = title.find('\n');
if (pos != title.npos) {
@@ -819,7 +856,7 @@ void AbstractChoiceWithValueDisplay::GetContentDimensionsBySpec(const UIContext
const std::string valueText = ValueText();
int paddingX = 12;
// Assume we want at least 20% of the size for the label, at a minimum.
- float availWidth = (horiz.size - paddingX * 2) * 0.8f;
+ float availWidth = (horiz.size - paddingX * 2) * (text_.empty() ? 1.0f : 0.8f);
if (availWidth < 0) {
availWidth = 65535.0f;
}
@@ -831,11 +868,15 @@ void AbstractChoiceWithValueDisplay::GetContentDimensionsBySpec(const UIContext
valueW += paddingX;
// Give the choice itself less space to grow in, so it shrinks if needed.
- MeasureSpec horizLabel = horiz;
- horizLabel.size -= valueW;
+ // MeasureSpec horizLabel = horiz;
+ // horizLabel.size -= valueW;
Choice::GetContentDimensionsBySpec(dc, horiz, vert, w, h);
w += valueW;
+ // Fill out anyway if there's space.
+ if (horiz.type == AT_MOST && w < horiz.size) {
+ w = horiz.size;
+ }
h = std::max(h, valueH);
}
@@ -854,20 +895,30 @@ void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) {
dc.SetFontStyle(dc.theme->uiFont);
const std::string valueText = ValueText();
- // Assume we want at least 20% of the size for the label, at a minimum.
- float availWidth = (bounds_.w - paddingX * 2) * 0.8f;
- float scale = CalculateValueScale(dc, valueText, availWidth);
- float w, h;
- Bounds availBounds(0, 0, availWidth, bounds_.h);
- dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &w, &h, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
- textPadding_.right = w + paddingX;
+ // If there is a label, assume we want at least 20% of the size for it, at a minimum.
- Choice::Draw(dc);
- dc.SetFontScale(scale, scale);
- Bounds valueBounds(bounds_.x2() - textPadding_.right, bounds_.y, w, bounds_.h);
- dc.DrawTextRect(valueText.c_str(), valueBounds, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
- dc.SetFontScale(1.0f, 1.0f);
+ if (!text_.empty()) {
+ float availWidth = (bounds_.w - paddingX * 2) * 0.8f;
+ float scale = CalculateValueScale(dc, valueText, availWidth);
+
+ float w, h;
+ Bounds availBounds(0, 0, availWidth, bounds_.h);
+ dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &w, &h, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
+ textPadding_.right = w + paddingX;
+
+ Choice::Draw(dc);
+ dc.SetFontScale(scale, scale);
+ Bounds valueBounds(bounds_.x2() - textPadding_.right, bounds_.y, w, bounds_.h);
+ dc.DrawTextRect(valueText.c_str(), valueBounds, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
+ dc.SetFontScale(1.0f, 1.0f);
+ } else {
+ Choice::Draw(dc);
+ float scale = CalculateValueScale(dc, valueText, bounds_.w);
+ dc.SetFontScale(scale, scale);
+ dc.DrawTextRect(valueText.c_str(), bounds_.Expand(-paddingX, 0.0f), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
+ dc.SetFontScale(1.0f, 1.0f);
+ }
}
float AbstractChoiceWithValueDisplay::CalculateValueScale(const UIContext &dc, const std::string &valueText, float availWidth) const {
diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h
index 4b113d3001..50a5f1f2d9 100644
--- a/Common/UI/UIScreen.h
+++ b/Common/UI/UIScreen.h
@@ -42,7 +42,8 @@ protected:
virtual void CreateViews() = 0;
virtual void DrawBackground(UIContext &dc) {}
- virtual void RecreateViews() override { recreateViews_ = true; }
+ void RecreateViews() override { recreateViews_ = true; }
+ bool UseVerticalLayout() const;
UI::ViewGroup *root_ = nullptr;
Vec3 translation_ = Vec3(0.0f);
@@ -54,6 +55,7 @@ private:
void DoRecreateViews();
bool recreateViews_ = true;
+ bool lastVertical_;
};
class UIDialogScreen : public UIScreen {
@@ -72,13 +74,12 @@ public:
PopupScreen(std::string title, std::string button1 = "", std::string button2 = "");
virtual void CreatePopupContents(UI::ViewGroup *parent) = 0;
- virtual void CreateViews() override;
- virtual bool isTransparent() const override { return true; }
- virtual bool touch(const TouchInput &touch) override;
- virtual bool key(const KeyInput &key) override;
- virtual void resized() override;
+ void CreateViews() override;
+ bool isTransparent() const override { return true; }
+ bool touch(const TouchInput &touch) override;
+ bool key(const KeyInput &key) override;
- virtual void TriggerFinish(DialogResult result) override;
+ void TriggerFinish(DialogResult result) override;
void SetPopupOrigin(const UI::View *view);
void SetPopupOffset(float y);
@@ -91,9 +92,10 @@ protected:
virtual bool ShowButtons() const { return true; }
virtual bool CanComplete(DialogResult result) { return true; }
virtual void OnCompleted(DialogResult result) {}
+ virtual bool HasTitleBar() const { return true; }
const std::string &Title() { return title_; }
- virtual void update() override;
+ void update() override;
private:
UI::ViewGroup *box_;
@@ -141,9 +143,9 @@ public:
UI::Event OnChoice;
protected:
- virtual bool FillVertical() const override { return false; }
- virtual bool ShowButtons() const override { return showButtons_; }
- virtual void CreatePopupContents(UI::ViewGroup *parent) override;
+ bool FillVertical() const override { return false; }
+ bool ShowButtons() const override { return showButtons_; }
+ void CreatePopupContents(UI::ViewGroup *parent) override;
UI::StringVectorListAdaptor adaptor_;
UI::ListView *listView_ = nullptr;
@@ -162,9 +164,9 @@ public:
UI::Event OnChoice;
protected:
- virtual bool FillVertical() const override { return false; }
- virtual bool ShowButtons() const override { return true; }
- virtual void CreatePopupContents(UI::ViewGroup *parent) override;
+ bool FillVertical() const override { return false; }
+ bool ShowButtons() const override { return true; }
+ void CreatePopupContents(UI::ViewGroup *parent) override;
private:
void OnCompleted(DialogResult result) override;
@@ -180,7 +182,7 @@ class SliderPopupScreen : public PopupScreen {
public:
SliderPopupScreen(int *value, int minValue, int maxValue, const std::string &title, int step = 1, const std::string &units = "")
: PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step) {}
- virtual void CreatePopupContents(ViewGroup *parent) override;
+ void CreatePopupContents(ViewGroup *parent) override;
void SetNegativeDisable(const std::string &str) {
negativeLabel_ = str;
@@ -196,7 +198,7 @@ private:
EventReturn OnIncrease(EventParams ¶ms);
EventReturn OnTextChange(EventParams ¶ms);
EventReturn OnSliderChange(EventParams ¶ms);
- virtual void OnCompleted(DialogResult result) override;
+ void OnCompleted(DialogResult result) override;
Slider *slider_ = nullptr;
UI::TextEdit *edit_ = nullptr;
std::string units_;
@@ -225,7 +227,7 @@ private:
EventReturn OnDecrease(EventParams ¶ms);
EventReturn OnTextChange(EventParams ¶ms);
EventReturn OnSliderChange(EventParams ¶ms);
- virtual void OnCompleted(DialogResult result) override;
+ void OnCompleted(DialogResult result) override;
UI::SliderFloat *slider_;
UI::TextEdit *edit_;
std::string units_;
@@ -243,14 +245,14 @@ class TextEditPopupScreen : public PopupScreen {
public:
TextEditPopupScreen(std::string *value, const std::string &placeholder, const std::string &title, int maxLen)
: PopupScreen(title, "OK", "Cancel"), value_(value), placeholder_(placeholder), maxLen_(maxLen) {}
- virtual void CreatePopupContents(ViewGroup *parent) override;
+ void CreatePopupContents(ViewGroup *parent) override;
const char *tag() const override { return "TextEditPopup"; }
Event OnChange;
private:
- virtual void OnCompleted(DialogResult result) override;
+ void OnCompleted(DialogResult result) override;
TextEdit *edit_;
std::string *value_;
std::string textEditValue_;
@@ -258,13 +260,45 @@ private:
int maxLen_;
};
+// TODO: Break out a lot of popup stuff from UIScreen.h.
+
+struct ContextMenuItem {
+ const char *text;
+ const char *imageID;
+};
+
+// Once a selection has been made,
+class PopupContextMenuScreen : public PopupScreen {
+public:
+ PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCategory *category, UI::View *sourceView);
+ void CreatePopupContents(ViewGroup *parent) override;
+
+ const char *tag() const override { return "ContextMenuPopup"; }
+
+ void SetEnabled(size_t index, bool enabled) {
+ enabled_[index] = enabled;
+ }
+
+ UI::Event OnChoice;
+
+protected:
+ bool HasTitleBar() const override { return false; }
+
+private:
+ const ContextMenuItem *items_;
+ size_t itemCount_;
+ I18NCategory *category_;
+ UI::View *sourceView_;
+ std::vector enabled_;
+};
+
class AbstractChoiceWithValueDisplay : public UI::Choice {
public:
AbstractChoiceWithValueDisplay(const std::string &text, LayoutParams *layoutParams = nullptr)
: Choice(text, layoutParams) {
}
- virtual void Draw(UIContext &dc) override;
+ void Draw(UIContext &dc) override;
void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override;
protected:
@@ -344,7 +378,7 @@ public:
}
protected:
- void PostChoiceCallback(int num) {
+ void PostChoiceCallback(int num) override {
*valueStr_ = choices_[num];
}
@@ -386,7 +420,7 @@ private:
std::string negativeLabel_;
std::string units_;
ScreenManager *screenManager_;
- bool restoreFocus_;
+ bool restoreFocus_ = false;
};
class PopupSliderChoiceFloat : public AbstractChoiceWithValueDisplay {
@@ -423,7 +457,7 @@ private:
std::string zeroLabel_;
std::string units_;
ScreenManager *screenManager_;
- bool restoreFocus_;
+ bool restoreFocus_ = false;
bool liveUpdate_ = false;
bool hasDropShadow_ = true;
};
@@ -463,8 +497,8 @@ public:
private:
std::string ValueText() const override;
- int *iValue_ = nullptr;
std::string *sValue_ = nullptr;
+ int *iValue_ = nullptr;
const char *category_ = nullptr;
std::string (*translateCallback_)(const char *value) = nullptr;
};
diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp
index 72d3443ab0..8be9b3076d 100644
--- a/Common/UI/View.cpp
+++ b/Common/UI/View.cpp
@@ -16,15 +16,15 @@
#include "Common/System/System.h"
#include "Common/TimeUtil.h"
#include "Common/StringUtils.h"
+#include "Common/Log.h"
namespace UI {
-const float ITEM_HEIGHT = 64.f;
-const float MIN_TEXT_SCALE = 0.8f;
-const float MAX_ITEM_SIZE = 65535.0f;
+static constexpr Size ITEM_HEIGHT = 64.f;
+static constexpr float MIN_TEXT_SCALE = 0.8f;
+static constexpr float MAX_ITEM_SIZE = 65535.0f;
void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measured) {
- *measured = sz;
if (sz == WRAP_CONTENT) {
if (spec.type == UNSPECIFIED)
*measured = contentWidth;
@@ -40,6 +40,8 @@ void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measure
*measured = spec.size;
} else if (spec.type == EXACTLY || (spec.type == AT_MOST && *measured > spec.size)) {
*measured = spec.size;
+ } else {
+ *measured = sz;
}
}
@@ -167,7 +169,7 @@ void View::PersistData(PersistStatus status, std::string anonId, PersistMap &sto
}
}
-Point View::GetFocusPosition(FocusDirection dir) {
+Point View::GetFocusPosition(FocusDirection dir) const {
// The +2/-2 is some extra fudge factor to cover for views sitting right next to each other.
// Distance zero yields strange results otherwise.
switch (dir) {
@@ -226,11 +228,13 @@ void Clickable::FocusChanged(int focusFlags) {
}
}
-void Clickable::Touch(const TouchInput &input) {
+bool Clickable::Touch(const TouchInput &input) {
+ bool contains = bounds_.Contains(input.x, input.y);
+
if (!IsEnabled()) {
dragging_ = false;
down_ = false;
- return;
+ return contains;
}
if (input.flags & TOUCH_DOWN) {
@@ -255,6 +259,7 @@ void Clickable::Touch(const TouchInput &input) {
downCountDown_ = 0;
dragging_ = false;
}
+ return contains;
}
static bool MatchesKeyDef(const std::vector &defs, const KeyInput &key) {
@@ -350,21 +355,24 @@ bool Clickable::Key(const KeyInput &key) {
return ret;
}
-void StickyChoice::Touch(const TouchInput &input) {
+bool StickyChoice::Touch(const TouchInput &touch) {
+ bool contains = bounds_.Contains(touch.x, touch.y);
dragging_ = false;
if (!IsEnabled()) {
down_ = false;
- return;
+ return contains;
}
- if (input.flags & TOUCH_DOWN) {
- if (bounds_.Contains(input.x, input.y)) {
+ if (touch.flags & TOUCH_DOWN) {
+ if (contains) {
if (IsFocusMovementEnabled())
SetFocusedView(this);
down_ = true;
Click();
+ return true;
}
}
+ return false;
}
bool StickyChoice::Key(const KeyInput &key) {
@@ -407,6 +415,7 @@ void ClickableItem::GetContentDimensions(const UIContext &dc, float &w, float &h
ClickableItem::ClickableItem(LayoutParams *layoutParams) : Clickable(layoutParams) {
if (!layoutParams) {
+ // The default LayoutParams assigned by View::View defaults to WRAP_CONTENT/WRAP_CONTENT.
if (layoutParams_->width == WRAP_CONTENT)
layoutParams_->width = FILL_PARENT;
}
@@ -483,7 +492,7 @@ void Choice::Draw(UIContext &dc) {
if (image_.isValid() && text_.empty()) {
dc.Draw()->DrawImageRotated(image_, bounds_.centerX(), bounds_.centerY(), imgScale_, imgRot_, style.fgColor, imgFlipH_);
- } else {
+ } else if (!text_.empty()) {
dc.SetFontStyle(dc.theme->uiFont);
int paddingX = 12;
@@ -491,6 +500,7 @@ void Choice::Draw(UIContext &dc) {
if (image_.isValid()) {
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(image_);
+ _dbg_assert_(image);
paddingX += image->w + 6;
availWidth -= image->w + 6;
// TODO: Use scale rotation and flip here as well (DrawImageRotated is always ALIGN_CENTER for now)
@@ -690,28 +700,55 @@ void CheckBox::Draw(UIContext &dc) {
if (!IsEnabled()) {
style = dc.theme->itemDisabledStyle;
}
- if (HasFocus()) {
- style = dc.theme->itemFocusedStyle;
- }
- if (down_) {
- style = dc.theme->itemDownStyle;
+ ImageID image = Toggled() ? dc.theme->checkOn : dc.theme->checkOff;
+
+ // In image mode, light up instead of showing a checkbox.
+ if (imageID_.isValid()) {
+ image = imageID_;
+ if (Toggled()) {
+ if (HasFocus()) {
+ style = dc.theme->itemDownStyle;
+ } else {
+ style = dc.theme->itemFocusedStyle;
+ }
+ } else {
+ if (HasFocus()) {
+ style = dc.theme->itemDownStyle;
+ } else {
+ style = dc.theme->itemStyle;
+ }
+ }
+
+ if (down_) {
+ style.background.color = lightenColor(style.background.color);
+ }
+
+ } else {
+ if (HasFocus()) {
+ style = dc.theme->itemFocusedStyle;
+ }
+ if (down_) {
+ style = dc.theme->itemDownStyle;
+ }
}
+
dc.SetFontStyle(dc.theme->uiFont);
- ClickableItem::Draw(dc);
+ DrawBG(dc, style);
- ImageID image = Toggled() ? dc.theme->checkOn : dc.theme->checkOff;
float imageW, imageH;
dc.Draw()->MeasureImage(image, &imageW, &imageH);
const int paddingX = 12;
// Padding right of the checkbox image too.
const float availWidth = bounds_.w - paddingX * 2 - imageW - paddingX;
- float scale = CalculateTextScale(dc, availWidth);
- dc.SetFontScale(scale, scale);
- Bounds textBounds(bounds_.x + paddingX, bounds_.y, availWidth, bounds_.h);
- dc.DrawTextRect(text_.c_str(), textBounds, style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT);
+ if (!text_.empty()) {
+ float scale = CalculateTextScale(dc, availWidth);
+ dc.SetFontScale(scale, scale);
+ Bounds textBounds(bounds_.x + paddingX, bounds_.y, availWidth, bounds_.h);
+ dc.DrawTextRect(text_.c_str(), textBounds, style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT);
+ }
dc.Draw()->DrawImage(image, bounds_.x2() - paddingX, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER);
dc.SetFontScale(1.0f, 1.0f);
}
@@ -737,24 +774,41 @@ float CheckBox::CalculateTextScale(const UIContext &dc, float availWidth) const
void CheckBox::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
ImageID image = Toggled() ? dc.theme->checkOn : dc.theme->checkOff;
+ if (imageID_.isValid()) {
+ image = imageID_;
+ }
+
float imageW, imageH;
dc.Draw()->MeasureImage(image, &imageW, &imageH);
const int paddingX = 12;
+
+ if (imageID_.isValid()) {
+ w = imageW + paddingX * 2;
+ h = std::max(imageH, ITEM_HEIGHT);
+ return;
+ }
+
+ // The below code is kinda wacky, we shouldn't involve bounds_ here.
+
// Padding right of the checkbox image too.
float availWidth = bounds_.w - paddingX * 2 - imageW - paddingX;
if (availWidth < 0.0f) {
// Let it have as much space as it needs.
availWidth = MAX_ITEM_SIZE;
}
- float scale = CalculateTextScale(dc, availWidth);
- float actualWidth, actualHeight;
- Bounds availBounds(0, 0, availWidth, bounds_.h);
- dc.MeasureTextRect(dc.theme->uiFont, scale, scale, text_.c_str(), (int)text_.size(), availBounds, &actualWidth, &actualHeight, ALIGN_VCENTER | FLAG_WRAP_TEXT);
+ if (!text_.empty()) {
+ float scale = CalculateTextScale(dc, availWidth);
+ float actualWidth, actualHeight;
+ Bounds availBounds(0, 0, availWidth, bounds_.h);
+ dc.MeasureTextRect(dc.theme->uiFont, scale, scale, text_.c_str(), (int)text_.size(), availBounds, &actualWidth, &actualHeight, ALIGN_VCENTER | FLAG_WRAP_TEXT);
+ h = std::max(actualHeight, ITEM_HEIGHT);
+ } else {
+ h = std::max(imageH, ITEM_HEIGHT);
+ }
w = bounds_.w;
- h = std::max(actualHeight, ITEM_HEIGHT);
}
void BitCheckBox::Toggle() {
@@ -1069,12 +1123,14 @@ static std::string FirstLine(const std::string &text) {
return text;
}
-void TextEdit::Touch(const TouchInput &touch) {
+bool TextEdit::Touch(const TouchInput &touch) {
if (touch.flags & TOUCH_DOWN) {
if (bounds_.Contains(touch.x, touch.y)) {
SetFocusedView(this, true);
+ return true;
}
}
+ return false;
}
bool TextEdit::Key(const KeyInput &input) {
@@ -1259,14 +1315,15 @@ void Spinner::Draw(UIContext &dc) {
}
}
-void TriggerButton::Touch(const TouchInput &input) {
+bool TriggerButton::Touch(const TouchInput &input) {
+ bool contains = bounds_.Contains(input.x, input.y);
if (input.flags & TOUCH_DOWN) {
- if (bounds_.Contains(input.x, input.y)) {
+ if (contains) {
down_ |= 1 << input.id;
}
}
if (input.flags & TOUCH_MOVE) {
- if (bounds_.Contains(input.x, input.y))
+ if (contains)
down_ |= 1 << input.id;
else
down_ &= ~(1 << input.id);
@@ -1281,6 +1338,8 @@ void TriggerButton::Touch(const TouchInput &input) {
} else {
*bitField_ &= ~bit_;
}
+
+ return contains;
}
void TriggerButton::Draw(UIContext &dc) {
@@ -1344,9 +1403,10 @@ bool Slider::ApplyKey(int keyCode) {
return true;
}
-void Slider::Touch(const TouchInput &input) {
+bool Slider::Touch(const TouchInput &input) {
// Calling it afterwards, so dragging_ hasn't been set false yet when checking it above.
- Clickable::Touch(input);
+ bool contains = Clickable::Touch(input);
+
if (dragging_) {
float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
*value_ = floorf(relativeX * (maxValue_ - minValue_) + minValue_ + 0.5f);
@@ -1360,6 +1420,7 @@ void Slider::Touch(const TouchInput &input) {
// Cancel any key repeat.
repeat_ = -1;
+ return contains;
}
void Slider::Clamp() {
@@ -1470,8 +1531,8 @@ bool SliderFloat::ApplyKey(int keyCode) {
return true;
}
-void SliderFloat::Touch(const TouchInput &input) {
- Clickable::Touch(input);
+bool SliderFloat::Touch(const TouchInput &input) {
+ bool contains = Clickable::Touch(input);
if (dragging_) {
float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
*value_ = (relativeX * (maxValue_ - minValue_) + minValue_);
@@ -1485,6 +1546,7 @@ void SliderFloat::Touch(const TouchInput &input) {
// Cancel any key repeat.
repeat_ = -1;
+ return contains;
}
void SliderFloat::Clamp() {
diff --git a/Common/UI/View.h b/Common/UI/View.h
index 0a78433073..f3c54a0a3d 100644
--- a/Common/UI/View.h
+++ b/Common/UI/View.h
@@ -128,10 +128,10 @@ enum FocusDirection {
FOCUS_NEXT_PAGE,
};
-enum {
- WRAP_CONTENT = -1,
- FILL_PARENT = -2,
-};
+typedef float Size; // can also be WRAP_CONTENT or FILL_PARENT.
+
+static constexpr Size WRAP_CONTENT = -1.0f;
+static constexpr Size FILL_PARENT = -2.0f;
// Gravity
enum Gravity {
@@ -174,8 +174,6 @@ enum class BorderStyle {
ITEM_DOWN_BG,
};
-typedef float Size; // can also be WRAP_CONTENT or FILL_PARENT.
-
enum Orientation {
ORIENT_HORIZONTAL,
ORIENT_VERTICAL,
@@ -289,6 +287,13 @@ struct Margins {
int vert() const {
return top + bottom;
}
+ void SetAll(float f) {
+ int8_t i = (int)f;
+ top = i;
+ bottom = i;
+ left = i;
+ right = i;
+ }
int8_t top;
int8_t bottom;
@@ -377,7 +382,7 @@ public:
// Can even be called on a different thread! This is to really minimize latency, and decouple
// touch response from the frame rate. Same with Key and Axis.
virtual bool Key(const KeyInput &input) { return false; }
- virtual void Touch(const TouchInput &input) {}
+ virtual bool Touch(const TouchInput &input) { return true; }
virtual void Axis(const AxisInput &input) {}
virtual void Update();
@@ -463,7 +468,7 @@ public:
virtual bool IsViewGroup() const { return false; }
virtual bool ContainsSubview(const View *view) const { return false; }
- Point GetFocusPosition(FocusDirection dir);
+ Point GetFocusPosition(FocusDirection dir) const;
template
T *AddTween(T *t) {
@@ -503,7 +508,7 @@ public:
: View(layoutParams) {}
bool Key(const KeyInput &input) override { return false; }
- void Touch(const TouchInput &input) override {}
+ bool Touch(const TouchInput &input) override { return false; }
bool CanBeFocused() const override { return false; }
};
@@ -514,7 +519,7 @@ public:
Clickable(LayoutParams *layoutParams);
bool Key(const KeyInput &input) override;
- void Touch(const TouchInput &input) override;
+ bool Touch(const TouchInput &input) override;
void FocusChanged(int focusFlags) override;
@@ -604,7 +609,7 @@ public:
void Draw(UIContext &dc) override;
std::string DescribeText() const override;
bool Key(const KeyInput &input) override;
- void Touch(const TouchInput &input) override;
+ bool Touch(const TouchInput &input) override;
void Update() override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
void SetShowPercent(bool s) { showPercent_ = s; }
@@ -635,7 +640,7 @@ public:
void Draw(UIContext &dc) override;
std::string DescribeText() const override;
bool Key(const KeyInput &input) override;
- void Touch(const TouchInput &input) override;
+ bool Touch(const TouchInput &input) override;
void Update() override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
@@ -663,7 +668,7 @@ public:
TriggerButton(uint32_t *bitField, uint32_t bit, ImageID imageBackground, ImageID imageForeground, LayoutParams *layoutParams)
: View(layoutParams), down_(0.0), bitField_(bitField), bit_(bit), imageBackground_(imageBackground), imageForeground_(imageForeground) {}
- void Touch(const TouchInput &input) override;
+ bool Touch(const TouchInput &input) override;
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
@@ -757,7 +762,7 @@ public:
: Choice(buttonImage, layoutParams) {}
bool Key(const KeyInput &key) override;
- void Touch(const TouchInput &touch) override;
+ bool Touch(const TouchInput &touch) override;
void FocusChanged(int focusFlags) override;
void Press() { down_ = true; dragging_ = false; }
@@ -829,11 +834,17 @@ private:
class CheckBox : public ClickableItem {
public:
- CheckBox(bool *toggle, const std::string &text, const std::string &smallText = "", LayoutParams *layoutParams = 0)
+ CheckBox(bool *toggle, const std::string &text, const std::string &smallText = "", LayoutParams *layoutParams = nullptr)
: ClickableItem(layoutParams), toggle_(toggle), text_(text), smallText_(smallText) {
OnClick.Handle(this, &CheckBox::OnClicked);
}
+ // Image-only "checkbox", lights up instead of showing a checkmark.
+ CheckBox(bool *toggle, ImageID imageID, LayoutParams *layoutParams = nullptr)
+ : ClickableItem(layoutParams), toggle_(toggle), imageID_(imageID) {
+ OnClick.Handle(this, &CheckBox::OnClicked);
+ }
+
void Draw(UIContext &dc) override;
std::string DescribeText() const override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
@@ -848,6 +859,7 @@ private:
bool *toggle_;
std::string text_;
std::string smallText_;
+ ImageID imageID_;
};
class BitCheckBox : public CheckBox {
@@ -945,7 +957,7 @@ public:
void Draw(UIContext &dc) override;
std::string DescribeText() const override;
bool Key(const KeyInput &key) override;
- void Touch(const TouchInput &touch) override;
+ bool Touch(const TouchInput &touch) override;
Event OnTextChange;
Event OnEnter;
diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp
index f4fb73ffa3..f8405fbb97 100644
--- a/Common/UI/ViewGroup.cpp
+++ b/Common/UI/ViewGroup.cpp
@@ -22,7 +22,7 @@
namespace UI {
-const float ITEM_HEIGHT = 64.f;
+static constexpr Size ITEM_HEIGHT = 64.f;
void ApplyGravity(const Bounds outer, const Margins &margins, float w, float h, int gravity, Bounds &inner) {
inner.w = w;
@@ -31,13 +31,13 @@ void ApplyGravity(const Bounds outer, const Margins &margins, float w, float h,
switch (gravity & G_HORIZMASK) {
case G_LEFT: inner.x = outer.x + margins.left; break;
case G_RIGHT: inner.x = outer.x + outer.w - w - margins.right; break;
- case G_HCENTER: inner.x = outer.x + (outer.w - w) / 2; break;
+ case G_HCENTER: inner.x = outer.x + (outer.w - w) * 0.5f; break;
}
switch (gravity & G_VERTMASK) {
case G_TOP: inner.y = outer.y + margins.top; break;
case G_BOTTOM: inner.y = outer.y + outer.h - h - margins.bottom; break;
- case G_VCENTER: inner.y = outer.y + (outer.h - h) / 2; break;
+ case G_VCENTER: inner.y = outer.y + (outer.h - h) * 0.5f; break;
}
}
@@ -87,12 +87,23 @@ void ViewGroup::PersistData(PersistStatus status, std::string anonId, PersistMap
}
}
-void ViewGroup::Touch(const TouchInput &input) {
+bool ViewGroup::Touch(const TouchInput &input) {
std::lock_guard guard(modifyLock_);
+ bool any = false;
for (auto iter = views_.begin(); iter != views_.end(); ++iter) {
// TODO: If there is a transformation active, transform input coordinates accordingly.
- if ((*iter)->GetVisibility() == V_VISIBLE)
- (*iter)->Touch(input);
+ if ((*iter)->GetVisibility() == V_VISIBLE) {
+ bool touch = (*iter)->Touch(input);
+ any = any || touch;
+ if (exclusiveTouch_ && touch && (input.flags & TOUCH_DOWN)) {
+ break;
+ }
+ }
+ }
+ if (clickableBackground_) {
+ return any || bounds_.Contains(input.x, input.y);
+ } else {
+ return any;
}
}
@@ -260,11 +271,12 @@ static float HorizontalOverlap(const Bounds &a, const Bounds &b) {
// okay they do overlap. Let's clip.
float maxMin = std::max(a.x, b.x);
float minMax = std::min(a.x2(), b.x2());
+ float minW = std::min(a.w, b.w);
float overlap = minMax - maxMin;
- if (overlap < 0.0f)
+ if (overlap < 0.0f || minW <= 0.0f)
return 0.0f;
else
- return std::min(1.0f, overlap / std::min(a.w, b.w));
+ return std::min(1.0f, overlap / minW);
}
// Returns the percentage the smaller one overlaps the bigger one.
@@ -274,14 +286,15 @@ static float VerticalOverlap(const Bounds &a, const Bounds &b) {
// okay they do overlap. Let's clip.
float maxMin = std::max(a.y, b.y);
float minMax = std::min(a.y2(), b.y2());
+ float minH = std::min(a.h, b.h);
float overlap = minMax - maxMin;
- if (overlap < 0.0f)
+ if (overlap < 0.0f || minH <= 0.0f)
return 0.0f;
else
- return std::min(1.0f, overlap / std::min(a.h, b.h));
+ return std::min(1.0f, overlap / minH);
}
-float GetTargetScore(const Point &originPos, int originIndex, View *origin, View *destination, FocusDirection direction) {
+float GetTargetScore(const Point &originPos, int originIndex, const View *origin, const View *destination, FocusDirection direction) {
// Skip labels and things like that.
if (!destination->CanBeFocused())
return 0.0f;
@@ -296,6 +309,9 @@ float GetTargetScore(const Point &originPos, int originIndex, View *origin, View
float dy = destPos.y - originPos.y;
float distance = sqrtf(dx*dx + dy*dy);
+ if (distance == 0.0f) {
+ distance = 0.001f;
+ }
float overlap = 0.0f;
float dirX = dx / distance;
float dirY = dy / distance;
@@ -378,7 +394,7 @@ float GetTargetScore(const Point &originPos, int originIndex, View *origin, View
}
}
-float GetDirectionScore(int originIndex, View *origin, View *destination, FocusDirection direction) {
+static float GetDirectionScore(int originIndex, const View *origin, View *destination, FocusDirection direction) {
Point originPos = origin->GetFocusPosition(direction);
return GetTargetScore(originPos, originIndex, origin, destination, direction);
}
@@ -498,8 +514,9 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
float sum = 0.0f;
float maxOther = 0.0f;
float totalWeight = 0.0f;
- float weightSum = 0.0f;
- float weightZeroSum = 0.0f;
+
+ float weightSum = 0.0f; // Total sum of weights
+ float weightZeroSum = 0.0f; // Sum of sizes of things with weight 0.0, a bit confusingly named
int numVisible = 0;
@@ -522,19 +539,19 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
MeasureSpec v = vert;
if (v.type == UNSPECIFIED && measuredHeight_ != 0.0f)
v = MeasureSpec(AT_MOST, measuredHeight_);
- view->Measure(dc, MeasureSpec(UNSPECIFIED, measuredWidth_), v - (float)margins.vert());
- if (horiz.type == AT_MOST && view->GetMeasuredWidth() + margins.horiz() > horiz.size - weightZeroSum) {
+ view->Measure(dc, MeasureSpec(UNSPECIFIED, measuredWidth_), v - (float)margins.vert() - (float)padding.vert());
+ if (horiz.type == AT_MOST && view->GetMeasuredWidth() + margins.horiz() + padding.horiz() > horiz.size - weightZeroSum) {
// Try again, this time with AT_MOST.
- view->Measure(dc, horiz, v - (float)margins.vert());
+ view->Measure(dc, horiz, v - (float)margins.vert() - (float)padding.vert());
}
} else if (orientation_ == ORIENT_VERTICAL) {
MeasureSpec h = horiz;
if (h.type == UNSPECIFIED && measuredWidth_ != 0.0f)
h = MeasureSpec(AT_MOST, measuredWidth_);
- view->Measure(dc, h - (float)margins.horiz(), MeasureSpec(UNSPECIFIED, measuredHeight_));
- if (vert.type == AT_MOST && view->GetMeasuredHeight() + margins.vert() > vert.size - weightZeroSum) {
+ view->Measure(dc, h - (float)margins.horiz() - (float)padding.horiz(), MeasureSpec(UNSPECIFIED, measuredHeight_));
+ if (vert.type == AT_MOST && view->GetMeasuredHeight() + margins.vert() + padding.horiz() > vert.size - weightZeroSum) {
// Try again, this time with AT_MOST.
- view->Measure(dc, h - (float)margins.horiz(), vert);
+ view->Measure(dc, h - (float)margins.horiz() - (float)padding.horiz(), vert);
}
}
@@ -558,12 +575,12 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
}
}
- weightZeroSum += spacing_ * (numVisible - 1);
+ weightZeroSum += spacing_ * (numVisible - 1); // +(orientation_ == ORIENT_HORIZONTAL) ? padding.horiz() : padding.vert();
// Alright, got the sum. Let's take the remaining space after the fixed-size views,
// and distribute among the weighted ones.
if (orientation_ == ORIENT_HORIZONTAL) {
- MeasureBySpec(layoutParams_->width, weightZeroSum, horiz, &measuredWidth_);
+ MeasureBySpec(layoutParams_->width, weightZeroSum + padding.horiz(), horiz, &measuredWidth_);
// If we've got stretch, allow growing to fill the parent.
float allowedWidth = measuredWidth_;
@@ -571,14 +588,17 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
allowedWidth = horiz.size;
}
- float usedWidth = 0.0f;
+ float usedWidth = 0.0f + padding.horiz();
// Redistribute the stretchy ones! and remeasure the children!
for (View *view : views_) {
if (view->GetVisibility() == V_GONE)
continue;
- const LinearLayoutParams *linLayoutParams = view->GetLayoutParams()->As();
+ // FILL_PARENT is not appropriate in this direction. It gets ignored though.
+ // We have a bit too many of these due to the hack in the ClickableItem constructor.
+ // _dbg_assert_(view->GetLayoutParams()->width != UI::FILL_PARENT);
+ const LinearLayoutParams *linLayoutParams = view->GetLayoutParams()->As();
if (linLayoutParams && linLayoutParams->weight > 0.0f) {
Margins margins = defaultMargins_;
if (linLayoutParams->HasMargins())
@@ -587,11 +607,15 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
if (v.type == UNSPECIFIED && measuredHeight_ != 0.0f)
v = MeasureSpec(AT_MOST, measuredHeight_);
float unit = (allowedWidth - weightZeroSum) / weightSum;
+ if (weightSum == 0.0f) {
+ // We must have gotten an inf.
+ unit = 1.0f;
+ }
MeasureSpec h(AT_MOST, unit * linLayoutParams->weight - margins.horiz());
if (horiz.type == EXACTLY) {
h.type = EXACTLY;
}
- view->Measure(dc, h, v - (float)margins.vert());
+ view->Measure(dc, h, v - (float)margins.vert() - (float)padding.vert());
usedWidth += view->GetMeasuredWidth();
maxOther = std::max(maxOther, view->GetMeasuredHeight() + margins.vert());
}
@@ -602,9 +626,9 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
}
// Measure here in case maxOther moved (can happen due to word wrap.)
- MeasureBySpec(layoutParams_->height, maxOther, vert, &measuredHeight_);
+ MeasureBySpec(layoutParams_->height, maxOther + padding.vert(), vert, &measuredHeight_);
} else {
- MeasureBySpec(layoutParams_->height, weightZeroSum, vert, &measuredHeight_);
+ MeasureBySpec(layoutParams_->height, weightZeroSum + padding.vert(), vert, &measuredHeight_);
// If we've got stretch, allow growing to fill the parent.
float allowedHeight = measuredHeight_;
@@ -612,14 +636,17 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
allowedHeight = vert.size;
}
- float usedHeight = 0.0f;
+ float usedHeight = 0.0f + padding.vert();
// Redistribute the stretchy ones! and remeasure the children!
for (View *view : views_) {
if (view->GetVisibility() == V_GONE)
continue;
- const LinearLayoutParams *linLayoutParams = view->GetLayoutParams()->As();
+ // FILL_PARENT is not appropriate in this direction. It gets ignored though.
+ // We have a bit too many of these due to the hack in the ClickableItem constructor.
+ // _dbg_assert_(view->GetLayoutParams()->height != UI::FILL_PARENT);
+ const LinearLayoutParams *linLayoutParams = view->GetLayoutParams()->As();
if (linLayoutParams && linLayoutParams->weight > 0.0f) {
Margins margins = defaultMargins_;
if (linLayoutParams->HasMargins())
@@ -628,11 +655,15 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
if (h.type == UNSPECIFIED && measuredWidth_ != 0.0f)
h = MeasureSpec(AT_MOST, measuredWidth_);
float unit = (allowedHeight - weightZeroSum) / weightSum;
+ if (weightSum == 0.0f) {
+ // We must have gotten an inf.
+ unit = 1.0f;
+ }
MeasureSpec v(AT_MOST, unit * linLayoutParams->weight - margins.vert());
if (vert.type == EXACTLY) {
v.type = EXACTLY;
}
- view->Measure(dc, h - (float)margins.horiz(), v);
+ view->Measure(dc, h - (float)margins.horiz() - (float)padding.horiz(), v);
usedHeight += view->GetMeasuredHeight();
maxOther = std::max(maxOther, view->GetMeasuredWidth() + margins.horiz());
}
@@ -643,7 +674,7 @@ void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec v
}
// Measure here in case maxOther moved (can happen due to word wrap.)
- MeasureBySpec(layoutParams_->width, maxOther, horiz, &measuredWidth_);
+ MeasureBySpec(layoutParams_->width, maxOther + padding.horiz(), horiz, &measuredWidth_);
}
}
@@ -655,13 +686,13 @@ void LinearLayout::Layout() {
float pos;
if (orientation_ == ORIENT_HORIZONTAL) {
- pos = bounds.x;
- itemBounds.y = bounds.y;
- itemBounds.h = measuredHeight_;
+ pos = bounds.x + padding.left;
+ itemBounds.y = bounds.y + padding.top;
+ itemBounds.h = measuredHeight_ - padding.vert();
} else {
- pos = bounds.y;
- itemBounds.x = bounds.x;
- itemBounds.w = measuredWidth_;
+ pos = bounds.y + padding.top;
+ itemBounds.x = bounds.x + padding.left;
+ itemBounds.w = measuredWidth_ - padding.horiz();
}
for (size_t i = 0; i < views_.size(); i++) {
@@ -728,8 +759,8 @@ void FrameLayout::Layout() {
bounds.w = w;
bounds.h = h;
- bounds.x = bounds_.x + (measuredWidth_ - w) / 2;
- bounds.y = bounds_.y + (measuredWidth_ - h) / 2;
+ bounds.x = bounds_.x + (measuredWidth_ - w) * 0.5f;
+ bounds.y = bounds_.y + (measuredWidth_ - h) * 0.5f;
views_[i]->SetBounds(bounds);
}
}
@@ -826,13 +857,21 @@ bool ScrollView::Key(const KeyInput &input) {
if (visibility_ != V_VISIBLE)
return ViewGroup::Key(input);
+ float scrollSpeed = 250;
+ switch (input.deviceId) {
+ case DEVICE_ID_XR_CONTROLLER_LEFT:
+ case DEVICE_ID_XR_CONTROLLER_RIGHT:
+ scrollSpeed = 50;
+ break;
+ }
+
if (input.flags & KEY_DOWN) {
switch (input.keyCode) {
case NKCODE_EXT_MOUSEWHEEL_UP:
- ScrollRelative(-250);
+ ScrollRelative(-scrollSpeed);
break;
case NKCODE_EXT_MOUSEWHEEL_DOWN:
- ScrollRelative(250);
+ ScrollRelative(scrollSpeed);
break;
}
}
@@ -842,7 +881,7 @@ bool ScrollView::Key(const KeyInput &input) {
const float friction = 0.92f;
const float stop_threshold = 0.1f;
-void ScrollView::Touch(const TouchInput &input) {
+bool ScrollView::Touch(const TouchInput &input) {
if ((input.flags & TOUCH_DOWN) && scrollTouchId_ == -1) {
scrollStart_ = scrollPos_;
inertia_ = 0.0f;
@@ -876,7 +915,9 @@ void ScrollView::Touch(const TouchInput &input) {
}
if (!(input.flags & TOUCH_DOWN) || bounds_.Contains(input.x, input.y)) {
- ViewGroup::Touch(input2);
+ return ViewGroup::Touch(input2);
+ } else {
+ return false;
}
}
@@ -887,6 +928,8 @@ void ScrollView::Draw(UIContext &dc) {
}
dc.PushScissor(bounds_);
+ dc.FillRect(bg_, bounds_);
+
// For debugging layout issues, this can be useful.
// dc.FillRect(Drawable(0x60FF00FF), bounds_);
views_[0]->Draw(dc);
@@ -895,7 +938,7 @@ void ScrollView::Draw(UIContext &dc) {
float childHeight = views_[0]->GetBounds().h;
float scrollMax = std::max(0.0f, childHeight - bounds_.h);
- float ratio = bounds_.h / views_[0]->GetBounds().h;
+ float ratio = bounds_.h / std::max(0.01f, views_[0]->GetBounds().h);
float bobWidth = 5;
if (ratio < 1.0f && scrollMax > 0.0f) {
@@ -920,7 +963,7 @@ bool ScrollView::SubviewFocused(View *view) {
float visibleSize = orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w;
float visibleEnd = scrollPos_ + visibleSize;
- float viewStart, viewEnd;
+ float viewStart = 0.0f, viewEnd = 0.0f;
switch (orientation_) {
case ORIENT_HORIZONTAL:
viewStart = layoutScrollPos_ + vBounds.x - bounds_.x;
@@ -1037,16 +1080,17 @@ void ScrollView::ScrollRelative(float distance) {
}
float ScrollView::ClampedScrollPos(float pos) {
- if (!views_.size()) {
+ if (!views_.size() || bounds_.h == 0.0f) {
return 0.0f;
}
float childSize = orientation_ == ORIENT_VERTICAL ? views_[0]->GetBounds().h : views_[0]->GetBounds().w;
- float scrollMax = std::max(0.0f, childSize - (orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w));
+ float containerSize = (orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w);
+ float scrollMax = std::max(0.0f, childSize - containerSize);
Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL;
- if (scrollTouchId_ >= 0 && gesture_.IsGestureActive(gesture, scrollTouchId_) && bounds_.h > 0) {
+ if (scrollTouchId_ >= 0 && gesture_.IsGestureActive(gesture, scrollTouchId_) && bounds_.h > 0.0f) {
float maxPull = bounds_.h * 0.1f;
if (pos < 0.0f) {
float dist = std::min(-pos * (1.0f / bounds_.h), 1.0f);
@@ -1065,7 +1109,9 @@ float ScrollView::ClampedScrollPos(float pos) {
if (pos > scrollMax && pos > scrollMax + pull_) {
pos = scrollMax + pull_;
}
-
+ if (childSize < containerSize && alignOpposite_) {
+ pos = -(containerSize - childSize);
+ }
return pos;
}
@@ -1240,6 +1286,9 @@ void AnchorLayout::Layout() {
if (center) {
vBounds.x += vBounds.w * 0.5f;
}
+ } else {
+ // Both left and right are NONE. Center.
+ vBounds.x = (bounds_.w - vBounds.w) / 2.0f + bounds_.x;
}
if (top > NONE) {
@@ -1250,6 +1299,9 @@ void AnchorLayout::Layout() {
vBounds.y = bounds_.y2() - bottom - vBounds.h;
if (center)
vBounds.y += vBounds.h * 0.5f;
+ } else {
+ // Both top and bottom are NONE. Center.
+ vBounds.y = (bounds_.h - vBounds.h) / 2.0f + bounds_.y;
}
views_[i]->SetBounds(vBounds);
@@ -1258,7 +1310,7 @@ void AnchorLayout::Layout() {
}
GridLayout::GridLayout(GridLayoutSettings settings, LayoutParams *layoutParams)
- : ViewGroup(layoutParams), settings_(settings), numColumns_(1) {
+ : ViewGroup(layoutParams), settings_(settings) {
if (settings.orientation != ORIENT_HORIZONTAL)
ERROR_LOG(SYSTEM, "GridLayout: Vertical layouts not yet supported");
}
@@ -1439,7 +1491,7 @@ void TabHolder::PersistData(PersistStatus status, std::string anonId, PersistMap
}
ChoiceStrip::ChoiceStrip(Orientation orientation, LayoutParams *layoutParams)
- : LinearLayout(orientation, layoutParams), selected_(0), topTabs_(false) {
+ : LinearLayout(orientation, layoutParams) {
SetSpacing(0.0f);
}
@@ -1551,6 +1603,7 @@ StickyChoice *ChoiceStrip::Choice(int index) {
return static_cast(views_[index]);
return nullptr;
}
+
ListView::ListView(ListAdaptor *a, std::set hidden, LayoutParams *layoutParams)
: ScrollView(ORIENT_VERTICAL, layoutParams), adaptor_(a), maxHeight_(0), hidden_(hidden) {
diff --git a/Common/UI/ViewGroup.h b/Common/UI/ViewGroup.h
index 5b552c5f0f..32ddb8eb56 100644
--- a/Common/UI/ViewGroup.h
+++ b/Common/UI/ViewGroup.h
@@ -24,27 +24,23 @@ struct NeighborResult {
class ViewGroup : public View {
public:
ViewGroup(LayoutParams *layoutParams = 0) : View(layoutParams) {}
- virtual ~ViewGroup();
+ ~ViewGroup();
// Pass through external events to children.
- virtual bool Key(const KeyInput &input) override;
- virtual void Touch(const TouchInput &input) override;
- virtual void Axis(const AxisInput &input) override;
+ bool Key(const KeyInput &input) override;
+ bool Touch(const TouchInput &input) override;
+ void Axis(const AxisInput &input) override;
// By default, a container will layout to its own bounds.
- virtual void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override = 0;
- virtual void Layout() override = 0;
- virtual void Update() override;
- virtual void Query(float x, float y, std::vector &list) override;
+ void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override = 0;
+ void Layout() override = 0;
+ void Update() override;
+ void Query(float x, float y, std::vector &list) override;
- virtual void DeviceLost() override;
- virtual void DeviceRestored(Draw::DrawContext *draw) override;
+ void DeviceLost() override;
+ void DeviceRestored(Draw::DrawContext *draw) override;
- virtual void Draw(UIContext &dc) override;
-
- // These should be unused.
- virtual float GetContentWidth() const { return 0.0f; }
- virtual float GetContentHeight() const { return 0.0f; }
+ void Draw(UIContext &dc) override;
// Takes ownership! DO NOT add a view to multiple parents!
template
@@ -54,8 +50,8 @@ public:
return view;
}
- virtual bool SetFocus() override;
- virtual bool SubviewFocused(View *view) override;
+ bool SetFocus() override;
+ bool SubviewFocused(View *view) override;
virtual void RemoveSubview(View *view);
void SetDefaultFocusView(View *view) { defaultFocusView_ = view; }
@@ -77,6 +73,8 @@ public:
int GetNumSubviews() const { return (int)views_.size(); }
void SetHasDropShadow(bool has) { hasDropShadow_ = has; }
void SetDropShadowExpand(float s) { dropShadowExpand_ = s; }
+ void SetExclusiveTouch(bool exclusive) { exclusiveTouch_ = exclusive; }
+ void SetClickableBackground(bool clickableBackground) { clickableBackground_ = clickableBackground; }
void Lock() { modifyLock_.lock(); }
void Unlock() { modifyLock_.unlock(); }
@@ -95,13 +93,16 @@ protected:
Drawable bg_;
float dropShadowExpand_ = 0.0f;
bool hasDropShadow_ = false;
+ bool clickableBackground_ = false;
bool clip_ = false;
+ bool exclusiveTouch_ = false;
};
// A frame layout contains a single child view (normally).
// It simply centers the child view.
class FrameLayout : public ViewGroup {
public:
+ FrameLayout(LayoutParams *layoutParams = nullptr) : ViewGroup(layoutParams) {}
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
void Layout() override;
};
@@ -111,9 +112,7 @@ const float NONE = -FLT_MAX;
class AnchorLayoutParams : public LayoutParams {
public:
AnchorLayoutParams(Size w, Size h, float l, float t, float r, float b, bool c = false)
- : LayoutParams(w, h, LP_ANCHOR), left(l), top(t), right(r), bottom(b), center(c) {
-
- }
+ : LayoutParams(w, h, LP_ANCHOR), left(l), top(t), right(r), bottom(b), center(c) {}
// There's a small hack here to make this behave more intuitively - AnchorLayout ordinarily ignores FILL_PARENT.
AnchorLayoutParams(Size w, Size h, bool c = false)
: LayoutParams(w, h, LP_ANCHOR), left(0), top(0), right(w == FILL_PARENT ? 0 : NONE), bottom(h == FILL_PARENT ? 0 : NONE), center(c) {
@@ -123,6 +122,7 @@ public:
// These are not bounds, but distances from the container edges.
// Set to NONE to not attach this edge to the container.
+ // If two opposite edges are NONE, centering will happen.
float left, top, right, bottom;
bool center; // If set, only two "sides" can be set, and they refer to the center, not the edge, of the view being layouted.
@@ -133,7 +133,7 @@ public:
class AnchorLayout : public ViewGroup {
public:
- AnchorLayout(LayoutParams *layoutParams = 0) : ViewGroup(layoutParams), overflow_(true) {}
+ AnchorLayout(LayoutParams *layoutParams = 0) : ViewGroup(layoutParams) {}
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
void Layout() override;
void Overflow(bool allow) {
@@ -143,7 +143,7 @@ public:
private:
void MeasureViews(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert);
- bool overflow_;
+ bool overflow_ = true;
};
class LinearLayoutParams : public LayoutParams {
@@ -155,7 +155,7 @@ public:
LinearLayoutParams(float wgt, const Margins &mgn)
: LayoutParams(LP_LINEAR), weight(wgt), gravity(G_TOPLEFT), margins(mgn), hasMargins_(true) {}
LinearLayoutParams(Size w, Size h, float wgt = 0.0f, Gravity grav = G_TOPLEFT)
- : LayoutParams(w, h, LP_LINEAR), weight(wgt), gravity(grav), hasMargins_(false) {}
+ : LayoutParams(w, h, LP_LINEAR), weight(wgt), gravity(grav), margins(0), hasMargins_(false) {}
LinearLayoutParams(Size w, Size h, float wgt, Gravity grav, const Margins &mgn)
: LayoutParams(w, h, LP_LINEAR), weight(wgt), gravity(grav), margins(mgn), hasMargins_(true) {}
LinearLayoutParams(Size w, Size h, const Margins &mgn)
@@ -182,7 +182,7 @@ private:
class LinearLayout : public ViewGroup {
public:
LinearLayout(Orientation orientation, LayoutParams *layoutParams = 0)
- : ViewGroup(layoutParams), orientation_(orientation), defaultMargins_(0), spacing_(10) {}
+ : ViewGroup(layoutParams), orientation_(orientation), defaultMargins_(0) {}
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
void Layout() override;
@@ -190,12 +190,13 @@ public:
spacing_ = spacing;
}
std::string DescribeLog() const override { return (orientation_ == ORIENT_HORIZONTAL ? "LinearLayoutHoriz: " : "LinearLayoutVert: ") + View::DescribeLog(); }
+ Margins padding;
protected:
Orientation orientation_;
private:
Margins defaultMargins_;
- float spacing_;
+ float spacing_ = 10.0f;
};
class LinearLayoutList : public LinearLayout {
@@ -247,7 +248,7 @@ public:
private:
GridLayoutSettings settings_;
- int numColumns_;
+ int numColumns_ = 1;
};
class GridLayoutList : public GridLayout {
@@ -270,7 +271,7 @@ public:
void Layout() override;
bool Key(const KeyInput &input) override;
- void Touch(const TouchInput &input) override;
+ bool Touch(const TouchInput &input) override;
void Draw(UIContext &dc) override;
std::string DescribeLog() const override { return "ScrollView: " + View::DescribeLog(); }
@@ -293,6 +294,11 @@ public:
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
void SetVisibility(Visibility visibility) override;
+ // If the view is smaller than the scroll view, sets whether to align to the bottom/right instead of the left.
+ void SetAlignOpposite(bool alignOpposite) {
+ alignOpposite_ = alignOpposite;
+ }
+
NeighborResult FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) override;
private:
@@ -310,16 +316,12 @@ private:
float pull_ = 0.0f;
float lastViewSize_ = 0.0f;
float *rememberPos_ = nullptr;
+ bool alignOpposite_ = false;
static float lastScrollPosX;
static float lastScrollPosY;
};
-class ViewPager : public ScrollView {
-public:
-};
-
-
class ChoiceStrip : public LinearLayout {
public:
ChoiceStrip(Orientation orientation, LayoutParams *layoutParams = 0);
@@ -346,8 +348,8 @@ private:
StickyChoice *Choice(int index);
EventReturn OnChoiceClick(EventParams &e);
- int selected_;
- bool topTabs_; // Can be controlled with L/R.
+ int selected_ = 0; // Can be controlled with L/R.
+ bool topTabs_ = false;
};
@@ -400,9 +402,9 @@ public:
class ChoiceListAdaptor : public ListAdaptor {
public:
ChoiceListAdaptor(const char *items[], int numItems) : items_(items), numItems_(numItems) {}
- virtual View *CreateItemView(int index);
- virtual int GetNumItems() { return numItems_; }
- virtual bool AddEventCallback(View *view, std::function callback);
+ View *CreateItemView(int index) override;
+ int GetNumItems() override { return numItems_; }
+ bool AddEventCallback(View *view, std::function callback) override;
private:
const char **items_;
@@ -415,12 +417,12 @@ class StringVectorListAdaptor : public ListAdaptor {
public:
StringVectorListAdaptor() : selected_(-1) {}
StringVectorListAdaptor(const std::vector &items, int selected = -1) : items_(items), selected_(selected) {}
- virtual View *CreateItemView(int index) override;
- virtual int GetNumItems() override { return (int)items_.size(); }
- virtual bool AddEventCallback(View *view, std::function callback) override;
+ View *CreateItemView(int index) override;
+ int GetNumItems() override { return (int)items_.size(); }
+ bool AddEventCallback(View *view, std::function callback) override;
void SetSelected(int sel) override { selected_ = sel; }
- virtual std::string GetTitle(int index) const override { return items_[index]; }
- virtual int GetSelected() override { return selected_; }
+ std::string GetTitle(int index) const override { return items_[index]; }
+ int GetSelected() override { return selected_; }
private:
std::vector items_;
@@ -434,7 +436,7 @@ public:
ListView(ListAdaptor *a, std::set hidden = std::set(), LayoutParams *layoutParams = 0);
int GetSelected() { return adaptor_->GetSelected(); }
- virtual void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
+ void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
virtual void SetMaxHeight(float mh) { maxHeight_ = mh; }
Event OnChoice;
std::string DescribeLog() const override { return "ListView: " + View::DescribeLog(); }
diff --git a/Common/VR/OpenXRLoader.cpp b/Common/VR/OpenXRLoader.cpp
new file mode 100644
index 0000000000..4d5f8fb246
--- /dev/null
+++ b/Common/VR/OpenXRLoader.cpp
@@ -0,0 +1,160 @@
+#include "Common/VR/OpenXRLoader.h"
+#ifdef _WIN32
+#include "Common/CommonWindows.h"
+#endif
+
+#ifdef XR_NO_PROTOTYPES
+
+PFN_xrGetInstanceProcAddr xrGetInstanceProcAddr;
+PFN_xrEnumerateApiLayerProperties xrEnumerateApiLayerProperties;
+PFN_xrEnumerateInstanceExtensionProperties xrEnumerateInstanceExtensionProperties;
+PFN_xrCreateInstance xrCreateInstance;
+PFN_xrDestroyInstance xrDestroyInstance;
+PFN_xrGetInstanceProperties xrGetInstanceProperties;
+PFN_xrPollEvent xrPollEvent;
+PFN_xrResultToString xrResultToString;
+PFN_xrStructureTypeToString xrStructureTypeToString;
+PFN_xrGetSystem xrGetSystem;
+PFN_xrGetSystemProperties xrGetSystemProperties;
+PFN_xrEnumerateEnvironmentBlendModes xrEnumerateEnvironmentBlendModes;
+PFN_xrCreateSession xrCreateSession;
+PFN_xrDestroySession xrDestroySession;
+PFN_xrEnumerateReferenceSpaces xrEnumerateReferenceSpaces;
+PFN_xrCreateReferenceSpace xrCreateReferenceSpace;
+PFN_xrGetReferenceSpaceBoundsRect xrGetReferenceSpaceBoundsRect;
+PFN_xrCreateActionSpace xrCreateActionSpace;
+PFN_xrLocateSpace xrLocateSpace;
+PFN_xrDestroySpace xrDestroySpace;
+PFN_xrEnumerateViewConfigurations xrEnumerateViewConfigurations;
+PFN_xrGetViewConfigurationProperties xrGetViewConfigurationProperties;
+PFN_xrEnumerateViewConfigurationViews xrEnumerateViewConfigurationViews;
+PFN_xrEnumerateSwapchainFormats xrEnumerateSwapchainFormats;
+PFN_xrCreateSwapchain xrCreateSwapchain;
+PFN_xrDestroySwapchain xrDestroySwapchain;
+PFN_xrEnumerateSwapchainImages xrEnumerateSwapchainImages;
+PFN_xrAcquireSwapchainImage xrAcquireSwapchainImage;
+PFN_xrWaitSwapchainImage xrWaitSwapchainImage;
+PFN_xrReleaseSwapchainImage xrReleaseSwapchainImage;
+PFN_xrBeginSession xrBeginSession;
+PFN_xrEndSession xrEndSession;
+PFN_xrRequestExitSession xrRequestExitSession;
+PFN_xrWaitFrame xrWaitFrame;
+PFN_xrBeginFrame xrBeginFrame;
+PFN_xrEndFrame xrEndFrame;
+PFN_xrLocateViews xrLocateViews;
+PFN_xrStringToPath xrStringToPath;
+PFN_xrPathToString xrPathToString;
+PFN_xrCreateActionSet xrCreateActionSet;
+PFN_xrDestroyActionSet xrDestroyActionSet;
+PFN_xrCreateAction xrCreateAction;
+PFN_xrDestroyAction xrDestroyAction;
+PFN_xrSuggestInteractionProfileBindings xrSuggestInteractionProfileBindings;
+PFN_xrAttachSessionActionSets xrAttachSessionActionSets;
+PFN_xrGetCurrentInteractionProfile xrGetCurrentInteractionProfile;
+PFN_xrGetActionStateBoolean xrGetActionStateBoolean;
+PFN_xrGetActionStateFloat xrGetActionStateFloat;
+PFN_xrGetActionStateVector2f xrGetActionStateVector2f;
+PFN_xrGetActionStatePose xrGetActionStatePose;
+PFN_xrSyncActions xrSyncActions;
+PFN_xrEnumerateBoundSourcesForAction xrEnumerateBoundSourcesForAction;
+PFN_xrGetInputSourceLocalizedName xrGetInputSourceLocalizedName;
+PFN_xrApplyHapticFeedback xrApplyHapticFeedback;
+PFN_xrStopHapticFeedback xrStopHapticFeedback;
+
+#ifdef XR_USE_PLATFORM_WIN32
+#define dlsym(x, y) GetProcAddress(x, y)
+static HMODULE g_xrLibrary;
+#else
+#define dlsym(x, y) nullptr
+void *g_xrLibrary;
+#endif
+
+#define LOAD_INSTANCE_FUNC(name) (PFN_ ## name)xrGetInstanceProcAddr(instance, #name, (PFN_xrVoidFunction *)(&name))
+
+
+bool XRLoad() {
+ if (g_xrLibrary) {
+ // Already loaded. That's OK.
+ return true;
+ }
+
+#ifdef XR_USE_PLATFORM_WIN32
+ g_xrLibrary = LoadLibrary(L"openxr_loader.dll");
+ if (!g_xrLibrary) {
+ return false;
+ }
+#else
+ return false;
+#endif
+
+ // Load the three basic functions.
+ xrGetInstanceProcAddr = (PFN_xrGetInstanceProcAddr)dlsym(g_xrLibrary, "xrGetInstanceProcAddr");
+ xrEnumerateApiLayerProperties = (PFN_xrEnumerateApiLayerProperties)dlsym(g_xrLibrary, "xrEnumerateApiLayerProperties");
+ xrEnumerateInstanceExtensionProperties = (PFN_xrEnumerateInstanceExtensionProperties)dlsym(g_xrLibrary, "xrEnumerateInstanceExtensionProperties");
+
+ // Load the rest.
+ return true;
+}
+
+void XRLoadInstanceFunctions(XrInstance instance) {
+ LOAD_INSTANCE_FUNC(xrCreateInstance);
+ LOAD_INSTANCE_FUNC(xrGetInstanceProcAddr);
+ LOAD_INSTANCE_FUNC(xrEnumerateApiLayerProperties);
+ LOAD_INSTANCE_FUNC(xrEnumerateInstanceExtensionProperties);
+ LOAD_INSTANCE_FUNC(xrCreateInstance);
+ LOAD_INSTANCE_FUNC(xrDestroyInstance);
+ LOAD_INSTANCE_FUNC(xrGetInstanceProperties);
+ LOAD_INSTANCE_FUNC(xrPollEvent);
+ LOAD_INSTANCE_FUNC(xrResultToString);
+ LOAD_INSTANCE_FUNC(xrStructureTypeToString);
+ LOAD_INSTANCE_FUNC(xrGetSystem);
+ LOAD_INSTANCE_FUNC(xrGetSystemProperties);
+ LOAD_INSTANCE_FUNC(xrEnumerateEnvironmentBlendModes);
+ LOAD_INSTANCE_FUNC(xrCreateSession);
+ LOAD_INSTANCE_FUNC(xrDestroySession);
+ LOAD_INSTANCE_FUNC(xrEnumerateReferenceSpaces);
+ LOAD_INSTANCE_FUNC(xrCreateReferenceSpace);
+ LOAD_INSTANCE_FUNC(xrGetReferenceSpaceBoundsRect);
+ LOAD_INSTANCE_FUNC(xrCreateActionSpace);
+ LOAD_INSTANCE_FUNC(xrLocateSpace);
+ LOAD_INSTANCE_FUNC(xrDestroySpace);
+ LOAD_INSTANCE_FUNC(xrEnumerateViewConfigurations);
+ LOAD_INSTANCE_FUNC(xrGetViewConfigurationProperties);
+ LOAD_INSTANCE_FUNC(xrEnumerateViewConfigurationViews);
+ LOAD_INSTANCE_FUNC(xrEnumerateSwapchainFormats);
+ LOAD_INSTANCE_FUNC(xrCreateSwapchain);
+ LOAD_INSTANCE_FUNC(xrDestroySwapchain);
+ LOAD_INSTANCE_FUNC(xrEnumerateSwapchainImages);
+ LOAD_INSTANCE_FUNC(xrAcquireSwapchainImage);
+ LOAD_INSTANCE_FUNC(xrWaitSwapchainImage);
+ LOAD_INSTANCE_FUNC(xrReleaseSwapchainImage);
+ LOAD_INSTANCE_FUNC(xrBeginSession);
+ LOAD_INSTANCE_FUNC(xrEndSession);
+ LOAD_INSTANCE_FUNC(xrRequestExitSession);
+ LOAD_INSTANCE_FUNC(xrWaitFrame);
+ LOAD_INSTANCE_FUNC(xrBeginFrame);
+ LOAD_INSTANCE_FUNC(xrEndFrame);
+ LOAD_INSTANCE_FUNC(xrLocateViews);
+ LOAD_INSTANCE_FUNC(xrStringToPath);
+ LOAD_INSTANCE_FUNC(xrPathToString);
+ LOAD_INSTANCE_FUNC(xrCreateActionSet);
+ LOAD_INSTANCE_FUNC(xrDestroyActionSet);
+ LOAD_INSTANCE_FUNC(xrCreateAction);
+ LOAD_INSTANCE_FUNC(xrDestroyAction);
+ LOAD_INSTANCE_FUNC(xrSuggestInteractionProfileBindings);
+ LOAD_INSTANCE_FUNC(xrAttachSessionActionSets);
+ LOAD_INSTANCE_FUNC(xrGetCurrentInteractionProfile);
+ LOAD_INSTANCE_FUNC(xrGetActionStateBoolean);
+ LOAD_INSTANCE_FUNC(xrGetActionStateFloat);
+ LOAD_INSTANCE_FUNC(xrGetActionStateVector2f);
+ LOAD_INSTANCE_FUNC(xrGetActionStatePose);
+ LOAD_INSTANCE_FUNC(xrSyncActions);
+ LOAD_INSTANCE_FUNC(xrEnumerateBoundSourcesForAction);
+ LOAD_INSTANCE_FUNC(xrGetInputSourceLocalizedName);
+ LOAD_INSTANCE_FUNC(xrApplyHapticFeedback);
+ LOAD_INSTANCE_FUNC(xrStopHapticFeedback);
+
+ // TODO: Load any extensions we need, too.
+}
+
+#endif
diff --git a/Common/VR/OpenXRLoader.h b/Common/VR/OpenXRLoader.h
new file mode 100644
index 0000000000..ce6613ec5e
--- /dev/null
+++ b/Common/VR/OpenXRLoader.h
@@ -0,0 +1,122 @@
+#pragma once
+
+//Vulkan
+#ifdef VK_USE_NATIVE_LIB
+#include
+#else
+#include "Common/GPU/Vulkan/VulkanLoader.h"
+using namespace PPSSPP_VK;
+#endif
+
+#ifdef ANDROID
+#include
+#define XR_USE_PLATFORM_ANDROID 1
+#define XR_USE_GRAPHICS_API_OPENGL_ES 1
+#define XR_USE_GRAPHICS_API_VULKAN 1
+#include
+#include
+
+#if OPENXR
+// One of the Android-based headsets. We're linking with a static OpenXR loader.
+#else
+// Normal Android app, we don't have a loader so use our own dynamic loader, which won't load anything.
+#define XR_NO_PROTOTYPES 1
+#endif
+
+#elif defined(_WIN32)
+#include "Common/CommonWindows.h"
+#if defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_PARTITION)
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP
+#if defined(_M_IX86) || defined(_M_X64)
+#include "Common/GPU/OpenGL/GLCommon.h"
+#define XR_USE_GRAPHICS_API_OPENGL 1
+#endif
+#define XR_USE_PLATFORM_WIN32 1
+#endif
+#endif
+#define XR_USE_GRAPHICS_API_VULKAN 1
+#include "Common/VR/OpenXRLoader.h"
+#include
+#define XR_NO_PROTOTYPES 1
+#else
+#define XR_USE_GRAPHICS_API_VULKAN 1
+#define XR_NO_PROTOTYPES 1
+#endif
+
+#include
+#include
+
+#ifdef XR_NO_PROTOTYPES
+
+extern PFN_xrGetInstanceProcAddr xrGetInstanceProcAddr;
+extern PFN_xrEnumerateApiLayerProperties xrEnumerateApiLayerProperties;
+extern PFN_xrEnumerateInstanceExtensionProperties xrEnumerateInstanceExtensionProperties;
+extern PFN_xrCreateInstance xrCreateInstance;
+extern PFN_xrDestroyInstance xrDestroyInstance;
+extern PFN_xrGetInstanceProperties xrGetInstanceProperties;
+extern PFN_xrPollEvent xrPollEvent;
+extern PFN_xrResultToString xrResultToString;
+extern PFN_xrStructureTypeToString xrStructureTypeToString;
+extern PFN_xrGetSystem xrGetSystem;
+extern PFN_xrGetSystemProperties xrGetSystemProperties;
+extern PFN_xrEnumerateEnvironmentBlendModes xrEnumerateEnvironmentBlendModes;
+extern PFN_xrCreateSession xrCreateSession;
+extern PFN_xrDestroySession xrDestroySession;
+extern PFN_xrEnumerateReferenceSpaces xrEnumerateReferenceSpaces;
+extern PFN_xrCreateReferenceSpace xrCreateReferenceSpace;
+extern PFN_xrGetReferenceSpaceBoundsRect xrGetReferenceSpaceBoundsRect;
+extern PFN_xrCreateActionSpace xrCreateActionSpace;
+extern PFN_xrLocateSpace xrLocateSpace;
+extern PFN_xrDestroySpace xrDestroySpace;
+extern PFN_xrEnumerateViewConfigurations xrEnumerateViewConfigurations;
+extern PFN_xrGetViewConfigurationProperties xrGetViewConfigurationProperties;
+extern PFN_xrEnumerateViewConfigurationViews xrEnumerateViewConfigurationViews;
+extern PFN_xrEnumerateSwapchainFormats xrEnumerateSwapchainFormats;
+extern PFN_xrCreateSwapchain xrCreateSwapchain;
+extern PFN_xrDestroySwapchain xrDestroySwapchain;
+extern PFN_xrEnumerateSwapchainImages xrEnumerateSwapchainImages;
+extern PFN_xrAcquireSwapchainImage xrAcquireSwapchainImage;
+extern PFN_xrWaitSwapchainImage xrWaitSwapchainImage;
+extern PFN_xrReleaseSwapchainImage xrReleaseSwapchainImage;
+extern PFN_xrBeginSession xrBeginSession;
+extern PFN_xrEndSession xrEndSession;
+extern PFN_xrRequestExitSession xrRequestExitSession;
+extern PFN_xrWaitFrame xrWaitFrame;
+extern PFN_xrBeginFrame xrBeginFrame;
+extern PFN_xrEndFrame xrEndFrame;
+extern PFN_xrLocateViews xrLocateViews;
+extern PFN_xrStringToPath xrStringToPath;
+extern PFN_xrPathToString xrPathToString;
+extern PFN_xrCreateActionSet xrCreateActionSet;
+extern PFN_xrDestroyActionSet xrDestroyActionSet;
+extern PFN_xrCreateAction xrCreateAction;
+extern PFN_xrDestroyAction xrDestroyAction;
+extern PFN_xrSuggestInteractionProfileBindings xrSuggestInteractionProfileBindings;
+extern PFN_xrAttachSessionActionSets xrAttachSessionActionSets;
+extern PFN_xrGetCurrentInteractionProfile xrGetCurrentInteractionProfile;
+extern PFN_xrGetActionStateBoolean xrGetActionStateBoolean;
+extern PFN_xrGetActionStateFloat xrGetActionStateFloat;
+extern PFN_xrGetActionStateVector2f xrGetActionStateVector2f;
+extern PFN_xrGetActionStatePose xrGetActionStatePose;
+extern PFN_xrSyncActions xrSyncActions;
+extern PFN_xrEnumerateBoundSourcesForAction xrEnumerateBoundSourcesForAction;
+extern PFN_xrGetInputSourceLocalizedName xrGetInputSourceLocalizedName;
+extern PFN_xrApplyHapticFeedback xrApplyHapticFeedback;
+extern PFN_xrStopHapticFeedback xrStopHapticFeedback;
+
+// Dynamic loader for OpenXR on desktop platforms.
+// On Quest/Pico, we use statically linked loaders provided by the platform owners.
+// On Windows (and Linux etc), we're not so lucky - we could link to static libraries, but we really
+// don't want to as we still want to function when one is not present.
+//
+// Similar to our dynamic Vulkan loader.
+
+bool XRLoad();
+void XRLoadInstanceFunctions(XrInstance instance);
+
+#else
+
+inline bool XRLoad() { return true; }
+inline void XRLoadInstanceFunctions(XrInstance instance) {}
+
+#endif
diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp
index 9d1db94576..46dca63f4b 100644
--- a/Common/VR/PPSSPPVR.cpp
+++ b/Common/VR/PPSSPPVR.cpp
@@ -1,18 +1,54 @@
-#include "Common/GPU/OpenGL/GLRenderManager.h"
-
#include "Common/VR/PPSSPPVR.h"
+
#include "Common/VR/VRBase.h"
+#if XR_USE_GRAPHICS_API_OPENGL || XR_USE_GRAPHICS_API_OPENGL_ES
+#include "Common/GPU/OpenGL/GLRenderManager.h"
+#endif
+
#include "Common/VR/VRInput.h"
#include "Common/VR/VRMath.h"
#include "Common/VR/VRRenderer.h"
-#include "Common/VR/VRTweaks.h"
+
+#include "Common/GPU/Vulkan/VulkanContext.h"
+
+#include "Common/Math/lin/matrix4x4.h"
#include "Core/HLE/sceDisplay.h"
#include "Core/Config.h"
#include "Core/KeyMap.h"
#include "Core/System.h"
+enum VRMatrix {
+ VR_PROJECTION_MATRIX,
+ VR_VIEW_MATRIX_LEFT_EYE,
+ VR_VIEW_MATRIX_RIGHT_EYE,
+ VR_MATRIX_COUNT
+};
+
+enum VRMirroring {
+ VR_MIRRORING_UPDATED,
+ VR_MIRRORING_AXIS_X,
+ VR_MIRRORING_AXIS_Y,
+ VR_MIRRORING_AXIS_Z,
+ VR_MIRRORING_PITCH,
+ VR_MIRRORING_YAW,
+ VR_MIRRORING_ROLL,
+ VR_MIRRORING_COUNT
+};
+
+static VRAppMode appMode = VR_MENU_MODE;
+static std::map > pspAxis;
+static std::map pspKeys;
+
+static int vr3DGeometryCount = 0;
static long vrCompat[VR_COMPAT_MAX];
+static bool vrFlatGame = false;
+static float vrMatrix[VR_MATRIX_COUNT][16];
+static bool vrMirroring[VR_MIRRORING_COUNT];
+
+static bool(*NativeAxis)(const AxisInput &axis);
+static bool(*NativeKey)(const KeyInput &key);
+static bool(*NativeTouch)(const TouchInput &touch);
/*
================================================================================
@@ -36,16 +72,6 @@ struct ButtonMapping {
}
};
-struct MouseActivator {
- bool activate;
- ovrButton ovr;
-
- MouseActivator(bool activate, ovrButton ovr) {
- this->activate = activate;
- this->ovr = ovr;
- }
-};
-
static std::vector leftControllerMapping = {
ButtonMapping(NKCODE_BUTTON_X, ovrButton_X),
ButtonMapping(NKCODE_BUTTON_Y, ovrButton_Y),
@@ -76,16 +102,15 @@ static std::vector controllerMapping[2] = {
leftControllerMapping,
rightControllerMapping
};
-static int mouseController = -1;
-static bool mousePressed[] = {false, false};
+static bool controllerMotion[2][5] = {};
+static int mouseController = 1;
+static bool mousePressed = false;
-static std::vector mouseActivators = {
- MouseActivator(true, ovrButton_Trigger),
- MouseActivator(false, ovrButton_Up),
- MouseActivator(false, ovrButton_Down),
- MouseActivator(false, ovrButton_Left),
- MouseActivator(false, ovrButton_Right),
-};
+inline float clampFloat(float x, float minValue, float maxValue) {
+ if (x < minValue) return minValue;
+ if (x > maxValue) return maxValue;
+ return x;
+}
/*
================================================================================
@@ -95,25 +120,65 @@ VR app flow integration
================================================================================
*/
-bool IsVRBuild() {
+bool IsVREnabled() {
+ // For now, let the OPENXR build flag control enablement.
+ // This will change.
+#ifdef OPENXR
return true;
+#else
+ return false;
+#endif
}
-void InitVROnAndroid(void* vm, void* activity, int version, const char* name) {
+#if PPSSPP_PLATFORM(ANDROID)
+void InitVROnAndroid(void* vm, void* activity, const char* system, int version, const char* name) {
+
+ //Get device vendor (uppercase)
+ char vendor[64];
+ sscanf(system, "%[^:]", vendor);
+ for (unsigned int i = 0; i < strlen(vendor); i++) {
+ if ((vendor[i] >= 'a') && (vendor[i] <= 'z')) {
+ vendor[i] = vendor[i] - 'a' + 'A';
+ }
+ }
+
+ //Set platform flags
+ if (strcmp(vendor, "PICO") == 0) {
+ VR_SetPlatformFLag(VR_PLATFORM_CONTROLLER_PICO, true);
+ VR_SetPlatformFLag(VR_PLATFORM_INSTANCE_EXT, true);
+ } else if ((strcmp(vendor, "META") == 0) || (strcmp(vendor, "OCULUS") == 0)) {
+ VR_SetPlatformFLag(VR_PLATFORM_CONTROLLER_QUEST, true);
+ VR_SetPlatformFLag(VR_PLATFORM_PERFORMANCE_EXT, true);
+ }
+ VR_SetPlatformFLag(VR_PLATFORM_RENDERER_VULKAN, (GPUBackend)g_Config.iGPUBackend == GPUBackend::VULKAN);
+
+ //Init VR
ovrJava java;
java.Vm = (JavaVM*)vm;
java.ActivityObject = (jobject)activity;
- java.AppVersion = version;
- strcpy(java.AppName, name);
- VR_Init(java);
-
- __DisplaySetFramerate(72);
+ VR_Init(&java, name, version);
}
+#endif
-void EnterVR(bool firstStart) {
+void EnterVR(bool firstStart, void* vulkanContext) {
if (firstStart) {
- VR_EnterVR(VR_GetEngine());
- IN_VRInit(VR_GetEngine());
+ engine_t* engine = VR_GetEngine();
+ bool useVulkan = (GPUBackend)g_Config.iGPUBackend == GPUBackend::VULKAN;
+ if (useVulkan) {
+ auto* context = (VulkanContext*)vulkanContext;
+ engine->graphicsBindingVulkan = {};
+ engine->graphicsBindingVulkan.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR;
+ engine->graphicsBindingVulkan.next = NULL;
+ engine->graphicsBindingVulkan.device = context->GetDevice();
+ engine->graphicsBindingVulkan.instance = context->GetInstance();
+ engine->graphicsBindingVulkan.physicalDevice = context->GetCurrentPhysicalDevice();
+ engine->graphicsBindingVulkan.queueFamilyIndex = context->GetGraphicsQueueFamilyIndex();
+ engine->graphicsBindingVulkan.queueIndex = 0;
+ VR_EnterVR(engine, &engine->graphicsBindingVulkan);
+ } else {
+ VR_EnterVR(engine, nullptr);
+ }
+ IN_VRInit(engine);
}
VR_SetConfig(VR_CONFIG_VIEWPORT_VALID, false);
}
@@ -124,17 +189,57 @@ void GetVRResolutionPerEye(int* width, int* height) {
}
}
-void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(const TouchInput &touch), bool haptics, float dp_xscale, float dp_yscale) {
+void SetVRCallbacks(bool(*axis)(const AxisInput &axis), bool(*key)(const KeyInput &key), bool(*touch)(const TouchInput &touch)) {
+ NativeAxis = axis;
+ NativeKey = key;
+ NativeTouch = touch;
+}
+
+/*
+================================================================================
+
+VR input integration
+
+================================================================================
+*/
+
+void SetVRAppMode(VRAppMode mode) {
+ appMode = mode;
+}
+
+void UpdateVRInput(bool haptics, float dp_xscale, float dp_yscale) {
+ //axis
+ if (pspKeys[VIRTKEY_VR_CAMERA_ADJUST]) {
+ AxisInput axis = {};
+ for (int j = 0; j < 2; j++) {
+ XrVector2f joystick = IN_VRGetJoystickState(j);
+ axis.deviceId = DEVICE_ID_DEFAULT;
+
+ //horizontal
+ axis.axisId = j == 0 ? JOYSTICK_AXIS_X : JOYSTICK_AXIS_Z;
+ axis.value = joystick.x;
+ NativeAxis(axis);
+
+ //vertical
+ axis.axisId = j == 0 ? JOYSTICK_AXIS_Y : JOYSTICK_AXIS_RZ;
+ axis.value = -joystick.y;
+ NativeAxis(axis);
+ }
+ }
+
//buttons
KeyInput keyInput = {};
for (int j = 0; j < 2; j++) {
int status = IN_VRGetButtonState(j);
for (ButtonMapping& m : controllerMapping[j]) {
+
+ //fill KeyInput structure
bool pressed = status & m.ovr;
keyInput.flags = pressed ? KEY_DOWN : KEY_UP;
keyInput.keyCode = m.keycode;
keyInput.deviceId = controllerIds[j];
+ //process the key action
if (m.pressed != pressed) {
if (pressed && haptics) {
INVR_Vibrate(100, j, 1000);
@@ -152,18 +257,104 @@ void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(con
}
}
+ //motion control
+ if (g_Config.bEnableMotions) {
+ for (int j = 0; j < 2; j++) {
+ bool activate;
+ float limit = g_Config.fMotionLength; //length of needed movement in meters
+ XrVector3f axis = {0, 1, 0};
+ float center = ToRadians(VR_GetConfigFloat(VR_CONFIG_MENU_YAW));
+ XrQuaternionf orientation = XrQuaternionf_CreateFromVectorAngle(axis, center);
+ XrVector3f position = XrQuaternionf_Rotate(orientation, IN_VRGetPose(j).position);
+
+ //up
+ activate = position.y > limit;
+ keyInput.flags = activate ? KEY_DOWN : KEY_UP;
+ keyInput.keyCode = NKCODE_EXT_MOTION_UP;
+ keyInput.deviceId = controllerIds[j];
+ if (controllerMotion[j][0] != activate) NativeKey(keyInput);
+ controllerMotion[j][0] = activate;
+
+ //down
+ activate = position.y < -limit * 1.5f;
+ keyInput.flags = activate ? KEY_DOWN : KEY_UP;
+ keyInput.keyCode = NKCODE_EXT_MOTION_DOWN;
+ keyInput.deviceId = controllerIds[j];
+ if (controllerMotion[j][1] != activate) NativeKey(keyInput);
+ controllerMotion[j][1] = activate;
+
+ //left
+ activate = position.x < -limit * (j == 0 ? 1.0f : 0.25f);
+ keyInput.flags = activate ? KEY_DOWN : KEY_UP;
+ keyInput.keyCode = NKCODE_EXT_MOTION_LEFT;
+ keyInput.deviceId = controllerIds[j];
+ if (controllerMotion[j][2] != activate) NativeKey(keyInput);
+ controllerMotion[j][2] = activate;
+
+ //right
+ activate = position.x > limit * (j == 1 ? 1.0f : 0.25f);
+ keyInput.flags = activate ? KEY_DOWN : KEY_UP;
+ keyInput.keyCode = NKCODE_EXT_MOTION_RIGHT;
+ keyInput.deviceId = controllerIds[j];
+ if (controllerMotion[j][3] != activate) NativeKey(keyInput);
+ controllerMotion[j][3] = activate;
+
+ //forward
+ activate = position.z < -limit;
+ keyInput.flags = activate ? KEY_DOWN : KEY_UP;
+ keyInput.keyCode = NKCODE_EXT_MOTION_FORWARD;
+ keyInput.deviceId = controllerIds[j];
+ if (controllerMotion[j][4] != activate) NativeKey(keyInput);
+ controllerMotion[j][4] = activate;
+ }
+ }
+
+ // Camera adjust
+ if (pspKeys[VIRTKEY_VR_CAMERA_ADJUST]) {
+ for (auto& device : pspAxis) {
+ for (auto& axis : device.second) {
+ switch(axis.first) {
+ case JOYSTICK_AXIS_X:
+ if (axis.second < -0.75f) g_Config.fCameraSide -= 0.05f;
+ if (axis.second > 0.75f) g_Config.fCameraSide += 0.05f;
+ g_Config.fCameraSide = clampFloat(g_Config.fCameraSide, -50.0f, 50.0f);
+ break;
+ case JOYSTICK_AXIS_Y:
+ if (axis.second > 0.75f) g_Config.fCameraHeight -= 0.05f;
+ if (axis.second < -0.75f) g_Config.fCameraHeight += 0.05f;
+ g_Config.fCameraHeight = clampFloat(g_Config.fCameraHeight, -50.0f, 50.0f);
+ break;
+ case JOYSTICK_AXIS_Z:
+ if (axis.second < -0.75f) g_Config.fHeadUpDisplayScale -= 0.01f;
+ if (axis.second > 0.75f) g_Config.fHeadUpDisplayScale += 0.01f;
+ g_Config.fHeadUpDisplayScale = clampFloat(g_Config.fHeadUpDisplayScale, 0.2f, 1.5f);
+ break;
+ case JOYSTICK_AXIS_RZ:
+ if (axis.second > 0.75f) g_Config.fCameraDistance -= 0.1f;
+ if (axis.second < -0.75f) g_Config.fCameraDistance += 0.1f;
+ g_Config.fCameraDistance = clampFloat(g_Config.fCameraDistance, -50.0f, 50.0f);
+ break;
+ }
+ }
+ }
+ }
+
//enable or disable mouse
for (int j = 0; j < 2; j++) {
- int status = IN_VRGetButtonState(j);
- for (MouseActivator& m : mouseActivators) {
- if (status & m.ovr) {
- mouseController = m.activate ? j : -1;
+ bool pressed = IN_VRGetButtonState(j) & ovrButton_Trigger;
+ if (pressed) {
+ int lastController = mouseController;
+ mouseController = j;
+
+ //prevent misclicks when changing the left/right controller
+ if (lastController != mouseController) {
+ mousePressed = true;
}
}
}
//mouse cursor
- if (mouseController >= 0) {
+ if ((mouseController >= 0) && ((appMode == VR_DIALOG_MODE) || (appMode == VR_MENU_MODE))) {
//get position on screen
XrPosef pose = IN_VRGetPose(mouseController);
XrVector3f angles = XrQuaternionf_ToEulerAngles(pose.orientation);
@@ -172,43 +363,104 @@ void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(con
float cx = width / 2;
float cy = height / 2;
float speed = (cx + cy) / 2;
- float x = cx - tan(ToRadians(angles.y - (float)VR_GetConfig(VR_CONFIG_MENU_YAW))) * speed;
- float y = cy - tan(ToRadians(angles.x)) * speed;
+ float x = cx - tan(ToRadians(angles.y - VR_GetConfigFloat(VR_CONFIG_MENU_YAW))) * speed;
+ float y = cy - tan(ToRadians(angles.x)) * speed * VR_GetConfigFloat(VR_CONFIG_CANVAS_ASPECT);
//set renderer
VR_SetConfig(VR_CONFIG_MOUSE_X, (int)x);
VR_SetConfig(VR_CONFIG_MOUSE_Y, (int)y);
- VR_SetConfig(VR_CONFIG_MOUSE_SIZE, 6 * (int)pow(VR_GetConfig(VR_CONFIG_CANVAS_DISTANCE), 0.25f));
+ VR_SetConfig(VR_CONFIG_MOUSE_SIZE, 6 * (int)pow(VR_GetConfigFloat(VR_CONFIG_CANVAS_DISTANCE), 0.25f));
//inform engine about the status
TouchInput touch;
touch.id = mouseController;
touch.x = x * dp_xscale;
- touch.y = (height - y - 1) * dp_yscale;
+ touch.y = (height - y - 1) * dp_yscale / VR_GetConfigFloat(VR_CONFIG_CANVAS_ASPECT);
bool pressed = IN_VRGetButtonState(mouseController) & ovrButton_Trigger;
- if (mousePressed[mouseController] != pressed) {
- if (!pressed) {
+ if (mousePressed != pressed) {
+ if (pressed) {
touch.flags = TOUCH_DOWN;
NativeTouch(touch);
touch.flags = TOUCH_UP;
NativeTouch(touch);
}
- mousePressed[mouseController] = pressed;
+ mousePressed = pressed;
+ }
+
+ //mouse wheel emulation
+ for (int j = 0; j < 2; j++) {
+ keyInput.deviceId = controllerIds[j];
+ float scroll = -IN_VRGetJoystickState(j).y;
+ keyInput.flags = scroll < -0.5f ? KEY_DOWN : KEY_UP;
+ keyInput.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
+ NativeKey(keyInput);
+ keyInput.flags = scroll > 0.5f ? KEY_DOWN : KEY_UP;
+ keyInput.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
+ NativeKey(keyInput);
}
} else {
VR_SetConfig(VR_CONFIG_MOUSE_SIZE, 0);
}
}
-void UpdateVRScreenKey(const KeyInput &key) {
+bool UpdateVRAxis(const AxisInput &axis) {
+ if (pspAxis.find(axis.deviceId) == pspAxis.end()) {
+ pspAxis[axis.deviceId] = std::map();
+ }
+ pspAxis[axis.deviceId][axis.axisId] = axis.value;
+ return !pspKeys[VIRTKEY_VR_CAMERA_ADJUST];
+}
+
+bool UpdateVRKeys(const KeyInput &key) {
+ //store key value
std::vector nativeKeys;
+ bool wasCameraAdjustOn = pspKeys[VIRTKEY_VR_CAMERA_ADJUST];
if (KeyMap::KeyToPspButton(key.deviceId, key.keyCode, &nativeKeys)) {
for (int& nativeKey : nativeKeys) {
- if (nativeKey == CTRL_SCREEN) {
- VR_SetConfig(VR_CONFIG_FORCE_2D, key.flags & KEY_DOWN);
+ pspKeys[nativeKey] = key.flags & KEY_DOWN;
+ }
+ }
+
+ //block VR controller keys in the UI mode
+ if ((key.deviceId == controllerIds[0]) || (key.deviceId == controllerIds[1])) {
+ if ((appMode == VR_DIALOG_MODE) || (appMode == VR_MENU_MODE)) {
+ switch (key.keyCode) {
+ case NKCODE_BACK:
+ case NKCODE_EXT_MOUSEWHEEL_UP:
+ case NKCODE_EXT_MOUSEWHEEL_DOWN:
+ return true;
+ default:
+ return false;
}
}
}
+
+ // Release keys on enabling camera adjust
+ if (!wasCameraAdjustOn && pspKeys[VIRTKEY_VR_CAMERA_ADJUST]) {
+ KeyInput keyUp;
+ keyUp.deviceId = key.deviceId;
+ keyUp.flags = KEY_UP;
+
+ pspKeys[VIRTKEY_VR_CAMERA_ADJUST] = false;
+ for (auto& pspKey : pspKeys) {
+ if (pspKey.second) {
+ keyUp.keyCode = pspKey.first;
+ NativeKey(keyUp);
+ }
+ }
+ pspKeys[VIRTKEY_VR_CAMERA_ADJUST] = true;
+ }
+
+ // Reset camera adjust
+ if (pspKeys[VIRTKEY_VR_CAMERA_ADJUST] && pspKeys[VIRTKEY_VR_CAMERA_RESET]) {
+ g_Config.fCameraHeight = 0;
+ g_Config.fCameraSide = 0;
+ g_Config.fCameraDistance = 0;
+ g_Config.fHeadUpDisplayScale = 0.3f;
+ }
+
+ //block keys by camera adjust
+ return !pspKeys[VIRTKEY_VR_CAMERA_ADJUST];
}
/*
@@ -219,6 +471,8 @@ void UpdateVRScreenKey(const KeyInput &key) {
================================================================================
*/
+#if XR_USE_GRAPHICS_API_OPENGL || XR_USE_GRAPHICS_API_OPENGL_ES
+
void PreprocessSkyplane(GLRStep* step) {
// Do not do anything if the scene is not in VR.
@@ -260,6 +514,12 @@ void PreprocessStepVR(void* step) {
if (vrCompat[VR_COMPAT_SKYPLANE]) PreprocessSkyplane(glrStep);
}
+#else
+
+void PreprocessStepVR(void* step) {}
+
+#endif
+
void SetVRCompat(VRCompatFlag flag, long value) {
vrCompat[flag] = value;
}
@@ -272,8 +532,8 @@ VR rendering integration
================================================================================
*/
-void BindVRFramebuffer() {
- VR_BindFramebuffer(VR_GetEngine());
+void* BindVRFramebuffer() {
+ return VR_BindFramebuffer(VR_GetEngine());
}
bool StartVRRender() {
@@ -284,22 +544,152 @@ bool StartVRRender() {
if (VR_InitFrame(VR_GetEngine())) {
- // Decide if the scene is 3D or not
- if (g_Config.bEnableVR && !VR_GetConfig(VR_CONFIG_FORCE_2D) && (VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) > 15)) {
- bool stereo = VR_GetConfig(VR_CONFIG_6DOF_PRECISE) && g_Config.bEnableStereo;
- VR_SetConfig(VR_CONFIG_MODE, stereo ? VR_MODE_STEREO_6DOF : VR_MODE_MONO_6DOF);
- } else {
- VR_SetConfig(VR_CONFIG_MODE, VR_MODE_FLAT_SCREEN);
+ // Get OpenXR view and fov
+ XrFovf fov = {};
+ XrPosef invViewTransform[2];
+ for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
+ XrView view = VR_GetView(eye);
+ fov.angleLeft += view.fov.angleLeft / 2.0f;
+ fov.angleRight += view.fov.angleRight / 2.0f;
+ fov.angleUp += view.fov.angleUp / 2.0f;
+ fov.angleDown += view.fov.angleDown / 2.0f;
+ invViewTransform[eye] = view.pose;
}
- VR_SetConfig(VR_CONFIG_3D_GEOMETRY_COUNT, VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) / 2);
+
+ // Get 6DoF scale
+ float scale = 1.0f;
+ bool hasUnitScale = false;
+ if (PSP_CoreParameter().compat.vrCompat().UnitsPerMeter > 0) {
+ scale = PSP_CoreParameter().compat.vrCompat().UnitsPerMeter;
+ hasUnitScale = true;
+ }
+
+ // Update matrices
+ for (int matrix = 0; matrix < VR_MATRIX_COUNT; matrix++) {
+ if (matrix == VR_PROJECTION_MATRIX) {
+ float nearZ = g_Config.fFieldOfViewPercentage / 200.0f;
+ float tanAngleLeft = tanf(fov.angleLeft);
+ float tanAngleRight = tanf(fov.angleRight);
+ float tanAngleDown = tanf(fov.angleDown);
+ float tanAngleUp = tanf(fov.angleUp);
+
+ float M[16] = {};
+ M[0] = 2 / (tanAngleRight - tanAngleLeft);
+ M[2] = (tanAngleRight + tanAngleLeft) / (tanAngleRight - tanAngleLeft);
+ M[5] = 2 / (tanAngleUp - tanAngleDown);
+ M[6] = (tanAngleUp + tanAngleDown) / (tanAngleUp - tanAngleDown);
+ M[10] = -1;
+ M[11] = -(nearZ + nearZ);
+ M[14] = -1;
+
+ memcpy(vrMatrix[matrix], M, sizeof(float) * 16);
+ } else if ((matrix == VR_VIEW_MATRIX_LEFT_EYE) || (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) {
+ bool flatScreen = false;
+ XrPosef invView = invViewTransform[0];
+ int vrMode = VR_GetConfig(VR_CONFIG_MODE);
+ if ((vrMode == VR_MODE_MONO_SCREEN) || (vrMode == VR_MODE_STEREO_SCREEN)) {
+ invView = XrPosef_Identity();
+ flatScreen = true;
+ }
+
+ // get axis mirroring configuration
+ float mx = vrMirroring[VR_MIRRORING_PITCH] ? -1.0f : 1.0f;
+ float my = vrMirroring[VR_MIRRORING_YAW] ? -1.0f : 1.0f;
+ float mz = vrMirroring[VR_MIRRORING_ROLL] ? -1.0f : 1.0f;
+
+ // ensure there is maximally one axis to mirror rotation
+ if (mx + my + mz < 0) {
+ mx *= -1.0f;
+ my *= -1.0f;
+ mz *= -1.0f;
+ } else {
+ invView = XrPosef_Inverse(invView);
+ }
+
+ // create updated quaternion
+ XrVector3f rotation = XrQuaternionf_ToEulerAngles(invView.orientation);
+ XrQuaternionf pitch = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, mx * ToRadians(rotation.x));
+ XrQuaternionf yaw = XrQuaternionf_CreateFromVectorAngle({0, 1, 0}, my * ToRadians(rotation.y));
+ XrQuaternionf roll = XrQuaternionf_CreateFromVectorAngle({0, 0, 1}, mz * ToRadians(rotation.z));
+ invView.orientation = XrQuaternionf_Multiply(roll, XrQuaternionf_Multiply(pitch, yaw));
+
+ float M[16];
+ XrQuaternionf_ToMatrix4f(&invView.orientation, M);
+ memcpy(&M, M, sizeof(float) * 16);
+
+ // Apply 6Dof head movement
+ if (!flatScreen && g_Config.bEnable6DoF) {
+ M[3] -= invViewTransform[0].position.x * (vrMirroring[VR_MIRRORING_AXIS_X] ? -1.0f : 1.0f) * scale;
+ M[7] -= invViewTransform[0].position.y * (vrMirroring[VR_MIRRORING_AXIS_Y] ? -1.0f : 1.0f) * scale;
+ M[11] -= invViewTransform[0].position.z * (vrMirroring[VR_MIRRORING_AXIS_Z] ? -1.0f : 1.0f) * scale;
+ }
+ // Camera adjust - distance
+ if (fabsf(g_Config.fCameraDistance) > 0.0f) {
+ XrVector3f forward = {0.0f, 0.0f, g_Config.fCameraDistance * scale};
+ forward = XrQuaternionf_Rotate(invView.orientation, forward);
+ forward = XrVector3f_ScalarMultiply(forward, vrMirroring[VR_MIRRORING_AXIS_Z] ? -1.0f : 1.0f);
+ M[3] += forward.x;
+ M[7] += forward.y;
+ M[11] += forward.z;
+ }
+ // Camera adjust - height
+ if (fabsf(g_Config.fCameraHeight) > 0.0f) {
+ XrVector3f up = {0.0f, -g_Config.fCameraHeight * scale, 0.0f};
+ up = XrQuaternionf_Rotate(invView.orientation, up);
+ up = XrVector3f_ScalarMultiply(up, vrMirroring[VR_MIRRORING_AXIS_Y] ? -1.0f : 1.0f);
+ M[3] += up.x;
+ M[7] += up.y;
+ M[11] += up.z;
+ }
+ // Camera adjust - side
+ if (fabsf(g_Config.fCameraSide) > 0.0f) {
+ XrVector3f side = {-g_Config.fCameraSide * scale, 0.0f, 0.0f};
+ side = XrQuaternionf_Rotate(invView.orientation, side);
+ side = XrVector3f_ScalarMultiply(side, vrMirroring[VR_MIRRORING_AXIS_X] ? -1.0f : 1.0f);
+ M[3] += side.x;
+ M[7] += side.y;
+ M[11] += side.z;
+ }
+ // Stereoscopy
+ if (hasUnitScale && (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) {
+ float dx = fabs(invViewTransform[1].position.x - invViewTransform[0].position.x);
+ float dy = fabs(invViewTransform[1].position.y - invViewTransform[0].position.y);
+ float dz = fabs(invViewTransform[1].position.z - invViewTransform[0].position.z);
+ float ipd = sqrt(dx * dx + dy * dy + dz * dz);
+ XrVector3f separation = {ipd * scale, 0.0f, 0.0f};
+ separation = XrQuaternionf_Rotate(invView.orientation, separation);
+ separation = XrVector3f_ScalarMultiply(separation, vrMirroring[VR_MIRRORING_AXIS_Z] ? -1.0f : 1.0f);
+ M[3] -= separation.x;
+ M[7] -= separation.y;
+ M[11] -= separation.z;
+ }
+ memcpy(vrMatrix[matrix], M, sizeof(float) * 16);
+ } else {
+ assert(false);
+ }
+ }
+
+ // Decide if the scene is 3D or not
+ bool stereo = hasUnitScale && g_Config.bEnableStereo;
+ VR_SetConfigFloat(VR_CONFIG_CANVAS_ASPECT, 480.0f / 272.0f);
+ if (g_Config.bEnableVR && !pspKeys[CTRL_SCREEN] && (appMode == VR_GAME_MODE) && (vr3DGeometryCount > 15)) {
+ VR_SetConfig(VR_CONFIG_MODE, stereo ? VR_MODE_STEREO_6DOF : VR_MODE_MONO_6DOF);
+ vrFlatGame = false;
+ } else {
+ VR_SetConfig(VR_CONFIG_MODE, stereo ? VR_MODE_STEREO_SCREEN : VR_MODE_MONO_SCREEN);
+ if (IsGameVRScene()) {
+ vrFlatGame = true;
+ }
+ }
+ vr3DGeometryCount /= 2;
// Set compatibility
vrCompat[VR_COMPAT_SKYPLANE] = PSP_CoreParameter().compat.vrCompat().Skyplane;
// Set customizations
- VR_SetConfig(VR_CONFIG_6DOF_ENABLED, g_Config.bEnable6DoF);
- VR_SetConfig(VR_CONFIG_CANVAS_DISTANCE, g_Config.iCanvasDistance);
- VR_SetConfig(VR_CONFIG_FOV_SCALE, g_Config.iFieldOfViewPercentage);
+ __DisplaySetFramerate(g_Config.bForce72Hz ? 72 : 60);
+ VR_SetConfigFloat(VR_CONFIG_CANVAS_DISTANCE, g_Config.fCanvasDistance);
+ vrMirroring[VR_MIRRORING_UPDATED] = false;
return true;
}
return false;
@@ -322,43 +712,137 @@ int GetVRFBOIndex() {
return VR_GetConfig(VR_CONFIG_CURRENT_FBO);
}
+int GetVRPassesCount() {
+ if (!IsMultiviewSupported() && g_Config.bEnableStereo) {
+ return 2;
+ } else {
+ return 1;
+ }
+}
+
bool IsMultiviewSupported() {
return false;
}
+bool IsFlatVRGame() {
+ return vrFlatGame;
+}
+
bool IsFlatVRScene() {
- return VR_GetConfig(VR_CONFIG_MODE) == VR_MODE_FLAT_SCREEN;
+ int vrMode = VR_GetConfig(VR_CONFIG_MODE);
+ return (vrMode == VR_MODE_MONO_SCREEN) || (vrMode == VR_MODE_STEREO_SCREEN);
+}
+
+bool IsGameVRScene() {
+ return (appMode == VR_GAME_MODE) || (appMode == VR_DIALOG_MODE);
}
bool Is2DVRObject(float* projMatrix, bool ortho) {
- bool is2D = VR_TweakIsMatrixBigScale(projMatrix) ||
- VR_TweakIsMatrixIdentity(projMatrix) ||
- VR_TweakIsMatrixOneOrtho(projMatrix) ||
- VR_TweakIsMatrixOneScale(projMatrix) ||
- VR_TweakIsMatrixOneTransform(projMatrix);
- if (!is2D && !ortho) {
- VR_SetConfig(VR_CONFIG_3D_GEOMETRY_COUNT, VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) + 1);
+
+ // Quick analyze if the object is in 2D
+ if ((fabs(fabs(projMatrix[12]) - 1.0f) < EPSILON) && (fabs(fabs(projMatrix[13]) - 1.0f) < EPSILON) && (fabs(fabs(projMatrix[14]) - 1.0f) < EPSILON)) {
+ return true;
+ } else if ((fabs(projMatrix[0]) > 10.0f) && (fabs(projMatrix[5]) > 10.0f)) {
+ return true;
+ } else if (fabs(projMatrix[15] - 1) < EPSILON) {
+ return true;
+ }
+
+ // Update 3D geometry count
+ bool identity = IsMatrixIdentity(projMatrix);
+ if (!identity && !ortho) {
+ vr3DGeometryCount++;
+ }
+ return identity || ortho;
+}
+
+void UpdateVRParams(float* projMatrix, float* viewMatrix) {
+
+ // Set mirroring of axes
+ bool identityView = PSP_CoreParameter().compat.vrCompat().IdentityViewHack && IsMatrixIdentity(viewMatrix);
+ if (!vrMirroring[VR_MIRRORING_UPDATED] && !IsMatrixIdentity(projMatrix) && !identityView) {
+ vrMirroring[VR_MIRRORING_UPDATED] = true;
+ vrMirroring[VR_MIRRORING_AXIS_X] = projMatrix[0] < 0;
+ vrMirroring[VR_MIRRORING_AXIS_Y] = projMatrix[5] < 0;
+ vrMirroring[VR_MIRRORING_AXIS_Z] = projMatrix[10] > 0;
+
+ float up = 0;
+ for (int i = 4; i < 7; i++) {
+ up += viewMatrix[i];
+ }
+
+ int variant = projMatrix[0] < 0;
+ variant += (projMatrix[5] < 0) << 1;
+ variant += (projMatrix[10] < 0) << 2;
+ variant += (up < 0) << 3;
+
+ switch (variant) {
+ case 0: //e.g. ATV
+ case 1: //untested
+ vrMirroring[VR_MIRRORING_PITCH] = false;
+ vrMirroring[VR_MIRRORING_YAW] = true;
+ vrMirroring[VR_MIRRORING_ROLL] = true;
+ break;
+ case 2: //e.g.PES 2014
+ case 3: //untested
+ case 5: //e.g Dante's Inferno
+ case 7: //untested
+ case 8: //untested
+ case 9: //untested
+ case 10: //untested
+ case 11: //untested
+ case 13: //untested
+ case 15: //untested
+ vrMirroring[VR_MIRRORING_PITCH] = true;
+ vrMirroring[VR_MIRRORING_YAW] = true;
+ vrMirroring[VR_MIRRORING_ROLL] = false;
+ break;
+ case 4: //e.g. Assassins Creed
+ case 6: //e.g. Ghost in the shell
+ case 12: //e.g. GTA Vice City
+ case 14: //untested
+ vrMirroring[VR_MIRRORING_PITCH] = true;
+ vrMirroring[VR_MIRRORING_YAW] = false;
+ vrMirroring[VR_MIRRORING_ROLL] = true;
+ break;
+ default:
+ assert(false);
+ std::exit(1);
+ }
}
- return is2D;
}
void UpdateVRProjection(float* projMatrix, float* leftEye, float* rightEye) {
- VR_TweakProjection(projMatrix, leftEye, VR_PROJECTION_MATRIX_LEFT_EYE);
- VR_TweakProjection(projMatrix, rightEye, VR_PROJECTION_MATRIX_RIGHT_EYE);
- VR_TweakMirroring(projMatrix);
-
- // Set 6DoF scale
- float scale = pow(fabs(projMatrix[14]), 1.15f);
- if (PSP_CoreParameter().compat.vrCompat().UnitsPerMeter > 0) {
- scale = PSP_CoreParameter().compat.vrCompat().UnitsPerMeter;
- VR_SetConfig(VR_CONFIG_6DOF_PRECISE, true);
- } else {
- VR_SetConfig(VR_CONFIG_6DOF_PRECISE, false);
+ float* hmdProjection = vrMatrix[VR_PROJECTION_MATRIX];
+ for (int i = 0; i < 16; i++) {
+ if ((hmdProjection[i] > 0) != (projMatrix[i] > 0)) {
+ hmdProjection[i] *= -1.0f;
+ }
}
- VR_SetConfig(VR_CONFIG_6DOF_SCALE, (int)(scale * 1000000));
+ memcpy(leftEye, hmdProjection, 16 * sizeof(float));
+ memcpy(rightEye, hmdProjection, 16 * sizeof(float));
}
void UpdateVRView(float* leftEye, float* rightEye) {
- VR_TweakView(leftEye, VR_VIEW_MATRIX_LEFT_EYE);
- VR_TweakView(rightEye, VR_VIEW_MATRIX_RIGHT_EYE);
+ float* dst[] = {leftEye, rightEye};
+ float* matrix[] = {vrMatrix[VR_VIEW_MATRIX_LEFT_EYE], vrMatrix[VR_VIEW_MATRIX_RIGHT_EYE]};
+ for (int index = 0; index < 2; index++) {
+
+ // Validate the view matrix
+ if (PSP_CoreParameter().compat.vrCompat().IdentityViewHack && IsMatrixIdentity(dst[index])) {
+ return;
+ }
+
+ // Get view matrix from the game
+ Lin::Matrix4x4 gameView = {};
+ memcpy(gameView.m, dst[index], 16 * sizeof(float));
+
+ // Get view matrix from the headset
+ Lin::Matrix4x4 hmdView = {};
+ memcpy(hmdView.m, matrix[index], 16 * sizeof(float));
+
+ // Combine the matrices
+ Lin::Matrix4x4 renderView = hmdView * gameView;
+ memcpy(dst[index], renderView.m, 16 * sizeof(float));
+ }
}
diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h
index fe6f7c4e98..009726a948 100644
--- a/Common/VR/PPSSPPVR.h
+++ b/Common/VR/PPSSPPVR.h
@@ -17,58 +17,43 @@ enum VRCompatFlag {
VR_COMPAT_MAX
};
-#ifdef OPENXR
+enum VRAppMode {
+ VR_CONTROLLER_MAPPING_MODE,
+ VR_DIALOG_MODE,
+ VR_GAME_MODE,
+ VR_MENU_MODE,
+};
// VR app flow integration
-bool IsVRBuild();
-void InitVROnAndroid(void* vm, void* activity, int version, const char* name);
-void EnterVR(bool firstStart);
+bool IsVREnabled();
+void InitVROnAndroid(void* vm, void* activity, const char* system, int version, const char* name);
+void EnterVR(bool firstStart, void* vulkanContext);
void GetVRResolutionPerEye(int* width, int* height);
-void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(const TouchInput &touch), bool haptics, float dp_xscale, float dp_yscale);
-void UpdateVRScreenKey(const KeyInput &key);
+void SetVRCallbacks(bool(*axis)(const AxisInput &axis), bool(*key)(const KeyInput &key), bool(*touch)(const TouchInput &touch));
+
+// VR input integration
+void SetVRAppMode(VRAppMode mode);
+void UpdateVRInput(bool haptics, float dp_xscale, float dp_yscale);
+bool UpdateVRAxis(const AxisInput &axis);
+bool UpdateVRKeys(const KeyInput &key);
// VR games compatibility
void PreprocessStepVR(void* step);
void SetVRCompat(VRCompatFlag flag, long value);
// VR rendering integration
-void BindVRFramebuffer();
+void* BindVRFramebuffer();
bool StartVRRender();
void FinishVRRender();
void PreVRFrameRender(int fboIndex);
void PostVRFrameRender();
int GetVRFBOIndex();
+int GetVRPassesCount();
bool IsMultiviewSupported();
+bool IsFlatVRGame();
bool IsFlatVRScene();
+bool IsGameVRScene();
bool Is2DVRObject(float* projMatrix, bool ortho);
+void UpdateVRParams(float* projMatrix, float* viewMatrix);
void UpdateVRProjection(float* projMatrix, float* leftEye, float* rightEye);
void UpdateVRView(float* leftEye, float* rightEye);
-
-#else //dummy integration
-
-// VR app flow integration
-inline bool IsVRBuild() { return false; }
-inline void InitVROnAndroid(void* vm, void* activity, int version, const char* name) {}
-inline void EnterVR(bool firstTime) {}
-inline void GetVRResolutionPerEye(int* width, int* height) {}
-inline void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(const TouchInput &touch), bool haptics, float dp_xscale, float dp_yscale) {}
-inline void UpdateVRScreenKey(const KeyInput &key) {}
-
-// VR games compatibility
-inline void PreprocessStepVR(void* step) {}
-inline void SetVRCompat(VRCompatFlag flag, long value) {}
-
-// VR rendering integration
-inline void BindVRFramebuffer() {}
-inline bool StartVRRender() { return false; }
-inline void FinishVRRender() {}
-inline void PreVRFrameRender(int fboIndex) {}
-inline void PostVRFrameRender() {}
-inline int GetVRFBOIndex() { return 0; }
-inline bool IsMultiviewSupported() { return false; }
-inline bool IsFlatVRScene() { return true; }
-inline bool Is2DVRObject(float* projMatrix, bool ortho) { return false; }
-inline void UpdateVRProjection(float* projMatrix, float* leftEye, float* rightEye) {}
-inline void UpdateVRView(float* leftEye, float* rightEye) {}
-
-#endif
diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp
index 4bc4723a11..2cb2d0557d 100644
--- a/Common/VR/VRBase.cpp
+++ b/Common/VR/VRBase.cpp
@@ -3,104 +3,89 @@
#include
#include
#include
+#include
+
+#ifndef _WIN32
#include
-
-#ifdef OPENXR_PLATFORM_PICO
-enum ConfigsSetEXT {
- UNREAL_VERSION = 0,
- TRACKING_ORIGIN,
- OPENGL_NOERROR,
- ENABLE_SIX_DOF,
- PRESENTATION_FLAG,
- ENABLE_CPT,
- PLATFORM,
- FOVEATION_LEVEL,
- SET_DISPLAY_RATE = 8,
- MRC_TEXTURE_ID = 9,
-};
-
-enum PxrTrackingDof {
- PXR_TRACKING_3DOF = 0,
- PXR_TRACKING_6DOF = 1
-};
-
-typedef XrResult (XRAPI_PTR *PFN_xrSetEngineVersionPico)(XrInstance instance,const char* version);
-typedef XrResult (XRAPI_PTR *PFN_xrStartCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState);
-typedef XrResult (XRAPI_PTR *PFN_xrStopCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState);
-typedef XrResult (XRAPI_PTR *PFN_xrSetConfigPICO) (XrSession instance, enum ConfigsSetEXT configIndex, char* configData);
-
-PFN_xrSetConfigPICO pfnXrSetConfigPICO = nullptr;
-PFN_xrSetEngineVersionPico pfnXrSetEngineVersionPico = nullptr;
-PFN_xrStartCVControllerThreadPico pfnXrStartCVControllerThreadPico = nullptr;
-PFN_xrStopCVControllerThreadPico pfnXrStopCVControllerThreadPico = nullptr;
#endif
+static bool vr_platform[VR_PLATFORM_MAX];
static engine_t vr_engine;
int vr_initialized = 0;
-const char* const requiredExtensionNames[] = {
- XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
-#ifdef OPENXR_HAS_PERFORMANCE_EXTENSION
- XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME,
- XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME,
-#endif
-#ifdef OPENXR_PLATFORM_PICO
- XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME,
- "XR_PICO_android_controller_function_ext_enable",
- "XR_PICO_view_state_ext_enable",
- "XR_PICO_frame_end_info_ext",
- "XR_PICO_configs_ext",
- "XR_PICO_reset_sensor",
-#endif
- XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME};
-const uint32_t numRequiredExtensions =
- sizeof(requiredExtensionNames) / sizeof(requiredExtensionNames[0]);
-
-void VR_Init( ovrJava java ) {
+void VR_Init( void* system, const char* name, int version ) {
if (vr_initialized)
return;
+ if (!XRLoad()) {
+ return;
+ }
+
ovrApp_Clear(&vr_engine.appState);
+#ifdef ANDROID
PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
- xrGetInstanceProcAddr(
- XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
+ xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
if (xrInitializeLoaderKHR != NULL) {
- XrLoaderInitInfoAndroidKHR loaderInitializeInfoAndroid;
- memset(&loaderInitializeInfoAndroid, 0, sizeof(loaderInitializeInfoAndroid));
- loaderInitializeInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
- loaderInitializeInfoAndroid.next = NULL;
- loaderInitializeInfoAndroid.applicationVM = java.Vm;
- loaderInitializeInfoAndroid.applicationContext = java.ActivityObject;
- xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loaderInitializeInfoAndroid);
+ ovrJava* java = (ovrJava*)system;
+ XrLoaderInitInfoAndroidKHR loaderInitializeInfo;
+ memset(&loaderInitializeInfo, 0, sizeof(loaderInitializeInfo));
+ loaderInitializeInfo.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
+ loaderInitializeInfo.next = NULL;
+ loaderInitializeInfo.applicationVM = java->Vm;
+ loaderInitializeInfo.applicationContext = java->ActivityObject;
+ xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loaderInitializeInfo);
}
+#endif
+
+ std::vector extensions;
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ extensions.push_back(XR_KHR_VULKAN_ENABLE_EXTENSION_NAME);
+ } else {
+#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
+ extensions.push_back(XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME);
+#endif
+ }
+ extensions.push_back(XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME);
+#ifdef ANDROID
+ if (VR_GetPlatformFlag(VR_PLATFORM_INSTANCE_EXT)) {
+ extensions.push_back(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME);
+ }
+ if (VR_GetPlatformFlag(VR_PLATFORM_PERFORMANCE_EXT)) {
+ extensions.push_back(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME);
+ extensions.push_back(XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME);
+ }
+#endif
// Create the OpenXR instance.
XrApplicationInfo appInfo;
memset(&appInfo, 0, sizeof(appInfo));
- strcpy(appInfo.applicationName, java.AppName);
- strcpy(appInfo.engineName, java.AppName);
- appInfo.applicationVersion = java.AppVersion;
- appInfo.engineVersion = java.AppVersion;
+ strcpy(appInfo.applicationName, name);
+ strcpy(appInfo.engineName, name);
+ appInfo.applicationVersion = version;
+ appInfo.engineVersion = version;
appInfo.apiVersion = XR_CURRENT_API_VERSION;
XrInstanceCreateInfo instanceCreateInfo;
memset(&instanceCreateInfo, 0, sizeof(instanceCreateInfo));
instanceCreateInfo.type = XR_TYPE_INSTANCE_CREATE_INFO;
-#ifdef OPENXR_PLATFORM_PICO
- XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
- instanceCreateInfoAndroid.applicationVM = java.Vm;
- instanceCreateInfoAndroid.applicationActivity = java.ActivityObject;
- instanceCreateInfo.next = (XrBaseInStructure*)&instanceCreateInfoAndroid;
-#else
instanceCreateInfo.next = NULL;
-#endif
instanceCreateInfo.createFlags = 0;
instanceCreateInfo.applicationInfo = appInfo;
instanceCreateInfo.enabledApiLayerCount = 0;
instanceCreateInfo.enabledApiLayerNames = NULL;
- instanceCreateInfo.enabledExtensionCount = numRequiredExtensions;
- instanceCreateInfo.enabledExtensionNames = requiredExtensionNames;
+ instanceCreateInfo.enabledExtensionCount = (uint32_t)extensions.size();
+ instanceCreateInfo.enabledExtensionNames = extensions.data();
+
+#ifdef ANDROID
+ XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
+ if (VR_GetPlatformFlag(VR_PLATFORM_INSTANCE_EXT)) {
+ ovrJava* java = (ovrJava*)system;
+ instanceCreateInfoAndroid.applicationVM = java->Vm;
+ instanceCreateInfoAndroid.applicationActivity = java->ActivityObject;
+ instanceCreateInfo.next = (XrBaseInStructure*)&instanceCreateInfoAndroid;
+ }
+#endif
XrResult initResult;
OXR(initResult = xrCreateInstance(&instanceCreateInfo, &vr_engine.appState.Instance));
@@ -109,14 +94,7 @@ void VR_Init( ovrJava java ) {
exit(1);
}
-#ifdef OPENXR_PLATFORM_PICO
- xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrSetEngineVersionPico", (PFN_xrVoidFunction*)(&pfnXrSetEngineVersionPico));
- xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrStartCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStartCVControllerThreadPico));
- xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrStopCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStopCVControllerThreadPico));
- xrGetInstanceProcAddr(vr_engine.appState.Instance,"xrSetConfigPICO", (PFN_xrVoidFunction*)(&pfnXrSetConfigPICO));
- if (pfnXrSetEngineVersionPico != nullptr) pfnXrSetEngineVersionPico(vr_engine.appState.Instance, "2.8.0.1");
- if (pfnXrStartCVControllerThreadPico != nullptr) pfnXrStartCVControllerThreadPico(vr_engine.appState.Instance, PXR_TRACKING_6DOF, PXR_TRACKING_6DOF);
-#endif
+ XRLoadInstanceFunctions(vr_engine.appState.Instance);
XrInstanceProperties instanceInfo;
instanceInfo.type = XR_TYPE_INSTANCE_PROPERTIES;
@@ -143,36 +121,45 @@ void VR_Init( ovrJava java ) {
}
// Get the graphics requirements.
- PFN_xrGetOpenGLESGraphicsRequirementsKHR pfnGetOpenGLESGraphicsRequirementsKHR = NULL;
- OXR(xrGetInstanceProcAddr(
- vr_engine.appState.Instance,
- "xrGetOpenGLESGraphicsRequirementsKHR",
- (PFN_xrVoidFunction*)(&pfnGetOpenGLESGraphicsRequirementsKHR)));
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ PFN_xrGetVulkanGraphicsRequirementsKHR pfnGetVulkanGraphicsRequirementsKHR = NULL;
+ OXR(xrGetInstanceProcAddr(
+ vr_engine.appState.Instance,
+ "xrGetVulkanGraphicsRequirementsKHR",
+ (PFN_xrVoidFunction*)(&pfnGetVulkanGraphicsRequirementsKHR)));
- XrGraphicsRequirementsOpenGLESKHR graphicsRequirements = {};
- graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR;
- OXR(pfnGetOpenGLESGraphicsRequirementsKHR(vr_engine.appState.Instance, systemId, &graphicsRequirements));
+ XrGraphicsRequirementsVulkanKHR graphicsRequirements = {};
+ graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR;
+ OXR(pfnGetVulkanGraphicsRequirementsKHR(vr_engine.appState.Instance, systemId, &graphicsRequirements));
+ } else {
+#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
+ PFN_xrGetOpenGLESGraphicsRequirementsKHR pfnGetOpenGLESGraphicsRequirementsKHR = NULL;
+ OXR(xrGetInstanceProcAddr(
+ vr_engine.appState.Instance,
+ "xrGetOpenGLESGraphicsRequirementsKHR",
+ (PFN_xrVoidFunction*)(&pfnGetOpenGLESGraphicsRequirementsKHR)));
+ XrGraphicsRequirementsOpenGLESKHR graphicsRequirements = {};
+ graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR;
+ OXR(pfnGetOpenGLESGraphicsRequirementsKHR(vr_engine.appState.Instance, systemId, &graphicsRequirements));
+#endif
+ }
+
+#ifdef ANDROID
vr_engine.appState.MainThreadTid = gettid();
+#endif
vr_engine.appState.SystemId = systemId;
-
- vr_engine.java = java;
vr_initialized = 1;
}
void VR_Destroy( engine_t* engine ) {
if (engine == &vr_engine) {
-#ifdef OPENXR_PLATFORM_PICO
- if (pfnXrStopCVControllerThreadPico != nullptr) {
- pfnXrStopCVControllerThreadPico(engine->appState.Instance, PXR_TRACKING_6DOF, PXR_TRACKING_6DOF);
- }
-#endif
xrDestroyInstance(engine->appState.Instance);
ovrApp_Destroy(&engine->appState);
}
}
-void VR_EnterVR( engine_t* engine ) {
+void VR_EnterVR( engine_t* engine, XrGraphicsBindingVulkanKHR* graphicsBindingVulkan ) {
if (engine->appState.Session) {
ALOGE("VR_EnterVR called with existing session");
@@ -180,17 +167,28 @@ void VR_EnterVR( engine_t* engine ) {
}
// Create the OpenXR Session.
- XrGraphicsBindingOpenGLESAndroidKHR graphicsBindingAndroidGLES = {};
- graphicsBindingAndroidGLES.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR;
- graphicsBindingAndroidGLES.next = NULL;
- graphicsBindingAndroidGLES.display = eglGetCurrentDisplay();
- graphicsBindingAndroidGLES.config = eglGetCurrentSurface(EGL_DRAW);
- graphicsBindingAndroidGLES.context = eglGetCurrentContext();
-
XrSessionCreateInfo sessionCreateInfo = {};
+#ifdef ANDROID
+ XrGraphicsBindingOpenGLESAndroidKHR graphicsBindingGL = {};
+#elif XR_USE_GRAPHICS_API_OPENGL
+ XrGraphicsBindingOpenGLWin32KHR graphicsBindingGL = {};
+#endif
memset(&sessionCreateInfo, 0, sizeof(sessionCreateInfo));
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ sessionCreateInfo.next = graphicsBindingVulkan;
+ } else {
+#ifdef ANDROID
+ graphicsBindingGL.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR;
+ graphicsBindingGL.next = NULL;
+ graphicsBindingGL.display = eglGetCurrentDisplay();
+ graphicsBindingGL.config = eglGetCurrentSurface(EGL_DRAW);
+ graphicsBindingGL.context = eglGetCurrentContext();
+ sessionCreateInfo.next = &graphicsBindingGL;
+#else
+ //TODO:PCVR definition
+#endif
+ }
sessionCreateInfo.type = XR_TYPE_SESSION_CREATE_INFO;
- sessionCreateInfo.next = &graphicsBindingAndroidGLES;
sessionCreateInfo.createFlags = 0;
sessionCreateInfo.systemId = engine->appState.SystemId;
@@ -200,9 +198,6 @@ void VR_EnterVR( engine_t* engine ) {
ALOGE("Failed to create XR session: %d.", initResult);
exit(1);
}
-#ifdef OPENXR_PLATFORM_PICO
- pfnXrSetConfigPICO(engine->appState.Session, TRACKING_ORIGIN, "1");
-#endif
// Create a space to the first path
XrReferenceSpaceCreateInfo spaceCreateInfo = {};
@@ -229,3 +224,11 @@ void VR_LeaveVR( engine_t* engine ) {
engine_t* VR_GetEngine( void ) {
return &vr_engine;
}
+
+bool VR_GetPlatformFlag(VRPlatformFlag flag) {
+ return vr_platform[flag];
+}
+
+void VR_SetPlatformFLag(VRPlatformFlag flag, bool value) {
+ vr_platform[flag] = value;
+}
diff --git a/Common/VR/VRBase.h b/Common/VR/VRBase.h
index a94e3d3fa9..25efb1b712 100644
--- a/Common/VR/VRBase.h
+++ b/Common/VR/VRBase.h
@@ -5,51 +5,20 @@
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, "OpenXR", __VA_ARGS__);
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "OpenXR", __VA_ARGS__);
#else
+#include
#define ALOGE(...) printf(__VA_ARGS__)
#define ALOGV(...) printf(__VA_ARGS__)
#endif
-//OpenXR
-#define XR_USE_PLATFORM_ANDROID 1
-#define XR_USE_GRAPHICS_API_OPENGL_ES 1
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "Common/VR/OpenXRLoader.h"
-#ifdef _DEBUG
-static const char* GlErrorString(GLenum error) {
- switch (error) {
- case GL_NO_ERROR:
- return "GL_NO_ERROR";
- case GL_INVALID_ENUM:
- return "GL_INVALID_ENUM";
- case GL_INVALID_VALUE:
- return "GL_INVALID_VALUE";
- case GL_INVALID_OPERATION:
- return "GL_INVALID_OPERATION";
- case GL_INVALID_FRAMEBUFFER_OPERATION:
- return "GL_INVALID_FRAMEBUFFER_OPERATION";
- case GL_OUT_OF_MEMORY:
- return "GL_OUT_OF_MEMORY";
- default:
- return "unknown";
- }
-}
+#define _USE_MATH_DEFINES
+#include
+#include
-static void GLCheckErrors(char* file, int line) {
- for (int i = 0; i < 10; i++) {
- const GLenum error = glGetError();
- if (error == GL_NO_ERROR) {
- break;
- }
- ALOGE("GL error on line %s:%d %s", file, line, GlErrorString(error));
- }
-}
+#if defined(_DEBUG) && (defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES))
+
+void GLCheckErrors(const char* file, int line);
#define GL(func) func; GLCheckErrors(__FILE__ , __LINE__);
#else
@@ -73,11 +42,7 @@ static void OXR_CheckErrors(XrInstance instance, XrResult result, const char* fu
#define OXR(func) func;
#endif
-#ifdef OPENXR_PLATFORM_QUEST
-#define OPENXR_HAS_PERFORMANCE_EXTENSION
-#endif
-
-enum { ovrMaxLayerCount = 1 };
+enum { ovrMaxLayerCount = 2 };
enum { ovrMaxNumEyes = 2 };
typedef union {
@@ -98,10 +63,15 @@ typedef struct {
uint32_t TextureSwapChainIndex;
ovrSwapChain ColorSwapChain;
ovrSwapChain DepthSwapChain;
- XrSwapchainImageOpenGLESKHR* ColorSwapChainImage;
- XrSwapchainImageOpenGLESKHR* DepthSwapChainImage;
- unsigned int* FrameBuffers;
+ void* ColorSwapChainImage;
+ void* DepthSwapChainImage;
+ unsigned int* GLFrameBuffers;
+ VkFramebuffer* VKFrameBuffers;
+ VkImageView* VKColorImages;
+ VkImageView* VKDepthImages;
+
bool Acquired;
+ XrGraphicsBindingVulkanKHR* VKContext;
} ovrFramebuffer;
typedef struct {
@@ -133,27 +103,39 @@ typedef struct {
ovrRenderer Renderer;
} ovrApp;
+#ifdef ANDROID
typedef struct {
JavaVM* Vm;
jobject ActivityObject;
JNIEnv* Env;
- char AppName[64];
- int AppVersion;
} ovrJava;
+#endif
typedef struct {
uint64_t frameIndex;
ovrApp appState;
- ovrJava java;
- float predictedDisplayTime;
+ XrTime predictedDisplayTime;
+ XrGraphicsBindingVulkanKHR graphicsBindingVulkan;
} engine_t;
-void VR_Init( ovrJava java );
+enum VRPlatformFlag {
+ VR_PLATFORM_CONTROLLER_PICO,
+ VR_PLATFORM_CONTROLLER_QUEST,
+ VR_PLATFORM_INSTANCE_EXT,
+ VR_PLATFORM_PERFORMANCE_EXT,
+ VR_PLATFORM_RENDERER_VULKAN,
+ VR_PLATFORM_TRACKING_FLOOR,
+ VR_PLATFORM_MAX
+};
+
+void VR_Init( void* system, const char* name, int version );
void VR_Destroy( engine_t* engine );
-void VR_EnterVR( engine_t* engine );
+void VR_EnterVR( engine_t* engine, XrGraphicsBindingVulkanKHR* graphicsBindingVulkan );
void VR_LeaveVR( engine_t* engine );
engine_t* VR_GetEngine( void );
+bool VR_GetPlatformFlag(VRPlatformFlag flag);
+void VR_SetPlatformFLag(VRPlatformFlag flag, bool value);
void ovrApp_Clear(ovrApp* app);
void ovrApp_Destroy(ovrApp* app);
diff --git a/Common/VR/VRFramebuffer.cpp b/Common/VR/VRFramebuffer.cpp
index 9dab8411e3..792dd5a879 100644
--- a/Common/VR/VRFramebuffer.cpp
+++ b/Common/VR/VRFramebuffer.cpp
@@ -1,17 +1,23 @@
#include "VRFramebuffer.h"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#if XR_USE_GRAPHICS_API_OPENGL_ES
+
#include
#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if !defined(_WIN32)
+#include
+#endif
+
double FromXrTime(const XrTime time) {
return (time * 1e-9);
}
@@ -39,12 +45,46 @@ void ovrFramebuffer_Clear(ovrFramebuffer* frameBuffer) {
frameBuffer->DepthSwapChain.Height = 0;
frameBuffer->DepthSwapChainImage = NULL;
- frameBuffer->FrameBuffers = NULL;
+ frameBuffer->GLFrameBuffers = NULL;
frameBuffer->Acquired = false;
}
-bool ovrFramebuffer_Create(XrSession session, ovrFramebuffer* frameBuffer, int width, int height, bool multiview) {
+#if XR_USE_GRAPHICS_API_OPENGL || XR_USE_GRAPHICS_API_OPENGL_ES
+static const char* GlErrorString(GLenum error) {
+ switch (error) {
+ case GL_NO_ERROR:
+ return "GL_NO_ERROR";
+ case GL_INVALID_ENUM:
+ return "GL_INVALID_ENUM";
+ case GL_INVALID_VALUE:
+ return "GL_INVALID_VALUE";
+ case GL_INVALID_OPERATION:
+ return "GL_INVALID_OPERATION";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "GL_INVALID_FRAMEBUFFER_OPERATION";
+ case GL_OUT_OF_MEMORY:
+ return "GL_OUT_OF_MEMORY";
+ default:
+ return "unknown";
+ }
+}
+
+void GLCheckErrors(const char* file, int line) {
+ for (int i = 0; i < 10; i++) {
+ const GLenum error = glGetError();
+ if (error == GL_NO_ERROR) {
+ break;
+ }
+ ALOGE("GL error on line %s:%d %s", file, line, GlErrorString(error));
+ }
+}
+
+#endif
+
+#if XR_USE_GRAPHICS_API_OPENGL_ES
+
+static bool ovrFramebuffer_CreateGLES(XrSession session, ovrFramebuffer* frameBuffer, int width, int height, bool multiview) {
frameBuffer->Width = width;
frameBuffer->Height = height;
@@ -54,12 +94,11 @@ bool ovrFramebuffer_Create(XrSession session, ovrFramebuffer* frameBuffer, int w
}
typedef void (*PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei);
- auto glFramebufferTextureMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR)eglGetProcAddress ("glFramebufferTextureMultiviewOVR");
- if (!glFramebufferTextureMultiviewOVR)
- {
+ PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR glFramebufferTextureMultiviewOVR = nullptr;
+ glFramebufferTextureMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR)eglGetProcAddress ("glFramebufferTextureMultiviewOVR");
+ if (!glFramebufferTextureMultiviewOVR) {
ALOGE("Can not get proc address for glFramebufferTextureMultiviewOVR.\n");
}
-
XrSwapchainCreateInfo swapChainCreateInfo;
memset(&swapChainCreateInfo, 0, sizeof(swapChainCreateInfo));
swapChainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
@@ -80,20 +119,20 @@ bool ovrFramebuffer_Create(XrSession session, ovrFramebuffer* frameBuffer, int w
swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
OXR(xrCreateSwapchain(session, &swapChainCreateInfo, &frameBuffer->ColorSwapChain.Handle));
OXR(xrEnumerateSwapchainImages(frameBuffer->ColorSwapChain.Handle, 0, &frameBuffer->TextureSwapChainLength, NULL));
- frameBuffer->ColorSwapChainImage = (XrSwapchainImageOpenGLESKHR*)malloc(frameBuffer->TextureSwapChainLength * sizeof(XrSwapchainImageOpenGLESKHR));
+ frameBuffer->ColorSwapChainImage = malloc(frameBuffer->TextureSwapChainLength * sizeof(XrSwapchainImageOpenGLESKHR));
// Create the depth swapchain.
swapChainCreateInfo.format = GL_DEPTH24_STENCIL8;
swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
OXR(xrCreateSwapchain(session, &swapChainCreateInfo, &frameBuffer->DepthSwapChain.Handle));
- frameBuffer->DepthSwapChainImage = (XrSwapchainImageOpenGLESKHR*)malloc(frameBuffer->TextureSwapChainLength * sizeof(XrSwapchainImageOpenGLESKHR));
+ frameBuffer->DepthSwapChainImage = malloc(frameBuffer->TextureSwapChainLength * sizeof(XrSwapchainImageOpenGLESKHR));
// Populate the swapchain image array.
for (uint32_t i = 0; i < frameBuffer->TextureSwapChainLength; i++) {
- frameBuffer->ColorSwapChainImage[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
- frameBuffer->ColorSwapChainImage[i].next = NULL;
- frameBuffer->DepthSwapChainImage[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
- frameBuffer->DepthSwapChainImage[i].next = NULL;
+ ((XrSwapchainImageOpenGLESKHR*)frameBuffer->ColorSwapChainImage)[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
+ ((XrSwapchainImageOpenGLESKHR*)frameBuffer->ColorSwapChainImage)[i].next = NULL;
+ ((XrSwapchainImageOpenGLESKHR*)frameBuffer->DepthSwapChainImage)[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
+ ((XrSwapchainImageOpenGLESKHR*)frameBuffer->DepthSwapChainImage)[i].next = NULL;
}
OXR(xrEnumerateSwapchainImages(
frameBuffer->ColorSwapChain.Handle,
@@ -106,14 +145,14 @@ bool ovrFramebuffer_Create(XrSession session, ovrFramebuffer* frameBuffer, int w
&frameBuffer->TextureSwapChainLength,
(XrSwapchainImageBaseHeader*)frameBuffer->DepthSwapChainImage));
- frameBuffer->FrameBuffers = (GLuint*)malloc(frameBuffer->TextureSwapChainLength * sizeof(GLuint));
+ frameBuffer->GLFrameBuffers = (GLuint*)malloc(frameBuffer->TextureSwapChainLength * sizeof(GLuint));
for (uint32_t i = 0; i < frameBuffer->TextureSwapChainLength; i++) {
- const GLuint colorTexture = frameBuffer->ColorSwapChainImage[i].image;
- const GLuint depthTexture = frameBuffer->DepthSwapChainImage[i].image;
+ const GLuint colorTexture = ((XrSwapchainImageOpenGLESKHR*)frameBuffer->ColorSwapChainImage)[i].image;
+ const GLuint depthTexture = ((XrSwapchainImageOpenGLESKHR*)frameBuffer->DepthSwapChainImage)[i].image;
// Create the frame buffer.
- GL(glGenFramebuffers(1, &frameBuffer->FrameBuffers[i]));
- GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->FrameBuffers[i]));
+ GL(glGenFramebuffers(1, &frameBuffer->GLFrameBuffers[i]));
+ GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->GLFrameBuffers[i]));
if (multiview) {
GL(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexture, 0, 0, 2));
GL(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0, 0, 2));
@@ -134,29 +173,148 @@ bool ovrFramebuffer_Create(XrSession session, ovrFramebuffer* frameBuffer, int w
return true;
}
+#endif
+
+#if XR_USE_GRAPHICS_API_VULKAN
+
+static bool ovrFramebuffer_CreateVK(XrSession session, ovrFramebuffer* frameBuffer, int width, int height,
+ bool multiview, void* context) {
+
+ frameBuffer->Width = width;
+ frameBuffer->Height = height;
+ frameBuffer->VKContext = (XrGraphicsBindingVulkanKHR*)context;
+
+ XrSwapchainCreateInfo swapChainCreateInfo;
+ memset(&swapChainCreateInfo, 0, sizeof(swapChainCreateInfo));
+ swapChainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
+ swapChainCreateInfo.sampleCount = 1;
+ swapChainCreateInfo.width = width;
+ swapChainCreateInfo.height = height;
+ swapChainCreateInfo.faceCount = 1;
+ swapChainCreateInfo.mipCount = 1;
+ swapChainCreateInfo.arraySize = multiview ? 2 : 1;
+
+ frameBuffer->ColorSwapChain.Width = swapChainCreateInfo.width;
+ frameBuffer->ColorSwapChain.Height = swapChainCreateInfo.height;
+ frameBuffer->DepthSwapChain.Width = swapChainCreateInfo.width;
+ frameBuffer->DepthSwapChain.Height = swapChainCreateInfo.height;
+
+ // Create the color swapchain.
+ swapChainCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+ swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
+ OXR(xrCreateSwapchain(session, &swapChainCreateInfo, &frameBuffer->ColorSwapChain.Handle));
+ OXR(xrEnumerateSwapchainImages(frameBuffer->ColorSwapChain.Handle, 0, &frameBuffer->TextureSwapChainLength, NULL));
+ frameBuffer->ColorSwapChainImage = malloc(frameBuffer->TextureSwapChainLength * sizeof(XrSwapchainImageVulkanKHR));
+
+ // Create the depth swapchain.
+ swapChainCreateInfo.format = VK_FORMAT_D24_UNORM_S8_UINT;
+ swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ OXR(xrCreateSwapchain(session, &swapChainCreateInfo, &frameBuffer->DepthSwapChain.Handle));
+ frameBuffer->DepthSwapChainImage = malloc(frameBuffer->TextureSwapChainLength * sizeof(XrSwapchainImageVulkanKHR));
+
+ // Populate the swapchain image array.
+ for (uint32_t i = 0; i < frameBuffer->TextureSwapChainLength; i++) {
+ ((XrSwapchainImageVulkanKHR*)frameBuffer->ColorSwapChainImage)[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR;
+ ((XrSwapchainImageVulkanKHR*)frameBuffer->ColorSwapChainImage)[i].next = NULL;
+ ((XrSwapchainImageVulkanKHR*)frameBuffer->DepthSwapChainImage)[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR;
+ ((XrSwapchainImageVulkanKHR*)frameBuffer->DepthSwapChainImage)[i].next = NULL;
+ }
+ OXR(xrEnumerateSwapchainImages(
+ frameBuffer->ColorSwapChain.Handle,
+ frameBuffer->TextureSwapChainLength,
+ &frameBuffer->TextureSwapChainLength,
+ (XrSwapchainImageBaseHeader*)frameBuffer->ColorSwapChainImage));
+ OXR(xrEnumerateSwapchainImages(
+ frameBuffer->DepthSwapChain.Handle,
+ frameBuffer->TextureSwapChainLength,
+ &frameBuffer->TextureSwapChainLength,
+ (XrSwapchainImageBaseHeader*)frameBuffer->DepthSwapChainImage));
+
+ frameBuffer->VKColorImages = new VkImageView[frameBuffer->TextureSwapChainLength];
+ frameBuffer->VKDepthImages = new VkImageView[frameBuffer->TextureSwapChainLength];
+ frameBuffer->VKFrameBuffers = new VkFramebuffer[frameBuffer->TextureSwapChainLength];
+ for (uint32_t i = 0; i < frameBuffer->TextureSwapChainLength; i++) {
+ VkImageViewCreateInfo createInfo{};
+ createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ createInfo.image = ((XrSwapchainImageVulkanKHR*)frameBuffer->ColorSwapChainImage)[i].image;
+ createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ createInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+ createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ createInfo.subresourceRange.baseMipLevel = 0;
+ createInfo.subresourceRange.levelCount = 1;
+ createInfo.subresourceRange.baseArrayLayer = 0;
+ createInfo.subresourceRange.layerCount = swapChainCreateInfo.arraySize;
+ if (vkCreateImageView(frameBuffer->VKContext->device, &createInfo, nullptr, &frameBuffer->VKColorImages[i]) != VK_SUCCESS) {
+ ALOGE("failed to create color image view!");
+ return false;
+ }
+
+ createInfo.image = ((XrSwapchainImageVulkanKHR*)frameBuffer->DepthSwapChainImage)[i].image;
+ createInfo.format = VK_FORMAT_D24_UNORM_S8_UINT;
+ createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+ if (vkCreateImageView(frameBuffer->VKContext->device, &createInfo, nullptr, &frameBuffer->VKDepthImages[i]) != VK_SUCCESS) {
+ ALOGE("failed to create depth image view!");
+ return false;
+ }
+
+ // Create the frame buffer.
+ VkImageView attachments[] = { frameBuffer->VKColorImages[i], frameBuffer->VKDepthImages[i] };
+ VkFramebufferCreateInfo framebufferInfo{};
+ framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ framebufferInfo.renderPass = VK_NULL_HANDLE; //TODO:This is probably wrong
+ framebufferInfo.attachmentCount = 2;
+ framebufferInfo.pAttachments = attachments;
+ framebufferInfo.width = width;
+ framebufferInfo.height = height;
+ framebufferInfo.layers = swapChainCreateInfo.arraySize;
+ if (vkCreateFramebuffer(frameBuffer->VKContext->device, &framebufferInfo, nullptr, &frameBuffer->VKFrameBuffers[i]) != VK_SUCCESS) {
+ ALOGE("failed to create framebuffer!");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#endif
+
void ovrFramebuffer_Destroy(ovrFramebuffer* frameBuffer) {
- GL(glDeleteFramebuffers(frameBuffer->TextureSwapChainLength, frameBuffer->FrameBuffers));
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ for (int i = 0; i < (int)frameBuffer->TextureSwapChainLength; i++) {
+ vkDestroyImageView(frameBuffer->VKContext->device, frameBuffer->VKColorImages[i], nullptr);
+ vkDestroyImageView(frameBuffer->VKContext->device, frameBuffer->VKDepthImages[i], nullptr);
+ vkDestroyFramebuffer(frameBuffer->VKContext->device, frameBuffer->VKFrameBuffers[i], nullptr);
+ }
+ delete[] frameBuffer->VKColorImages;
+ delete[] frameBuffer->VKDepthImages;
+ delete[] frameBuffer->VKFrameBuffers;
+ } else {
+#if XR_USE_GRAPHICS_API_OPENGL_ES || XR_USE_GRAPHICS_API_OPENGL
+ GL(glDeleteFramebuffers(frameBuffer->TextureSwapChainLength, frameBuffer->GLFrameBuffers));
+ free(frameBuffer->GLFrameBuffers);
+#endif
+ }
OXR(xrDestroySwapchain(frameBuffer->ColorSwapChain.Handle));
OXR(xrDestroySwapchain(frameBuffer->DepthSwapChain.Handle));
free(frameBuffer->ColorSwapChainImage);
free(frameBuffer->DepthSwapChainImage);
- free(frameBuffer->FrameBuffers);
ovrFramebuffer_Clear(frameBuffer);
}
-void ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer) {
- GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->FrameBuffers[frameBuffer->TextureSwapChainIndex]));
-}
-
-void ovrFramebuffer_SetNone() {
- GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
-}
-
-void ovrFramebuffer_Resolve(ovrFramebuffer* frameBuffer) {
- // Discard the depth buffer, so the tiler won't need to write it back out to memory.
- const GLenum depthAttachment[1] = {GL_DEPTH_ATTACHMENT};
- glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, depthAttachment);
+void* ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer) {
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ return (void *)frameBuffer->VKFrameBuffers[frameBuffer->TextureSwapChainIndex];
+ } else {
+#if XR_USE_GRAPHICS_API_OPENGL_ES || XR_USE_GRAPHICS_API_OPENGL
+ GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->GLFrameBuffers[frameBuffer->TextureSwapChainIndex]));
+#endif
+ return nullptr;
+ }
}
void ovrFramebuffer_Acquire(ovrFramebuffer* frameBuffer) {
@@ -178,6 +336,22 @@ void ovrFramebuffer_Acquire(ovrFramebuffer* frameBuffer) {
waitInfo.timeout * (1E-9));
}
frameBuffer->Acquired = res == XR_SUCCESS;
+
+ ovrFramebuffer_SetCurrent(frameBuffer);
+
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ //TODO:implement
+ } else {
+#if XR_USE_GRAPHICS_API_OPENGL_ES || XR_USE_GRAPHICS_API_OPENGL
+ GL(glEnable( GL_SCISSOR_TEST ));
+ GL(glViewport( 0, 0, frameBuffer->Width, frameBuffer->Height ));
+ GL(glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ));
+ GL(glScissor( 0, 0, frameBuffer->Width, frameBuffer->Height ));
+ GL(glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ));
+ GL(glScissor( 0, 0, 0, 0 ));
+ GL(glDisable( GL_SCISSOR_TEST ));
+#endif
+ }
}
void ovrFramebuffer_Release(ovrFramebuffer* frameBuffer) {
@@ -185,6 +359,18 @@ void ovrFramebuffer_Release(ovrFramebuffer* frameBuffer) {
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, NULL};
OXR(xrReleaseSwapchainImage(frameBuffer->ColorSwapChain.Handle, &releaseInfo));
frameBuffer->Acquired = false;
+
+ // Clear the alpha channel, other way OpenXR would not transfer the framebuffer fully
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ //TODO:implement
+ } else {
+#if XR_USE_GRAPHICS_API_OPENGL_ES || XR_USE_GRAPHICS_API_OPENGL
+ GL(glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE));
+ GL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+ GL(glClear(GL_COLOR_BUFFER_BIT));
+ GL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
+#endif
+ }
}
}
@@ -202,11 +388,19 @@ void ovrRenderer_Clear(ovrRenderer* renderer) {
}
}
-void ovrRenderer_Create(XrSession session, ovrRenderer* renderer, int width, int height, bool multiview) {
+void ovrRenderer_Create(XrSession session, ovrRenderer* renderer, int width, int height, bool multiview, void* vulkanContext) {
renderer->Multiview = multiview;
int instances = renderer->Multiview ? 1 : ovrMaxNumEyes;
for (int i = 0; i < instances; i++) {
- ovrFramebuffer_Create(session, &renderer->FrameBuffer[i], width, height, multiview);
+ if (vulkanContext) {
+ ovrFramebuffer_CreateVK(session, &renderer->FrameBuffer[i], width, height, multiview, vulkanContext);
+ } else {
+#if XR_USE_GRAPHICS_API_OPENGL_ES
+ ovrFramebuffer_CreateGLES(session, &renderer->FrameBuffer[i], width, height, multiview);
+#elif XR_USE_GRAPHICS_API_OPENGL
+ // TODO
+#endif
+ }
}
}
@@ -217,6 +411,21 @@ void ovrRenderer_Destroy(ovrRenderer* renderer) {
}
}
+void ovrRenderer_MouseCursor(ovrRenderer* renderer, int x, int y, int sx, int sy) {
+ if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) {
+ //TODO:implement
+ } else {
+#if XR_USE_GRAPHICS_API_OPENGL_ES || XR_USE_GRAPHICS_API_OPENGL
+ GL(glEnable(GL_SCISSOR_TEST));
+ GL(glScissor(x, y, sx, sy));
+ GL(glViewport(x, y, sx, sy));
+ GL(glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
+ GL(glClear(GL_COLOR_BUFFER_BIT));
+ GL(glDisable(GL_SCISSOR_TEST));
+#endif
+ }
+}
+
/*
================================================================================
@@ -250,6 +459,7 @@ void ovrApp_Destroy(ovrApp* app) {
ovrApp_Clear(app);
}
+
void ovrApp_HandleSessionStateChanges(ovrApp* app, XrSessionState state) {
if (state == XR_SESSION_STATE_READY) {
assert(app->SessionActive == false);
@@ -262,12 +472,10 @@ void ovrApp_HandleSessionStateChanges(ovrApp* app, XrSessionState state) {
XrResult result;
OXR(result = xrBeginSession(app->Session, &sessionBeginInfo));
-
app->SessionActive = (result == XR_SUCCESS);
- // Set session state once we have entered VR mode and have a valid session object.
-#ifdef OPENXR_HAS_PERFORMANCE_EXTENSION
- if (app->SessionActive) {
+#ifdef ANDROID
+ if (app->SessionActive && VR_GetPlatformFlag(VR_PLATFORM_PERFORMANCE_EXT)) {
XrPerfSettingsLevelEXT cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_BOOST_EXT;
XrPerfSettingsLevelEXT gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_BOOST_EXT;
diff --git a/Common/VR/VRFramebuffer.h b/Common/VR/VRFramebuffer.h
index dcedad580f..07a5c19834 100644
--- a/Common/VR/VRFramebuffer.h
+++ b/Common/VR/VRFramebuffer.h
@@ -1,16 +1,15 @@
#pragma once
-#include "VR/VRBase.h"
+#include "VRBase.h"
void ovrApp_Clear(ovrApp* app);
void ovrApp_Destroy(ovrApp* app);
int ovrApp_HandleXrEvents(ovrApp* app);
void ovrFramebuffer_Acquire(ovrFramebuffer* frameBuffer);
-void ovrFramebuffer_Resolve(ovrFramebuffer* frameBuffer);
void ovrFramebuffer_Release(ovrFramebuffer* frameBuffer);
-void ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer);
-void ovrFramebuffer_SetNone();
+void* ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer);
-void ovrRenderer_Create(XrSession session, ovrRenderer* renderer, int width, int height, bool multiview);
+void ovrRenderer_Create(XrSession session, ovrRenderer* renderer, int width, int height, bool multiview, void* vulkanContext);
void ovrRenderer_Destroy(ovrRenderer* renderer);
+void ovrRenderer_MouseCursor(ovrRenderer* renderer, int x, int y, int sx, int sy);
diff --git a/Common/VR/VRInput.cpp b/Common/VR/VRInput.cpp
index 2fc8816f61..dec132ab34 100644
--- a/Common/VR/VRInput.cpp
+++ b/Common/VR/VRInput.cpp
@@ -1,6 +1,5 @@
#include "VRInput.h"
-#include
-#include
+#include
//OpenXR
XrPath leftHandPath;
@@ -38,6 +37,8 @@ XrActionStateVector2f moveJoystickState[2];
float vibration_channel_duration[2] = {0.0f, 0.0f};
float vibration_channel_intensity[2] = {0.0f, 0.0f};
+#if !defined(_WIN32)
+#include
unsigned long sys_timeBase = 0;
int milliseconds(void) {
@@ -47,14 +48,33 @@ int milliseconds(void) {
if (!sys_timeBase) {
sys_timeBase = tp.tv_sec;
- return tp.tv_usec/1000;
+ return tp.tv_usec / 1000;
}
- return (tp.tv_sec - sys_timeBase)*1000 + tp.tv_usec/1000;
+ return (tp.tv_sec - sys_timeBase) * 1000 + tp.tv_usec / 1000;
+}
+#else
+
+static LARGE_INTEGER frequency;
+static double frequencyMult;
+static LARGE_INTEGER startTime;
+
+int milliseconds() {
+ if (frequency.QuadPart == 0) {
+ QueryPerformanceFrequency(&frequency);
+ QueryPerformanceCounter(&startTime);
+ frequencyMult = 1.0 / static_cast(frequency.QuadPart);
+ }
+ LARGE_INTEGER time;
+ QueryPerformanceCounter(&time);
+ double elapsed = static_cast(time.QuadPart - startTime.QuadPart);
+ return (int)(elapsed * frequencyMult * 1000.0);
}
+#endif
+
XrTime ToXrTime(const double timeInSeconds) {
- return (timeInSeconds * 1e9);
+ return (XrTime)(timeInSeconds * 1e9);
}
void INVR_Vibrate( int duration, int chan, float intensity ) {
@@ -67,16 +87,15 @@ void INVR_Vibrate( int duration, int chan, float intensity ) {
if (vibration_channel_duration[channel] == -1.0f && duration != 0.0f)
return;
- vibration_channel_duration[channel] = duration;
+ vibration_channel_duration[channel] = (float)duration;
vibration_channel_intensity[channel] = intensity;
}
}
}
-
void VR_processHaptics() {
float lastFrameTime = 0.0f;
- float timestamp = (float)(milliseconds( ));
+ float timestamp = (float)(milliseconds());
float frametime = timestamp - lastFrameTime;
lastFrameTime = timestamp;
@@ -247,25 +266,27 @@ void IN_VRInit( engine_t *engine ) {
handPoseRightAction = CreateAction(runningActionSet, XR_ACTION_TYPE_POSE_INPUT, "hand_pose_right", NULL, 1, &rightHandPath);
XrPath interactionProfilePath = XR_NULL_PATH;
-#ifdef OPENXR_PLATFORM_QUEST
- OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePath));
-#elif OPENXR_PLATFORM_PICO
- OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/pico/neo3_controller", &interactionProfilePath));
-#endif
+ if (VR_GetPlatformFlag(VR_PLATFORM_CONTROLLER_QUEST)) {
+ OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePath));
+ } else if (VR_GetPlatformFlag(VR_PLATFORM_CONTROLLER_PICO)) {
+ OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/pico/neo3_controller", &interactionProfilePath));
+ }
+
// Map bindings
XrActionSuggestedBinding bindings[32]; // large enough for all profiles
int currBinding = 0;
-#ifdef OPENXR_PLATFORM_QUEST
- bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger");
- bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger");
- bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/menu/click");
-#elif OPENXR_PLATFORM_PICO
- bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger/click");
- bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger/click");
- bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/back/click");
- bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/right/input/back/click");
-#endif
+
+ if (VR_GetPlatformFlag(VR_PLATFORM_CONTROLLER_QUEST)) {
+ bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger");
+ bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger");
+ bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/menu/click");
+ } else if (VR_GetPlatformFlag(VR_PLATFORM_CONTROLLER_PICO)) {
+ bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger/click");
+ bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger/click");
+ bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/back/click");
+ bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/right/input/back/click");
+ }
bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/left/input/x/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonYAction, "/user/hand/left/input/y/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/right/input/a/click");
@@ -438,6 +459,6 @@ XrPosef IN_VRGetPose( int controllerIndex ) {
XrSpaceLocation loc = {};
loc.type = XR_TYPE_SPACE_LOCATION;
XrSpace aimSpace[] = { leftControllerAimSpace, rightControllerAimSpace };
- xrLocateSpace(aimSpace[controllerIndex], engine->appState.CurrentSpace, engine->predictedDisplayTime, &loc);
+ xrLocateSpace(aimSpace[controllerIndex], engine->appState.CurrentSpace, (XrTime)(engine->predictedDisplayTime), &loc);
return loc.pose;
}
diff --git a/Common/VR/VRMath.cpp b/Common/VR/VRMath.cpp
index 204e591fad..d65f13a1a7 100644
--- a/Common/VR/VRMath.cpp
+++ b/Common/VR/VRMath.cpp
@@ -1,3 +1,8 @@
+#define _USE_MATH_DEFINES
+
+#include
+#include
+
#include "VRMath.h"
float ToDegrees(float rad) {
@@ -8,223 +13,18 @@ float ToRadians(float deg) {
return (float)(deg * M_PI / 180.0f);
}
-/*
-================================================================================
+bool IsMatrixIdentity(float* matrix) {
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ float value = matrix[i * 4 + j];
-ovrMatrix4f
-
-================================================================================
-*/
-
-float ovrMatrix4f_Minor(const ovrMatrix4f* m, int r0, int r1, int r2, int c0, int c1, int c2) {
- return m->M[r0][c0] * (m->M[r1][c1] * m->M[r2][c2] - m->M[r2][c1] * m->M[r1][c2]) -
- m->M[r0][c1] * (m->M[r1][c0] * m->M[r2][c2] - m->M[r2][c0] * m->M[r1][c2]) +
- m->M[r0][c2] * (m->M[r1][c0] * m->M[r2][c1] - m->M[r2][c0] * m->M[r1][c1]);
-}
-
-ovrMatrix4f ovrMatrix4f_CreateFromQuaternion(const XrQuaternionf* q) {
- const float ww = q->w * q->w;
- const float xx = q->x * q->x;
- const float yy = q->y * q->y;
- const float zz = q->z * q->z;
-
- ovrMatrix4f out;
- out.M[0][0] = ww + xx - yy - zz;
- out.M[0][1] = 2 * (q->x * q->y - q->w * q->z);
- out.M[0][2] = 2 * (q->x * q->z + q->w * q->y);
- out.M[0][3] = 0;
-
- out.M[1][0] = 2 * (q->x * q->y + q->w * q->z);
- out.M[1][1] = ww - xx + yy - zz;
- out.M[1][2] = 2 * (q->y * q->z - q->w * q->x);
- out.M[1][3] = 0;
-
- out.M[2][0] = 2 * (q->x * q->z - q->w * q->y);
- out.M[2][1] = 2 * (q->y * q->z + q->w * q->x);
- out.M[2][2] = ww - xx - yy + zz;
- out.M[2][3] = 0;
-
- out.M[3][0] = 0;
- out.M[3][1] = 0;
- out.M[3][2] = 0;
- out.M[3][3] = 1;
- return out;
-}
-
-ovrMatrix4f ovrMatrix4f_CreateProjectionFov(
- const float angleLeft,
- const float angleRight,
- const float angleUp,
- const float angleDown,
- const float nearZ,
- const float farZ) {
-
- const float tanAngleLeft = tanf(angleLeft);
- const float tanAngleRight = tanf(angleRight);
-
- const float tanAngleDown = tanf(angleDown);
- const float tanAngleUp = tanf(angleUp);
-
- const float tanAngleWidth = tanAngleRight - tanAngleLeft;
-
- // Set to tanAngleDown - tanAngleUp for a clip space with positive Y
- // down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with
- // positive Y up (OpenGL / D3D / Metal).
- const float tanAngleHeight = tanAngleUp - tanAngleDown;
-
- // Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
- // Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
- const float offsetZ = nearZ;
-
- ovrMatrix4f result;
- if (farZ <= nearZ) {
- // place the far plane at infinity
- result.M[0][0] = 2 / tanAngleWidth;
- result.M[0][1] = 0;
- result.M[0][2] = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
- result.M[0][3] = 0;
-
- result.M[1][0] = 0;
- result.M[1][1] = 2 / tanAngleHeight;
- result.M[1][2] = (tanAngleUp + tanAngleDown) / tanAngleHeight;
- result.M[1][3] = 0;
-
- result.M[2][0] = 0;
- result.M[2][1] = 0;
- result.M[2][2] = -1;
- result.M[2][3] = -(nearZ + offsetZ);
-
- result.M[3][0] = 0;
- result.M[3][1] = 0;
- result.M[3][2] = -1;
- result.M[3][3] = 0;
- } else {
- // normal projection
- result.M[0][0] = 2 / tanAngleWidth;
- result.M[0][1] = 0;
- result.M[0][2] = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
- result.M[0][3] = 0;
-
- result.M[1][0] = 0;
- result.M[1][1] = 2 / tanAngleHeight;
- result.M[1][2] = (tanAngleUp + tanAngleDown) / tanAngleHeight;
- result.M[1][3] = 0;
-
- result.M[2][0] = 0;
- result.M[2][1] = 0;
- result.M[2][2] = -(farZ + offsetZ) / (farZ - nearZ);
- result.M[2][3] = -(farZ * (nearZ + offsetZ)) / (farZ - nearZ);
-
- result.M[3][0] = 0;
- result.M[3][1] = 0;
- result.M[3][2] = -1;
- result.M[3][3] = 0;
+ // Other number than zero on non-diagonale
+ if ((i != j) && (fabs(value) > EPSILON)) return false;
+ // Other number than one on diagonale
+ if ((i == j) && (fabs(value - 1.0f) > EPSILON)) return false;
+ }
}
- return result;
-}
-
-ovrMatrix4f ovrMatrix4f_CreateRotation(const float radiansX, const float radiansY, const float radiansZ) {
- const float sinX = sinf(radiansX);
- const float cosX = cosf(radiansX);
- const ovrMatrix4f rotationX = {
- {{1, 0, 0, 0}, {0, cosX, -sinX, 0}, {0, sinX, cosX, 0}, {0, 0, 0, 1}}};
- const float sinY = sinf(radiansY);
- const float cosY = cosf(radiansY);
- const ovrMatrix4f rotationY = {
- {{cosY, 0, sinY, 0}, {0, 1, 0, 0}, {-sinY, 0, cosY, 0}, {0, 0, 0, 1}}};
- const float sinZ = sinf(radiansZ);
- const float cosZ = cosf(radiansZ);
- const ovrMatrix4f rotationZ = {
- {{cosZ, -sinZ, 0, 0}, {sinZ, cosZ, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}};
- const ovrMatrix4f rotationXY = ovrMatrix4f_Multiply(&rotationY, &rotationX);
- return ovrMatrix4f_Multiply(&rotationZ, &rotationXY);
-}
-
-ovrMatrix4f ovrMatrix4f_Inverse(const ovrMatrix4f* m) {
- const float rcpDet = 1.0f /
- (m->M[0][0] * ovrMatrix4f_Minor(m, 1, 2, 3, 1, 2, 3) -
- m->M[0][1] * ovrMatrix4f_Minor(m, 1, 2, 3, 0, 2, 3) +
- m->M[0][2] * ovrMatrix4f_Minor(m, 1, 2, 3, 0, 1, 3) -
- m->M[0][3] * ovrMatrix4f_Minor(m, 1, 2, 3, 0, 1, 2));
- ovrMatrix4f out;
- out.M[0][0] = ovrMatrix4f_Minor(m, 1, 2, 3, 1, 2, 3) * rcpDet;
- out.M[0][1] = -ovrMatrix4f_Minor(m, 0, 2, 3, 1, 2, 3) * rcpDet;
- out.M[0][2] = ovrMatrix4f_Minor(m, 0, 1, 3, 1, 2, 3) * rcpDet;
- out.M[0][3] = -ovrMatrix4f_Minor(m, 0, 1, 2, 1, 2, 3) * rcpDet;
- out.M[1][0] = -ovrMatrix4f_Minor(m, 1, 2, 3, 0, 2, 3) * rcpDet;
- out.M[1][1] = ovrMatrix4f_Minor(m, 0, 2, 3, 0, 2, 3) * rcpDet;
- out.M[1][2] = -ovrMatrix4f_Minor(m, 0, 1, 3, 0, 2, 3) * rcpDet;
- out.M[1][3] = ovrMatrix4f_Minor(m, 0, 1, 2, 0, 2, 3) * rcpDet;
- out.M[2][0] = ovrMatrix4f_Minor(m, 1, 2, 3, 0, 1, 3) * rcpDet;
- out.M[2][1] = -ovrMatrix4f_Minor(m, 0, 2, 3, 0, 1, 3) * rcpDet;
- out.M[2][2] = ovrMatrix4f_Minor(m, 0, 1, 3, 0, 1, 3) * rcpDet;
- out.M[2][3] = -ovrMatrix4f_Minor(m, 0, 1, 2, 0, 1, 3) * rcpDet;
- out.M[3][0] = -ovrMatrix4f_Minor(m, 1, 2, 3, 0, 1, 2) * rcpDet;
- out.M[3][1] = ovrMatrix4f_Minor(m, 0, 2, 3, 0, 1, 2) * rcpDet;
- out.M[3][2] = -ovrMatrix4f_Minor(m, 0, 1, 3, 0, 1, 2) * rcpDet;
- out.M[3][3] = ovrMatrix4f_Minor(m, 0, 1, 2, 0, 1, 2) * rcpDet;
- return out;
-}
-
-/// Use left-multiplication to accumulate transformations.
-ovrMatrix4f ovrMatrix4f_Multiply(const ovrMatrix4f* a, const ovrMatrix4f* b) {
- ovrMatrix4f out;
- out.M[0][0] = a->M[0][0] * b->M[0][0] + a->M[0][1] * b->M[1][0] + a->M[0][2] * b->M[2][0] +
- a->M[0][3] * b->M[3][0];
- out.M[1][0] = a->M[1][0] * b->M[0][0] + a->M[1][1] * b->M[1][0] + a->M[1][2] * b->M[2][0] +
- a->M[1][3] * b->M[3][0];
- out.M[2][0] = a->M[2][0] * b->M[0][0] + a->M[2][1] * b->M[1][0] + a->M[2][2] * b->M[2][0] +
- a->M[2][3] * b->M[3][0];
- out.M[3][0] = a->M[3][0] * b->M[0][0] + a->M[3][1] * b->M[1][0] + a->M[3][2] * b->M[2][0] +
- a->M[3][3] * b->M[3][0];
-
- out.M[0][1] = a->M[0][0] * b->M[0][1] + a->M[0][1] * b->M[1][1] + a->M[0][2] * b->M[2][1] +
- a->M[0][3] * b->M[3][1];
- out.M[1][1] = a->M[1][0] * b->M[0][1] + a->M[1][1] * b->M[1][1] + a->M[1][2] * b->M[2][1] +
- a->M[1][3] * b->M[3][1];
- out.M[2][1] = a->M[2][0] * b->M[0][1] + a->M[2][1] * b->M[1][1] + a->M[2][2] * b->M[2][1] +
- a->M[2][3] * b->M[3][1];
- out.M[3][1] = a->M[3][0] * b->M[0][1] + a->M[3][1] * b->M[1][1] + a->M[3][2] * b->M[2][1] +
- a->M[3][3] * b->M[3][1];
-
- out.M[0][2] = a->M[0][0] * b->M[0][2] + a->M[0][1] * b->M[1][2] + a->M[0][2] * b->M[2][2] +
- a->M[0][3] * b->M[3][2];
- out.M[1][2] = a->M[1][0] * b->M[0][2] + a->M[1][1] * b->M[1][2] + a->M[1][2] * b->M[2][2] +
- a->M[1][3] * b->M[3][2];
- out.M[2][2] = a->M[2][0] * b->M[0][2] + a->M[2][1] * b->M[1][2] + a->M[2][2] * b->M[2][2] +
- a->M[2][3] * b->M[3][2];
- out.M[3][2] = a->M[3][0] * b->M[0][2] + a->M[3][1] * b->M[1][2] + a->M[3][2] * b->M[2][2] +
- a->M[3][3] * b->M[3][2];
-
- out.M[0][3] = a->M[0][0] * b->M[0][3] + a->M[0][1] * b->M[1][3] + a->M[0][2] * b->M[2][3] +
- a->M[0][3] * b->M[3][3];
- out.M[1][3] = a->M[1][0] * b->M[0][3] + a->M[1][1] * b->M[1][3] + a->M[1][2] * b->M[2][3] +
- a->M[1][3] * b->M[3][3];
- out.M[2][3] = a->M[2][0] * b->M[0][3] + a->M[2][1] * b->M[1][3] + a->M[2][2] * b->M[2][3] +
- a->M[2][3] * b->M[3][3];
- out.M[3][3] = a->M[3][0] * b->M[0][3] + a->M[3][1] * b->M[1][3] + a->M[3][2] * b->M[2][3] +
- a->M[3][3] * b->M[3][3];
- return out;
-}
-
-XrVector3f ovrMatrix4f_ToEulerAngles(const ovrMatrix4f* m) {
- XrVector4f v1 = {0, 0, -1, 0};
- XrVector4f v2 = {1, 0, 0, 0};
- XrVector4f v3 = {0, 1, 0, 0};
-
- XrVector4f forwardInVRSpace = XrVector4f_MultiplyMatrix4f(m, &v1);
- XrVector4f rightInVRSpace = XrVector4f_MultiplyMatrix4f(m, &v2);
- XrVector4f upInVRSpace = XrVector4f_MultiplyMatrix4f(m, &v3);
-
- XrVector3f forward = {-forwardInVRSpace.z, -forwardInVRSpace.x, forwardInVRSpace.y};
- XrVector3f right = {-rightInVRSpace.z, -rightInVRSpace.x, rightInVRSpace.y};
- XrVector3f up = {-upInVRSpace.z, -upInVRSpace.x, upInVRSpace.y};
-
- XrVector3f forwardNormal = XrVector3f_Normalized(forward);
- XrVector3f rightNormal = XrVector3f_Normalized(right);
- XrVector3f upNormal = XrVector3f_Normalized(up);
-
- return XrVector3f_GetAnglesFromVectors(forwardNormal, rightNormal, upNormal);
+ return true;
}
/*
@@ -236,15 +36,15 @@ XrPosef
*/
XrPosef XrPosef_Identity() {
- XrPosef r;
- r.orientation.x = 0;
- r.orientation.y = 0;
- r.orientation.z = 0;
- r.orientation.w = 1;
- r.position.x = 0;
- r.position.y = 0;
- r.position.z = 0;
- return r;
+ XrPosef r;
+ r.orientation.x = 0;
+ r.orientation.y = 0;
+ r.orientation.z = 0;
+ r.orientation.w = 1;
+ r.position.x = 0;
+ r.position.y = 0;
+ r.position.z = 0;
+ return r;
}
XrPosef XrPosef_Inverse(const XrPosef a) {
@@ -254,18 +54,6 @@ XrPosef XrPosef_Inverse(const XrPosef a) {
return b;
}
-XrPosef XrPosef_Multiply(const XrPosef a, const XrPosef b) {
- XrPosef c;
- c.orientation = XrQuaternionf_Multiply(a.orientation, b.orientation);
- c.position = XrPosef_Transform(a, b.position);
- return c;
-}
-
-XrVector3f XrPosef_Transform(const XrPosef a, const XrVector3f v) {
- XrVector3f r0 = XrQuaternionf_Rotate(a.orientation, v);
- return XrVector3f_Add(r0, a.position);
-}
-
/*
================================================================================
@@ -325,8 +113,56 @@ XrVector3f XrQuaternionf_Rotate(const XrQuaternionf a, const XrVector3f v) {
}
XrVector3f XrQuaternionf_ToEulerAngles(const XrQuaternionf q) {
- ovrMatrix4f m = ovrMatrix4f_CreateFromQuaternion( &q );
- return ovrMatrix4f_ToEulerAngles(&m);
+ float M[16];
+ XrQuaternionf_ToMatrix4f( &q, M);
+
+ XrVector4f v1 = {0, 0, -1, 0};
+ XrVector4f v2 = {1, 0, 0, 0};
+ XrVector4f v3 = {0, 1, 0, 0};
+
+ XrVector4f forwardInVRSpace = XrVector4f_MultiplyMatrix4f(M, &v1);
+ XrVector4f rightInVRSpace = XrVector4f_MultiplyMatrix4f(M, &v2);
+ XrVector4f upInVRSpace = XrVector4f_MultiplyMatrix4f(M, &v3);
+
+ XrVector3f forward = {-forwardInVRSpace.z, -forwardInVRSpace.x, forwardInVRSpace.y};
+ XrVector3f right = {-rightInVRSpace.z, -rightInVRSpace.x, rightInVRSpace.y};
+ XrVector3f up = {-upInVRSpace.z, -upInVRSpace.x, upInVRSpace.y};
+
+ XrVector3f forwardNormal = XrVector3f_Normalized(forward);
+ XrVector3f rightNormal = XrVector3f_Normalized(right);
+ XrVector3f upNormal = XrVector3f_Normalized(up);
+
+ return XrVector3f_GetAnglesFromVectors(forwardNormal, rightNormal, upNormal);
+}
+
+void XrQuaternionf_ToMatrix4f(const XrQuaternionf* q, float* matrix) {
+ const float ww = q->w * q->w;
+ const float xx = q->x * q->x;
+ const float yy = q->y * q->y;
+ const float zz = q->z * q->z;
+
+ float M[4][4];
+ M[0][0] = ww + xx - yy - zz;
+ M[0][1] = 2 * (q->x * q->y - q->w * q->z);
+ M[0][2] = 2 * (q->x * q->z + q->w * q->y);
+ M[0][3] = 0;
+
+ M[1][0] = 2 * (q->x * q->y + q->w * q->z);
+ M[1][1] = ww - xx + yy - zz;
+ M[1][2] = 2 * (q->y * q->z - q->w * q->x);
+ M[1][3] = 0;
+
+ M[2][0] = 2 * (q->x * q->z - q->w * q->y);
+ M[2][1] = 2 * (q->y * q->z + q->w * q->x);
+ M[2][2] = ww - xx - yy + zz;
+ M[2][3] = 0;
+
+ M[3][0] = 0;
+ M[3][1] = 0;
+ M[3][2] = 0;
+ M[3][3] = 1;
+
+ memcpy(matrix, &M, sizeof(float) * 16);
}
/*
@@ -337,22 +173,10 @@ XrVector3f, XrVector4f
================================================================================
*/
-float XrVector3f_Length(const XrVector3f v) {
- return sqrtf(XrVector3f_LengthSquared(v));
-}
-
float XrVector3f_LengthSquared(const XrVector3f v) {
return v.x * v.x + v.y * v.y + v.z * v.z;;
}
-XrVector3f XrVector3f_Add(const XrVector3f u, const XrVector3f v) {
- XrVector3f w;
- w.x = u.x + v.x;
- w.y = u.y + v.y;
- w.z = u.z + v.z;
- return w;
-}
-
XrVector3f XrVector3f_GetAnglesFromVectors(const XrVector3f forward, const XrVector3f right, const XrVector3f up) {
float sr, sp, sy, cr, cp, cy;
@@ -393,7 +217,7 @@ XrVector3f XrVector3f_GetAnglesFromVectors(const XrVector3f forward, const XrVec
}
XrVector3f XrVector3f_Normalized(const XrVector3f v) {
- float rcpLen = 1.0f / XrVector3f_Length(v);
+ float rcpLen = 1.0f / sqrtf(XrVector3f_LengthSquared(v));
return XrVector3f_ScalarMultiply(v, rcpLen);
}
@@ -405,11 +229,14 @@ XrVector3f XrVector3f_ScalarMultiply(const XrVector3f v, float scale) {
return u;
}
-XrVector4f XrVector4f_MultiplyMatrix4f(const ovrMatrix4f* a, const XrVector4f* v) {
+XrVector4f XrVector4f_MultiplyMatrix4f(const float* m, const XrVector4f* v) {
+ float M[4][4];
+ memcpy(&M, m, sizeof(float) * 16);
+
XrVector4f out;
- out.x = a->M[0][0] * v->x + a->M[0][1] * v->y + a->M[0][2] * v->z + a->M[0][3] * v->w;
- out.y = a->M[1][0] * v->x + a->M[1][1] * v->y + a->M[1][2] * v->z + a->M[1][3] * v->w;
- out.z = a->M[2][0] * v->x + a->M[2][1] * v->y + a->M[2][2] * v->z + a->M[2][3] * v->w;
- out.w = a->M[3][0] * v->x + a->M[3][1] * v->y + a->M[3][2] * v->z + a->M[3][3] * v->w;
+ out.x = M[0][0] * v->x + M[0][1] * v->y + M[0][2] * v->z + M[0][3] * v->w;
+ out.y = M[1][0] * v->x + M[1][1] * v->y + M[1][2] * v->z + M[1][3] * v->w;
+ out.z = M[2][0] * v->x + M[2][1] * v->y + M[2][2] * v->z + M[2][3] * v->w;
+ out.w = M[3][0] * v->x + M[3][1] * v->y + M[3][2] * v->z + M[3][3] * v->w;
return out;
}
diff --git a/Common/VR/VRMath.h b/Common/VR/VRMath.h
index eb801ac73f..4f6e0610c9 100644
--- a/Common/VR/VRMath.h
+++ b/Common/VR/VRMath.h
@@ -7,27 +7,13 @@
#define EPSILON 0.001f
#endif
-typedef struct {
- float M[4][4];
-} ovrMatrix4f;
-
float ToDegrees(float rad);
float ToRadians(float deg);
-
-// ovrMatrix4f
-float ovrMatrix4f_Minor(const ovrMatrix4f* m, int r0, int r1, int r2, int c0, int c1, int c2);
-ovrMatrix4f ovrMatrix4f_CreateFromQuaternion(const XrQuaternionf* q);
-ovrMatrix4f ovrMatrix4f_CreateProjectionFov(const float angleLeft, const float angleRight, const float angleUp, const float angleDown, const float nearZ, const float farZ);
-ovrMatrix4f ovrMatrix4f_CreateRotation(const float radiansX, const float radiansY, const float radiansZ);
-ovrMatrix4f ovrMatrix4f_Inverse(const ovrMatrix4f* m);
-ovrMatrix4f ovrMatrix4f_Multiply(const ovrMatrix4f* a, const ovrMatrix4f* b);
-XrVector3f ovrMatrix4f_ToEulerAngles(const ovrMatrix4f* m);
+bool IsMatrixIdentity(float* matrix);
// XrPosef
XrPosef XrPosef_Identity();
XrPosef XrPosef_Inverse(const XrPosef a);
-XrPosef XrPosef_Multiply(const XrPosef a, const XrPosef b);
-XrVector3f XrPosef_Transform(const XrPosef a, const XrVector3f v);
// XrQuaternionf
XrQuaternionf XrQuaternionf_CreateFromVectorAngle(const XrVector3f axis, const float angle);
@@ -35,12 +21,11 @@ XrQuaternionf XrQuaternionf_Inverse(const XrQuaternionf q);
XrQuaternionf XrQuaternionf_Multiply(const XrQuaternionf a, const XrQuaternionf b);
XrVector3f XrQuaternionf_Rotate(const XrQuaternionf a, const XrVector3f v);
XrVector3f XrQuaternionf_ToEulerAngles(const XrQuaternionf q);
+void XrQuaternionf_ToMatrix4f(const XrQuaternionf* q, float* m);
// XrVector3f, XrVector4f
-float XrVector3f_Length(const XrVector3f v);
float XrVector3f_LengthSquared(const XrVector3f v);
-XrVector3f XrVector3f_Add(const XrVector3f u, const XrVector3f v);
XrVector3f XrVector3f_GetAnglesFromVectors(const XrVector3f forward, const XrVector3f right, const XrVector3f up);
XrVector3f XrVector3f_Normalized(const XrVector3f v);
XrVector3f XrVector3f_ScalarMultiply(const XrVector3f v, float scale);
-XrVector4f XrVector4f_MultiplyMatrix4f(const ovrMatrix4f* a, const XrVector4f* v);
+XrVector4f XrVector4f_MultiplyMatrix4f(const float* m, const XrVector4f* v);
diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp
index 22668fb3f3..5de7cb4055 100644
--- a/Common/VR/VRRenderer.cpp
+++ b/Common/VR/VRRenderer.cpp
@@ -1,25 +1,24 @@
+#define _USE_MATH_DEFINES
+#include