mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 03:25:16 +00:00
Merge branch 'master' into oskss
This commit is contained in:
commit
9078932e99
37 changed files with 1980 additions and 1533 deletions
31
.github/workflows/rpcs3.yml
vendored
31
.github/workflows/rpcs3.yml
vendored
|
@ -1,5 +1,8 @@
|
|||
name: Build RPCS3
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
|
@ -33,6 +36,8 @@ jobs:
|
|||
docker_img: "rpcs3/rpcs3-ci-jammy:1.1"
|
||||
build_sh: "/rpcs3/.ci/build-linux.sh"
|
||||
compiler: clang
|
||||
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
|
||||
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux"
|
||||
- os: ubuntu-24.04
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:1.1"
|
||||
build_sh: "/rpcs3/.ci/build-linux.sh"
|
||||
|
@ -92,8 +97,7 @@ jobs:
|
|||
github.event_name != 'pull_request' &&
|
||||
github.repository == 'RPCS3/rpcs3' &&
|
||||
github.ref == 'refs/heads/master' &&
|
||||
matrix.compiler == 'clang' &&
|
||||
runner.arch == 'ARM64'
|
||||
matrix.compiler == 'clang'
|
||||
env:
|
||||
RPCS3_TOKEN: ${{ secrets.RPCS3_TOKEN }}
|
||||
run: |
|
||||
|
@ -120,6 +124,8 @@ jobs:
|
|||
CCACHE_INODECACHE: 'true'
|
||||
CCACHE_SLOPPINESS: 'time_macros'
|
||||
DEPS_CACHE_DIR: ./dependency_cache
|
||||
UPLOAD_COMMIT_HASH: 7d09e3be30805911226241afbb14f8cdc2eb054e
|
||||
UPLOAD_REPO_FULL_NAME: "RPCS3/rpcs3-binaries-win"
|
||||
steps:
|
||||
|
||||
- name: Checkout repository
|
||||
|
@ -128,12 +134,12 @@ jobs:
|
|||
fetch-depth: 0
|
||||
|
||||
- name: Setup env
|
||||
shell: pwsh
|
||||
run: |
|
||||
echo "QTDIR=C:\Qt\${{ env.QT_VER }}\${{ env.QT_VER_MSVC }}_64" >> ${{ github.env }}
|
||||
echo "VULKAN_SDK=C:\VulkanSDK\${{ env.VULKAN_VER }}" >> ${{ github.env }}
|
||||
|
||||
- name: Get Cache Keys
|
||||
shell: bash
|
||||
run: .ci/get_keys-windows.sh
|
||||
|
||||
- name: Setup Build Ccache
|
||||
|
@ -151,11 +157,9 @@ jobs:
|
|||
restore-keys: ${{ runner.os }}-${{ env.COMPILER }}-
|
||||
|
||||
- name: Download and unpack dependencies
|
||||
shell: bash
|
||||
run: .ci/setup-windows.sh
|
||||
|
||||
- name: Export Variables
|
||||
shell: bash
|
||||
run: |
|
||||
while IFS='=' read -r key val; do
|
||||
# Skip lines that are empty or start with '#'
|
||||
|
@ -167,24 +171,27 @@ jobs:
|
|||
uses: microsoft/setup-msbuild@main
|
||||
|
||||
- name: Compile RPCS3
|
||||
shell: pwsh
|
||||
run: msbuild rpcs3.sln /p:Configuration=Release /p:Platform=x64 /p:CLToolPath=${{ env.CCACHE_BIN_DIR }} /p:UseMultiToolTask=true /p:CustomAfterMicrosoftCommonTargets="${{ github.workspace }}\buildfiles\msvc\ci_no_debug_info.targets"
|
||||
|
||||
- name: Pack up build artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p "${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}"
|
||||
.ci/deploy-windows.sh
|
||||
|
||||
- name: Upload artifacts (7z)
|
||||
#TODO: Upload artifact to release repository
|
||||
#condition for release
|
||||
#if: |
|
||||
# github.event_name != 'pull_request' &&
|
||||
# github.repository == 'RPCS3/rpcs3' &&
|
||||
# github.ref == 'refs/heads/master'
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: RPCS3 for Windows (MSVC)
|
||||
path: ${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }}
|
||||
compression-level: 0
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Deploy master build to GitHub Releases
|
||||
if: |
|
||||
github.event_name != 'pull_request' &&
|
||||
github.repository == 'RPCS3/rpcs3' &&
|
||||
github.ref == 'refs/heads/master'
|
||||
env:
|
||||
RPCS3_TOKEN: ${{ secrets.RPCS3_TOKEN }}
|
||||
run: .ci/github-upload.sh
|
||||
|
|
|
@ -10,131 +10,131 @@ pr:
|
|||
include:
|
||||
- master
|
||||
jobs:
|
||||
- job: Linux_Build
|
||||
strategy:
|
||||
matrix:
|
||||
Clang:
|
||||
COMPILER: clang
|
||||
GCC:
|
||||
COMPILER: gcc
|
||||
variables:
|
||||
CCACHE_DIR: $(Pipeline.Workspace)/ccache
|
||||
CI_HAS_ARTIFACTS: true
|
||||
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
|
||||
UPLOAD_REPO_FULL_NAME: "RPCS3/rpcs3-binaries-linux"
|
||||
DEPLOY_APPIMAGE: true
|
||||
APPDIR: "/rpcs3/build/appdir"
|
||||
ARTDIR: "/root/artifacts"
|
||||
RELEASE_MESSAGE: "/rpcs3/GitHubReleaseMessage.txt"
|
||||
# - job: Linux_Build
|
||||
# strategy:
|
||||
# matrix:
|
||||
# Clang:
|
||||
# COMPILER: clang
|
||||
# GCC:
|
||||
# COMPILER: gcc
|
||||
# variables:
|
||||
# CCACHE_DIR: $(Pipeline.Workspace)/ccache
|
||||
# CI_HAS_ARTIFACTS: true
|
||||
# UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
|
||||
# UPLOAD_REPO_FULL_NAME: "RPCS3/rpcs3-binaries-linux"
|
||||
# DEPLOY_APPIMAGE: true
|
||||
# APPDIR: "/rpcs3/build/appdir"
|
||||
# ARTDIR: "/root/artifacts"
|
||||
# RELEASE_MESSAGE: "/rpcs3/GitHubReleaseMessage.txt"
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
# pool:
|
||||
# vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: ccache | $(Agent.OS) | $(COMPILER) | $(Build.SourceVersion)
|
||||
restoreKeys: |
|
||||
ccache | $(Agent.OS) | $(COMPILER)
|
||||
path: $(CCACHE_DIR)
|
||||
displayName: ccache
|
||||
# steps:
|
||||
# - task: Cache@2
|
||||
# inputs:
|
||||
# key: ccache | $(Agent.OS) | $(COMPILER) | $(Build.SourceVersion)
|
||||
# restoreKeys: |
|
||||
# ccache | $(Agent.OS) | $(COMPILER)
|
||||
# path: $(CCACHE_DIR)
|
||||
# displayName: ccache
|
||||
|
||||
- bash: |
|
||||
docker pull --quiet rpcs3/rpcs3-ci-jammy:1.1
|
||||
docker run \
|
||||
-v $(pwd):/rpcs3 \
|
||||
--env-file .ci/docker.env \
|
||||
-v $CCACHE_DIR:/root/.ccache \
|
||||
-v $BUILD_ARTIFACTSTAGINGDIRECTORY:/root/artifacts \
|
||||
rpcs3/rpcs3-ci-jammy:1.1 \
|
||||
/rpcs3/.ci/build-linux.sh
|
||||
displayName: Docker setup and build
|
||||
# - bash: |
|
||||
# docker pull --quiet rpcs3/rpcs3-ci-jammy:1.1
|
||||
# docker run \
|
||||
# -v $(pwd):/rpcs3 \
|
||||
# --env-file .ci/docker.env \
|
||||
# -v $CCACHE_DIR:/root/.ccache \
|
||||
# -v $BUILD_ARTIFACTSTAGINGDIRECTORY:/root/artifacts \
|
||||
# rpcs3/rpcs3-ci-jammy:1.1 \
|
||||
# /rpcs3/.ci/build-linux.sh
|
||||
# displayName: Docker setup and build
|
||||
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
condition: succeeded()
|
||||
artifact: RPCS3 for Linux ($(COMPILER))
|
||||
# - publish: $(Build.ArtifactStagingDirectory)
|
||||
# condition: succeeded()
|
||||
# artifact: RPCS3 for Linux ($(COMPILER))
|
||||
|
||||
- bash: |
|
||||
COMM_TAG=$(awk '/version{.*}/ { printf("%d.%d.%d", $5, $6, $7) }' ./rpcs3/rpcs3_version.cpp)
|
||||
COMM_COUNT=$(git rev-list --count HEAD)
|
||||
COMM_HASH=$(git rev-parse --short=8 HEAD)
|
||||
# - bash: |
|
||||
# COMM_TAG=$(awk '/version{.*}/ { printf("%d.%d.%d", $5, $6, $7) }' ./rpcs3/rpcs3_version.cpp)
|
||||
# COMM_COUNT=$(git rev-list --count HEAD)
|
||||
# COMM_HASH=$(git rev-parse --short=8 HEAD)
|
||||
|
||||
export AVVER="${COMM_TAG}-${COMM_COUNT}"
|
||||
# export AVVER="${COMM_TAG}-${COMM_COUNT}"
|
||||
|
||||
.ci/github-upload.sh
|
||||
condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.Repository.Name'], 'RPCS3/rpcs3'), eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['COMPILER'], 'clang'))
|
||||
displayName: Push build to GitHub
|
||||
env:
|
||||
RPCS3_TOKEN: $(RPCS3-Token)
|
||||
# .ci/github-upload.sh
|
||||
# condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.Repository.Name'], 'RPCS3/rpcs3'), eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['COMPILER'], 'clang'))
|
||||
# displayName: Push build to GitHub
|
||||
# env:
|
||||
# RPCS3_TOKEN: $(RPCS3-Token)
|
||||
|
||||
- job: Windows_Build
|
||||
variables:
|
||||
COMPILER: msvc
|
||||
QT_VER_MAIN: '6'
|
||||
QT_VER: '6.8.2'
|
||||
QT_VER_MSVC: 'msvc2022'
|
||||
QT_DATE: '202501260838'
|
||||
QTDIR: C:\Qt\$(QT_VER)\$(QT_VER_MSVC)_64
|
||||
VULKAN_VER: '1.3.268.0'
|
||||
VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5'
|
||||
VULKAN_SDK: C:\VulkanSDK\$(VULKAN_VER)
|
||||
CCACHE_SHA: '6252f081876a9a9f700fae13a5aec5d0d486b28261d7f1f72ac11c7ad9df4da9'
|
||||
CCACHE_BIN_DIR: 'C:\ccache_bin'
|
||||
CCACHE_DIR: 'C:\ccache'
|
||||
CCACHE_INODECACHE: 'true'
|
||||
CCACHE_SLOPPINESS: 'time_macros'
|
||||
DEPS_CACHE_DIR: ./dependency_cache
|
||||
UPLOAD_COMMIT_HASH: 7d09e3be30805911226241afbb14f8cdc2eb054e
|
||||
UPLOAD_REPO_FULL_NAME: "RPCS3/rpcs3-binaries-win"
|
||||
# - job: Windows_Build
|
||||
# variables:
|
||||
# COMPILER: msvc
|
||||
# QT_VER_MAIN: '6'
|
||||
# QT_VER: '6.8.2'
|
||||
# QT_VER_MSVC: 'msvc2022'
|
||||
# QT_DATE: '202501260838'
|
||||
# QTDIR: C:\Qt\$(QT_VER)\$(QT_VER_MSVC)_64
|
||||
# VULKAN_VER: '1.3.268.0'
|
||||
# VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5'
|
||||
# VULKAN_SDK: C:\VulkanSDK\$(VULKAN_VER)
|
||||
# CCACHE_SHA: '6252f081876a9a9f700fae13a5aec5d0d486b28261d7f1f72ac11c7ad9df4da9'
|
||||
# CCACHE_BIN_DIR: 'C:\ccache_bin'
|
||||
# CCACHE_DIR: 'C:\ccache'
|
||||
# CCACHE_INODECACHE: 'true'
|
||||
# CCACHE_SLOPPINESS: 'time_macros'
|
||||
# DEPS_CACHE_DIR: ./dependency_cache
|
||||
# UPLOAD_COMMIT_HASH: 7d09e3be30805911226241afbb14f8cdc2eb054e
|
||||
# UPLOAD_REPO_FULL_NAME: "RPCS3/rpcs3-binaries-win"
|
||||
|
||||
pool:
|
||||
vmImage: "windows-latest"
|
||||
# pool:
|
||||
# vmImage: "windows-latest"
|
||||
|
||||
steps:
|
||||
- bash: .ci/get_keys-windows.sh
|
||||
displayName: Get Cache Keys
|
||||
# steps:
|
||||
# - bash: .ci/get_keys-windows.sh
|
||||
# displayName: Get Cache Keys
|
||||
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: ccache | $(Agent.OS) | $(COMPILER) | "$(Build.SourceVersion)"
|
||||
path: $(CCACHE_DIR)
|
||||
restoreKeys:
|
||||
ccache | $(Agent.OS) | $(COMPILER)
|
||||
displayName: Build Ccache
|
||||
# - task: Cache@2
|
||||
# inputs:
|
||||
# key: ccache | $(Agent.OS) | $(COMPILER) | "$(Build.SourceVersion)"
|
||||
# path: $(CCACHE_DIR)
|
||||
# restoreKeys:
|
||||
# ccache | $(Agent.OS) | $(COMPILER)
|
||||
# displayName: Build Ccache
|
||||
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: $(Agent.OS) | $(COMPILER) | "$(QT_VER)" | $(VULKAN_SDK_SHA) | $(CCACHE_SHA) | llvm.lock | glslang.lock
|
||||
path: $(DEPS_CACHE_DIR)
|
||||
displayName: Dependencies Cache
|
||||
# - task: Cache@2
|
||||
# inputs:
|
||||
# key: $(Agent.OS) | $(COMPILER) | "$(QT_VER)" | $(VULKAN_SDK_SHA) | $(CCACHE_SHA) | llvm.lock | glslang.lock
|
||||
# path: $(DEPS_CACHE_DIR)
|
||||
# displayName: Dependencies Cache
|
||||
|
||||
- bash: .ci/setup-windows.sh
|
||||
displayName: Download and unpack dependencies
|
||||
# - bash: .ci/setup-windows.sh
|
||||
# displayName: Download and unpack dependencies
|
||||
|
||||
- bash: .ci/export-azure-vars.sh
|
||||
displayName: Export Variables
|
||||
# - bash: .ci/export-azure-vars.sh
|
||||
# displayName: Export Variables
|
||||
|
||||
- task: VSBuild@1
|
||||
inputs:
|
||||
solution: 'rpcs3.sln'
|
||||
maximumCpuCount: true
|
||||
platform: x64
|
||||
configuration: 'Release'
|
||||
msbuildArgs: /p:CLToolPath=$(CCACHE_BIN_DIR) /p:UseMultiToolTask=true /p:CustomAfterMicrosoftCommonTargets="$(Build.SourcesDirectory)\buildfiles\msvc\ci_no_debug_info.targets"
|
||||
displayName: Compile RPCS3
|
||||
# - task: VSBuild@1
|
||||
# inputs:
|
||||
# solution: 'rpcs3.sln'
|
||||
# maximumCpuCount: true
|
||||
# platform: x64
|
||||
# configuration: 'Release'
|
||||
# msbuildArgs: /p:CLToolPath=$(CCACHE_BIN_DIR) /p:UseMultiToolTask=true /p:CustomAfterMicrosoftCommonTargets="$(Build.SourcesDirectory)\buildfiles\msvc\ci_no_debug_info.targets"
|
||||
# displayName: Compile RPCS3
|
||||
|
||||
- bash: .ci/deploy-windows.sh
|
||||
displayName: Pack up build artifacts
|
||||
# - bash: .ci/deploy-windows.sh
|
||||
# displayName: Pack up build artifacts
|
||||
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
condition: succeeded()
|
||||
artifact: RPCS3 for Windows
|
||||
# - publish: $(Build.ArtifactStagingDirectory)
|
||||
# condition: succeeded()
|
||||
# artifact: RPCS3 for Windows
|
||||
|
||||
- bash: .ci/github-upload.sh
|
||||
condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.Repository.Name'], 'RPCS3/rpcs3'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
displayName: Push build to GitHub
|
||||
env:
|
||||
RPCS3_TOKEN: $(RPCS3-Token)
|
||||
# - bash: .ci/github-upload.sh
|
||||
# condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.Repository.Name'], 'RPCS3/rpcs3'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
# displayName: Push build to GitHub
|
||||
# env:
|
||||
# RPCS3_TOKEN: $(RPCS3-Token)
|
||||
|
||||
- job: Mac_Build_x86_64
|
||||
timeoutInMinutes: 180
|
||||
|
|
|
@ -597,8 +597,10 @@ if(TARGET 3rdparty_vulkan)
|
|||
RSX/VK/vkutils/descriptors.cpp
|
||||
RSX/VK/vkutils/image.cpp
|
||||
RSX/VK/vkutils/image_helpers.cpp
|
||||
RSX/VK/vkutils/instance.cpp
|
||||
RSX/VK/vkutils/scratch.cpp
|
||||
RSX/VK/vkutils/sync.cpp
|
||||
RSX/VK/vkutils/swapchain.cpp
|
||||
RSX/VK/vkutils/memory.cpp
|
||||
RSX/VK/vkutils/device.cpp
|
||||
RSX/VK/vkutils/sampler.cpp
|
||||
|
|
|
@ -4275,9 +4275,30 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
|||
}
|
||||
}
|
||||
|
||||
if (!src && !Emu.klic.empty() && src.open(path))
|
||||
{
|
||||
src = decrypt_self(src, reinterpret_cast<u8*>(&Emu.klic[0]));
|
||||
|
||||
if (src)
|
||||
{
|
||||
ppu_log.error("Possible missed KLIC for precompilation of '%s', please report to developers.", path);
|
||||
|
||||
// Ignore executables larger than 500KB to prevent a long pause on exitspawn
|
||||
if (src.size() >= 500000)
|
||||
{
|
||||
g_progr_ftotal_bits -= file_size;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!src)
|
||||
{
|
||||
ppu_log.notice("Failed to decrypt '%s'", path);
|
||||
|
||||
g_progr_ftotal_bits -= file_size;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -4459,9 +4480,22 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
|||
}
|
||||
}
|
||||
|
||||
if (!src && !Emu.klic.empty() && src.open(path))
|
||||
{
|
||||
src = decrypt_self(src, reinterpret_cast<u8*>(&Emu.klic[0]));
|
||||
|
||||
if (src)
|
||||
{
|
||||
ppu_log.error("Possible missed KLIC for precompilation of '%s', please report to developers.", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!src)
|
||||
{
|
||||
ppu_log.notice("Failed to decrypt '%s'", path);
|
||||
|
||||
g_progr_ftotal_bits -= file_size;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -4603,7 +4603,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
{
|
||||
bb.terminator = term_type::interrupt_call;
|
||||
}
|
||||
else if (last_inst != spu_itype::BISL && bb.targets[0] == tia + 4 && op.ra == s_reg_lr)
|
||||
else if (last_inst != spu_itype::BISL && !bb.targets.empty() && bb.targets[0] == tia + 4 && op.ra == s_reg_lr)
|
||||
{
|
||||
// Conditional return (TODO)
|
||||
bb.terminator = term_type::ret;
|
||||
|
@ -4728,9 +4728,11 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
if (is_ok && bb.terminator == term_type::fallthrough)
|
||||
{
|
||||
// Can't just fall out of the function
|
||||
if (bb.targets.size() != 1 || bb.targets[0] >= flim)
|
||||
const auto bb_target_value = bb.targets.empty() ? 0 : bb.targets[0];
|
||||
|
||||
if (bb.targets.size() != 1 || bb_target_value >= flim)
|
||||
{
|
||||
spu_log.error("Function 0x%05x: [0x%05x] bad fallthrough to 0x%x", f.first, addr, bb.targets[0]);
|
||||
spu_log.error("Function 0x%05x: [0x%05x] bad fallthrough to 0x%x", f.first, addr, bb_target_value);
|
||||
is_ok = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,6 +121,7 @@ namespace rsx
|
|||
m_list->set_pos(20, 85);
|
||||
|
||||
m_message_box = std::make_shared<home_menu_message_box>(20, 85, virtual_width - 2 * 20, 540);
|
||||
m_message_box->visible = false;
|
||||
|
||||
m_description = std::make_unique<label>();
|
||||
m_description->set_font("Arial", 20);
|
||||
|
@ -156,7 +157,7 @@ namespace rsx
|
|||
{
|
||||
if (fade_animation.active) return;
|
||||
|
||||
if (m_message_box && m_message_box->visible())
|
||||
if (m_message_box && m_message_box->visible)
|
||||
{
|
||||
const page_navigation navigation = m_message_box->handle_button_press(button_press);
|
||||
if (navigation != page_navigation::stay)
|
||||
|
@ -210,7 +211,7 @@ namespace rsx
|
|||
}
|
||||
}
|
||||
|
||||
if (!selected_username.empty() && m_message_box && !m_message_box->visible())
|
||||
if (!selected_username.empty() && m_message_box && !m_message_box->visible)
|
||||
{
|
||||
m_message_box->show(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_REMOVE_USER_MSG, selected_username.c_str()), [this, selected_username]()
|
||||
{
|
||||
|
@ -247,7 +248,7 @@ namespace rsx
|
|||
user_index++;
|
||||
}
|
||||
|
||||
if (!selected_username.empty() && m_message_box && !m_message_box->visible())
|
||||
if (!selected_username.empty() && m_message_box && !m_message_box->visible)
|
||||
{
|
||||
if (user_index < m_friend_data.requests_received.size())
|
||||
{
|
||||
|
@ -291,7 +292,7 @@ namespace rsx
|
|||
}
|
||||
}
|
||||
|
||||
if (!selected_username.empty() && m_message_box && !m_message_box->visible())
|
||||
if (!selected_username.empty() && m_message_box && !m_message_box->visible)
|
||||
{
|
||||
m_message_box->show(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_UNBLOCK_USER_MSG, selected_username.c_str()), []()
|
||||
{
|
||||
|
@ -392,7 +393,7 @@ namespace rsx
|
|||
m_last_page.store(m_current_page);
|
||||
}
|
||||
|
||||
if (m_message_box && m_message_box->visible())
|
||||
if (m_message_box && m_message_box->visible)
|
||||
{
|
||||
result.add(m_message_box->get_compiled());
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ namespace rsx
|
|||
is_current_page = true;
|
||||
|
||||
m_message_box = std::make_shared<home_menu_message_box>(x, y, width, height);
|
||||
m_message_box->visible = false;
|
||||
|
||||
m_config_changed = std::make_shared<bool>(g_backup_cfg.to_string() != g_cfg.to_string());
|
||||
|
||||
std::unique_ptr<overlay_element> resume = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_RESUME));
|
||||
|
|
|
@ -61,13 +61,13 @@ namespace rsx
|
|||
m_label.set_text(text);
|
||||
m_label.auto_resize();
|
||||
m_label.set_pos(x + (w - m_label.w) / 2, y + (h - m_label.h) / 2);
|
||||
m_visible = true;
|
||||
visible = true;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void home_menu_message_box::hide()
|
||||
{
|
||||
m_visible = false;
|
||||
visible = false;
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,8 @@ namespace rsx
|
|||
void show(const std::string& text, std::function<void()> on_accept = nullptr, std::function<void()> on_cancel = nullptr);
|
||||
void hide();
|
||||
page_navigation handle_button_press(pad_button button_press);
|
||||
bool visible() const { return m_visible; }
|
||||
|
||||
private:
|
||||
bool m_visible = false;
|
||||
label m_label{};
|
||||
image_button m_accept_btn;
|
||||
image_button m_cancel_btn;
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace rsx
|
|||
|
||||
void home_menu_page::show_dialog(const std::string& text, std::function<void()> on_accept, std::function<void()> on_cancel)
|
||||
{
|
||||
if (m_message_box && !m_message_box->visible())
|
||||
if (m_message_box && !m_message_box->visible)
|
||||
{
|
||||
rsx_log.notice("home_menu_page::show_dialog: page='%s', text='%s'", title, text);
|
||||
m_message_box->show(text, std::move(on_accept), std::move(on_cancel));
|
||||
|
@ -129,7 +129,7 @@ namespace rsx
|
|||
|
||||
page_navigation home_menu_page::handle_button_press(pad_button button_press, bool is_auto_repeat, u64 auto_repeat_interval_ms)
|
||||
{
|
||||
if (m_message_box && m_message_box->visible())
|
||||
if (m_message_box && m_message_box->visible)
|
||||
{
|
||||
const page_navigation navigation = m_message_box->handle_button_press(button_press);
|
||||
if (navigation != page_navigation::stay)
|
||||
|
@ -270,7 +270,7 @@ namespace rsx
|
|||
{
|
||||
compiled_resources = list_view::get_compiled();
|
||||
|
||||
if (m_message_box && m_message_box->visible())
|
||||
if (m_message_box && m_message_box->visible)
|
||||
{
|
||||
compiled_resources.add(m_message_box->get_compiled());
|
||||
}
|
||||
|
|
|
@ -9,7 +9,21 @@ namespace rsx
|
|||
{
|
||||
namespace overlays
|
||||
{
|
||||
trophy_list_dialog::trophy_list_entry::trophy_list_entry(const std::string& name, const std::string& description, const std::string& trophy_type, const std::string& icon_path, bool hidden, bool locked, bool platinum_relevant)
|
||||
static constexpr u16 trophy_list_y = 85;
|
||||
static constexpr u16 trophy_list_h = 540;
|
||||
|
||||
struct trophy_list_entry : horizontal_layout
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<image_info> icon_data;
|
||||
|
||||
public:
|
||||
trophy_list_entry(const SceNpTrophyDetails& details, const std::string& icon_path, bool locked, bool platinum_relevant);
|
||||
s32 trophy_id = 0;
|
||||
};
|
||||
|
||||
trophy_list_entry::trophy_list_entry(const SceNpTrophyDetails& details, const std::string& icon_path, bool locked, bool platinum_relevant)
|
||||
: trophy_id(details.trophyId)
|
||||
{
|
||||
std::unique_ptr<overlay_element> image = std::make_unique<image_view>();
|
||||
image->set_size(160, 110);
|
||||
|
@ -17,7 +31,7 @@ namespace rsx
|
|||
|
||||
if (fs::exists(icon_path))
|
||||
{
|
||||
icon_data = std::make_unique<image_info>(icon_path.c_str(), hidden || locked);
|
||||
icon_data = std::make_unique<image_info>(icon_path.c_str(), details.hidden || locked);
|
||||
static_cast<image_view*>(image.get())->set_raw_image(icon_data.get());
|
||||
}
|
||||
else
|
||||
|
@ -27,10 +41,20 @@ namespace rsx
|
|||
static_cast<image_view*>(image.get())->set_image_resource(resource_config::standard_image_resource::square);
|
||||
}
|
||||
|
||||
std::string trophy_type;
|
||||
switch (details.trophyGrade)
|
||||
{
|
||||
case SCE_NP_TROPHY_GRADE_BRONZE: trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_BRONZE); break;
|
||||
case SCE_NP_TROPHY_GRADE_SILVER: trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_SILVER); break;
|
||||
case SCE_NP_TROPHY_GRADE_GOLD: trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_GOLD); break;
|
||||
case SCE_NP_TROPHY_GRADE_PLATINUM: trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_PLATINUM); break;
|
||||
default: trophy_type = "?"; break;
|
||||
}
|
||||
|
||||
std::unique_ptr<overlay_element> text_stack = std::make_unique<vertical_layout>();
|
||||
std::unique_ptr<overlay_element> padding = std::make_unique<spacer>();
|
||||
std::unique_ptr<overlay_element> header_text = std::make_unique<label>(fmt::format("%s (%s%s)", (locked && !hidden) ? get_localized_string(localized_string_id::HOME_MENU_TROPHY_LOCKED_TITLE, name.c_str()) : name, trophy_type, platinum_relevant ? " - " + get_localized_string(localized_string_id::HOME_MENU_TROPHY_PLATINUM_RELEVANT) : ""));
|
||||
std::unique_ptr<overlay_element> subtext = std::make_unique<label>(description);
|
||||
std::unique_ptr<overlay_element> header_text = std::make_unique<label>(fmt::format("%s (%s%s)", (locked && !details.hidden) ? get_localized_string(localized_string_id::HOME_MENU_TROPHY_LOCKED_TITLE, details.name) : details.name, trophy_type, platinum_relevant ? " - " + get_localized_string(localized_string_id::HOME_MENU_TROPHY_PLATINUM_RELEVANT) : ""));
|
||||
std::unique_ptr<overlay_element> subtext = std::make_unique<label>(details.description);
|
||||
|
||||
padding->set_size(1, 1);
|
||||
header_text->set_size(800, 40);
|
||||
|
@ -70,10 +94,6 @@ namespace rsx
|
|||
m_dim_background->set_size(virtual_width, virtual_height);
|
||||
m_dim_background->back_color.a = 0.9f;
|
||||
|
||||
m_list = std::make_unique<list_view>(virtual_width - 2 * 20, 540);
|
||||
m_list->set_pos(20, 85);
|
||||
m_list->set_cancel_only(true);
|
||||
|
||||
m_description = std::make_unique<label>();
|
||||
m_description->set_font("Arial", 20);
|
||||
m_description->set_pos(20, 37);
|
||||
|
@ -81,6 +101,13 @@ namespace rsx
|
|||
m_description->auto_resize();
|
||||
m_description->back_color.a = 0.f;
|
||||
|
||||
m_show_hidden_trophies_button = std::make_unique<image_button>();
|
||||
m_show_hidden_trophies_button->set_text(m_show_hidden_trophies ? localized_string_id::HOME_MENU_TROPHY_HIDE_HIDDEN_TROPHIES : localized_string_id::HOME_MENU_TROPHY_SHOW_HIDDEN_TROPHIES);
|
||||
m_show_hidden_trophies_button->set_image_resource(resource_config::standard_image_resource::square);
|
||||
m_show_hidden_trophies_button->set_size(120, 30);
|
||||
m_show_hidden_trophies_button->set_pos(180, trophy_list_y + trophy_list_h + 20);
|
||||
m_show_hidden_trophies_button->set_font("Arial", 16);
|
||||
|
||||
fade_animation.duration_sec = 0.15f;
|
||||
|
||||
return_code = selection_code::canceled;
|
||||
|
@ -106,6 +133,10 @@ namespace rsx
|
|||
Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav");
|
||||
close_dialog = true;
|
||||
break;
|
||||
case pad_button::square:
|
||||
m_show_hidden_trophies = !m_show_hidden_trophies;
|
||||
m_list_dirty = true;
|
||||
break;
|
||||
case pad_button::dpad_up:
|
||||
case pad_button::ls_up:
|
||||
m_list->select_previous();
|
||||
|
@ -150,10 +181,24 @@ namespace rsx
|
|||
return {};
|
||||
}
|
||||
|
||||
if (m_show_hidden_trophies_last != m_show_hidden_trophies)
|
||||
{
|
||||
m_show_hidden_trophies_button->set_text(m_show_hidden_trophies ? localized_string_id::HOME_MENU_TROPHY_HIDE_HIDDEN_TROPHIES : localized_string_id::HOME_MENU_TROPHY_SHOW_HIDDEN_TROPHIES);
|
||||
m_show_hidden_trophies_last = m_show_hidden_trophies;
|
||||
}
|
||||
|
||||
compiled_resource result;
|
||||
result.add(m_dim_background->get_compiled());
|
||||
result.add(m_list->get_compiled());
|
||||
if (m_list_dirty.exchange(false))
|
||||
{
|
||||
reload();
|
||||
}
|
||||
if (m_list)
|
||||
{
|
||||
result.add(m_list->get_compiled());
|
||||
}
|
||||
result.add(m_description->get_compiled());
|
||||
result.add(m_show_hidden_trophies_button->get_compiled());
|
||||
|
||||
fade_animation.apply(result);
|
||||
|
||||
|
@ -163,103 +208,9 @@ namespace rsx
|
|||
void trophy_list_dialog::show(const std::string& trop_name)
|
||||
{
|
||||
visible = false;
|
||||
|
||||
std::unique_ptr<trophy_data> data = load_trophies(trop_name);
|
||||
ensure(data && data->trop_usr);
|
||||
|
||||
rsx_log.trace("Populating Trophy List Overlay with %s %s", data->game_name, data->path);
|
||||
|
||||
std::vector<std::unique_ptr<overlay_element>> entries;
|
||||
|
||||
const int all_trophies = data->trop_usr->GetTrophiesCount();
|
||||
const int unlocked_trophies = data->trop_usr->GetUnlockedTrophiesCount();
|
||||
const int percentage = (all_trophies > 0) ? (100 * unlocked_trophies / all_trophies) : 0;
|
||||
|
||||
std::shared_ptr<rXmlNode> trophy_base = data->trop_config.GetRoot();
|
||||
if (!trophy_base)
|
||||
{
|
||||
rsx_log.error("Populating Trophy List Overlay failed (root is null): %s %s", data->game_name, data->path);
|
||||
}
|
||||
|
||||
const std::string hidden_title = get_localized_string(localized_string_id::HOME_MENU_TROPHY_HIDDEN_TITLE);
|
||||
const std::string hidden_description = get_localized_string(localized_string_id::HOME_MENU_TROPHY_HIDDEN_DESCRIPTION);
|
||||
|
||||
for (std::shared_ptr<rXmlNode> n = trophy_base ? trophy_base->GetChildren() : nullptr; n; n = n->GetNext())
|
||||
{
|
||||
// Only show trophies.
|
||||
if (n->GetName() != "trophy")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get data (stolen graciously from sceNpTrophy.cpp)
|
||||
SceNpTrophyDetails details{};
|
||||
|
||||
// Get trophy id
|
||||
const s32 trophy_id = atoi(n->GetAttribute("id").c_str());
|
||||
details.trophyId = trophy_id;
|
||||
|
||||
// Get platinum link id (we assume there only exists one platinum trophy per game for now)
|
||||
const s32 platinum_link_id = atoi(n->GetAttribute("pid").c_str());
|
||||
const bool platinum_relevant = platinum_link_id >= 0;
|
||||
|
||||
// Get trophy type
|
||||
std::string trophy_type;
|
||||
|
||||
switch (n->GetAttribute("ttype")[0])
|
||||
{
|
||||
case 'B': details.trophyGrade = SCE_NP_TROPHY_GRADE_BRONZE; trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_BRONZE); break;
|
||||
case 'S': details.trophyGrade = SCE_NP_TROPHY_GRADE_SILVER; trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_SILVER); break;
|
||||
case 'G': details.trophyGrade = SCE_NP_TROPHY_GRADE_GOLD; trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_GOLD); break;
|
||||
case 'P': details.trophyGrade = SCE_NP_TROPHY_GRADE_PLATINUM; trophy_type = get_localized_string(localized_string_id::HOME_MENU_TROPHY_GRADE_PLATINUM); break;
|
||||
default: rsx_log.warning("Unknown trophy grade %s", n->GetAttribute("ttype")); break;
|
||||
}
|
||||
|
||||
// Get hidden state
|
||||
const bool hidden = n->GetAttribute("hidden")[0] == 'y';
|
||||
details.hidden = hidden;
|
||||
|
||||
// Get name and detail
|
||||
if (hidden)
|
||||
{
|
||||
strcpy_trunc(details.name, hidden_title);
|
||||
strcpy_trunc(details.description, hidden_description);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::shared_ptr<rXmlNode> n2 = n->GetChildren(); n2; n2 = n2->GetNext())
|
||||
{
|
||||
const std::string name = n2->GetName();
|
||||
if (name == "name")
|
||||
{
|
||||
strcpy_trunc(details.name, n2->GetNodeContent());
|
||||
}
|
||||
else if (name == "detail")
|
||||
{
|
||||
strcpy_trunc(details.description, n2->GetNodeContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool unlocked = data->trop_usr->GetTrophyUnlockState(trophy_id);
|
||||
const auto icon_path_it = data->trophy_image_paths.find(trophy_id);
|
||||
|
||||
std::unique_ptr<overlay_element> entry = std::make_unique<trophy_list_entry>(details.name, details.description, trophy_type, icon_path_it != data->trophy_image_paths.cend() ? icon_path_it->second : "", hidden, !unlocked, platinum_relevant);
|
||||
entries.emplace_back(std::move(entry));
|
||||
}
|
||||
|
||||
for (auto& entry : entries)
|
||||
{
|
||||
m_list->add_entry(entry);
|
||||
}
|
||||
|
||||
if (!m_list->m_items.empty())
|
||||
{
|
||||
m_list->select_entry(0);
|
||||
}
|
||||
|
||||
m_description->set_text(get_localized_string(localized_string_id::HOME_MENU_TROPHY_LIST_TITLE, fmt::format("%d%% (%d/%d)", percentage, unlocked_trophies, all_trophies).c_str()));
|
||||
m_description->auto_resize();
|
||||
|
||||
m_trophy_data = load_trophies(trop_name);
|
||||
ensure(m_trophy_data && m_trophy_data->trop_usr);
|
||||
|
||||
fade_animation.current = color4f(0.f);
|
||||
fade_animation.end = color4f(1.f);
|
||||
|
@ -351,5 +302,125 @@ namespace rsx
|
|||
|
||||
return game_trophy_data;
|
||||
}
|
||||
|
||||
void trophy_list_dialog::reload()
|
||||
{
|
||||
ensure(m_trophy_data);
|
||||
|
||||
rsx_log.trace("Reloading Trophy List Overlay with %s %s", m_trophy_data->game_name, m_trophy_data->path);
|
||||
|
||||
std::string selected_trophy;
|
||||
s32 selected_index = 0;
|
||||
const overlay_element* old_trophy = m_list ? m_list->get_selected_entry() : nullptr;
|
||||
const s32 old_trophy_id = old_trophy ? static_cast<const trophy_list_entry*>(old_trophy)->trophy_id : 0;
|
||||
|
||||
std::vector<std::unique_ptr<overlay_element>> entries;
|
||||
|
||||
const int all_trophies = m_trophy_data->trop_usr->GetTrophiesCount();
|
||||
const int unlocked_trophies = m_trophy_data->trop_usr->GetUnlockedTrophiesCount();
|
||||
const int percentage = (all_trophies > 0) ? (100 * unlocked_trophies / all_trophies) : 0;
|
||||
|
||||
std::shared_ptr<rXmlNode> trophy_base = m_trophy_data->trop_config.GetRoot();
|
||||
if (!trophy_base)
|
||||
{
|
||||
rsx_log.error("Populating Trophy List Overlay failed (root is null): %s %s", m_trophy_data->game_name, m_trophy_data->path);
|
||||
}
|
||||
|
||||
const std::string hidden_title = get_localized_string(localized_string_id::HOME_MENU_TROPHY_HIDDEN_TITLE);
|
||||
const std::string hidden_description = get_localized_string(localized_string_id::HOME_MENU_TROPHY_HIDDEN_DESCRIPTION);
|
||||
|
||||
for (std::shared_ptr<rXmlNode> n = trophy_base ? trophy_base->GetChildren() : nullptr; n; n = n->GetNext())
|
||||
{
|
||||
// Only show trophies.
|
||||
if (n->GetName() != "trophy")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get data (stolen graciously from sceNpTrophy.cpp)
|
||||
SceNpTrophyDetails details{};
|
||||
details.trophyId = atoi(n->GetAttribute("id").c_str());
|
||||
details.hidden = n->GetAttribute("hidden")[0] == 'y';
|
||||
|
||||
const bool hide_trophy = details.hidden && !m_show_hidden_trophies;
|
||||
|
||||
if (details.trophyId == old_trophy_id)
|
||||
{
|
||||
// Select this entry if the trophy is visible. Use the previous index otherwise.
|
||||
const s32 index = static_cast<s32>(entries.size());
|
||||
selected_index = hide_trophy ? std::max(0, index - 1) : index;
|
||||
}
|
||||
|
||||
if (hide_trophy)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get platinum link id (we assume there only exists one platinum trophy per game for now)
|
||||
const s32 platinum_link_id = atoi(n->GetAttribute("pid").c_str());
|
||||
const bool platinum_relevant = platinum_link_id >= 0;
|
||||
|
||||
// Get trophy type
|
||||
switch (n->GetAttribute("ttype")[0])
|
||||
{
|
||||
case 'B': details.trophyGrade = SCE_NP_TROPHY_GRADE_BRONZE; break;
|
||||
case 'S': details.trophyGrade = SCE_NP_TROPHY_GRADE_SILVER; break;
|
||||
case 'G': details.trophyGrade = SCE_NP_TROPHY_GRADE_GOLD; break;
|
||||
case 'P': details.trophyGrade = SCE_NP_TROPHY_GRADE_PLATINUM; break;
|
||||
default: rsx_log.warning("Unknown trophy grade %s", n->GetAttribute("ttype")); break;
|
||||
}
|
||||
|
||||
// Get name and detail
|
||||
if (details.hidden)
|
||||
{
|
||||
strcpy_trunc(details.name, hidden_title);
|
||||
strcpy_trunc(details.description, hidden_description);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::shared_ptr<rXmlNode> n2 = n->GetChildren(); n2; n2 = n2->GetNext())
|
||||
{
|
||||
const std::string name = n2->GetName();
|
||||
if (name == "name")
|
||||
{
|
||||
strcpy_trunc(details.name, n2->GetNodeContent());
|
||||
}
|
||||
else if (name == "detail")
|
||||
{
|
||||
strcpy_trunc(details.description, n2->GetNodeContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool unlocked = m_trophy_data->trop_usr->GetTrophyUnlockState(details.trophyId);
|
||||
const auto icon_path_it = m_trophy_data->trophy_image_paths.find(details.trophyId);
|
||||
|
||||
std::unique_ptr<overlay_element> entry = std::make_unique<trophy_list_entry>(details, icon_path_it != m_trophy_data->trophy_image_paths.cend() ? icon_path_it->second : "", !unlocked, platinum_relevant);
|
||||
entries.emplace_back(std::move(entry));
|
||||
}
|
||||
|
||||
// Recreate list
|
||||
if (m_list)
|
||||
{
|
||||
status_flags |= status_bits::invalidate_image_cache;
|
||||
}
|
||||
|
||||
m_list = std::make_unique<list_view>(virtual_width - 2 * 20, trophy_list_h);
|
||||
m_list->set_pos(20, trophy_list_y);
|
||||
m_list->set_cancel_only(true);
|
||||
|
||||
for (auto& entry : entries)
|
||||
{
|
||||
m_list->add_entry(entry);
|
||||
}
|
||||
|
||||
if (!m_list->m_items.empty())
|
||||
{
|
||||
m_list->select_entry(selected_index);
|
||||
}
|
||||
|
||||
m_description->set_text(get_localized_string(localized_string_id::HOME_MENU_TROPHY_LIST_TITLE, fmt::format("%d%% (%d/%d)", percentage, unlocked_trophies, all_trophies).c_str()));
|
||||
m_description->auto_resize();
|
||||
}
|
||||
} // namespace overlays
|
||||
} // namespace RSX
|
||||
|
|
|
@ -23,23 +23,21 @@ namespace rsx
|
|||
struct trophy_list_dialog : public user_interface
|
||||
{
|
||||
private:
|
||||
struct trophy_list_entry : horizontal_layout
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<image_info> icon_data;
|
||||
|
||||
public:
|
||||
trophy_list_entry(const std::string& name, const std::string& description, const std::string& trophy_type, const std::string& icon_path, bool hidden, bool locked, bool platinum_relevant);
|
||||
};
|
||||
|
||||
std::unique_ptr<trophy_data> load_trophies(const std::string& trop_name) const;
|
||||
void reload();
|
||||
|
||||
std::unique_ptr<overlay_element> m_dim_background;
|
||||
std::unique_ptr<list_view> m_list;
|
||||
std::unique_ptr<label> m_description;
|
||||
std::unique_ptr<image_button> m_show_hidden_trophies_button;
|
||||
|
||||
animation_color_interpolate fade_animation;
|
||||
|
||||
std::unique_ptr<trophy_data> m_trophy_data;
|
||||
atomic_t<bool> m_list_dirty { true };
|
||||
bool m_show_hidden_trophies = false;
|
||||
bool m_show_hidden_trophies_last = false;
|
||||
|
||||
public:
|
||||
trophy_list_dialog();
|
||||
|
||||
|
|
|
@ -735,14 +735,20 @@ namespace rsx
|
|||
{
|
||||
if (!item)
|
||||
{
|
||||
rsx_log.error("Found null item in overlay_controls");
|
||||
rsx_log.error("Found null item in overlay_controls::vertical_layout");
|
||||
continue;
|
||||
}
|
||||
|
||||
const s32 item_y_limit = s32{item->y} + item->h - scroll_offset_value - y;
|
||||
const s32 item_y_base = s32{item->y} - scroll_offset_value - y;
|
||||
|
||||
if (item_y_limit < 0 || item_y_base > h)
|
||||
if (item_y_base > h)
|
||||
{
|
||||
// Out of bounds. The following items will be too.
|
||||
break;
|
||||
}
|
||||
|
||||
if (item_y_limit < 0)
|
||||
{
|
||||
// Out of bounds
|
||||
continue;
|
||||
|
@ -809,15 +815,28 @@ namespace rsx
|
|||
|
||||
for (auto &item : m_items)
|
||||
{
|
||||
if (!item)
|
||||
{
|
||||
rsx_log.error("Found null item in overlay_controls::horizontal_layout");
|
||||
continue;
|
||||
}
|
||||
|
||||
const s32 item_x_limit = s32{item->x} + item->w - scroll_offset_value - w;
|
||||
const s32 item_x_base = s32{item->x} - scroll_offset_value - w;
|
||||
|
||||
if (item_x_limit < 0 || item_x_base > h)
|
||||
if (item_x_base > w)
|
||||
{
|
||||
// Out of bounds. The following items will be too.
|
||||
break;
|
||||
}
|
||||
|
||||
if (item_x_limit < 0)
|
||||
{
|
||||
// Out of bounds
|
||||
continue;
|
||||
}
|
||||
else if (item_x_limit > h || item_x_base < 0)
|
||||
|
||||
if (item_x_limit > w || item_x_base < 0)
|
||||
{
|
||||
// Partial render
|
||||
areaf clip_rect = static_cast<areaf>(areai{x, y, (x + w), (y + h)});
|
||||
|
|
|
@ -167,6 +167,8 @@ namespace rsx
|
|||
compiled_resource compiled_resources;
|
||||
bool is_compiled = false;
|
||||
|
||||
bool visible = true;
|
||||
|
||||
u16 padding_left = 0;
|
||||
u16 padding_right = 0;
|
||||
u16 padding_top = 0;
|
||||
|
|
|
@ -71,25 +71,34 @@ namespace rsx
|
|||
m_scroll_indicator_bottom->fore_color.a = 0.f;
|
||||
}
|
||||
|
||||
void list_view::update_selection()
|
||||
const overlay_element* list_view::get_selected_entry() const
|
||||
{
|
||||
if (m_selected_entry < 0)
|
||||
{
|
||||
return; // Ideally unreachable but it should still be possible to recover by user interaction.
|
||||
return nullptr; // Ideally unreachable but it should still be possible to recover by user interaction.
|
||||
}
|
||||
|
||||
const usz current_index = static_cast<usz>(m_selected_entry) * (m_use_separators ? 2 : 1);
|
||||
|
||||
if (m_items.size() <= current_index)
|
||||
if (current_index >= m_items.size())
|
||||
{
|
||||
return nullptr; // Ideally unreachable but it should still be possible to recover by user interaction.
|
||||
}
|
||||
|
||||
return m_items[current_index].get();
|
||||
}
|
||||
|
||||
void list_view::update_selection()
|
||||
{
|
||||
const overlay_element* current_element = get_selected_entry();
|
||||
if (!current_element)
|
||||
{
|
||||
return; // Ideally unreachable but it should still be possible to recover by user interaction.
|
||||
}
|
||||
|
||||
auto current_element = m_items[current_index].get();
|
||||
|
||||
// Calculate bounds
|
||||
auto min_y = current_element->y - y;
|
||||
auto max_y = current_element->y + current_element->h + pack_padding + 2 - y;
|
||||
const auto min_y = current_element->y - y;
|
||||
const auto max_y = current_element->y + current_element->h + pack_padding + 2 - y;
|
||||
|
||||
if (min_y < scroll_offset_value)
|
||||
{
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace rsx
|
|||
|
||||
int get_selected_index() const;
|
||||
bool get_cancel_only() const;
|
||||
const overlay_element* get_selected_entry() const;
|
||||
|
||||
void set_cancel_only(bool cancel_only);
|
||||
void translate(s16 _x, s16 _y) override;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "stdafx.h"
|
||||
#include "ProgramStateCache.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "util/sysinfo.hpp"
|
||||
|
||||
#include <stack>
|
||||
|
||||
|
@ -21,31 +22,119 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_ARM64
|
||||
#define AVX512_ICL_FUNC
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define AVX512_ICL_FUNC
|
||||
#else
|
||||
#define AVX512_ICL_FUNC __attribute__((__target__("avx512f,avx512bw,avx512dq,avx512cd,avx512vl,avx512bitalg,avx512ifma,avx512vbmi,avx512vbmi2,avx512vnni,avx512vpopcntdq")))
|
||||
#endif
|
||||
|
||||
|
||||
using namespace program_hash_util;
|
||||
|
||||
usz vertex_program_utils::get_vertex_program_ucode_hash(const RSXVertexProgram &program)
|
||||
AVX512_ICL_FUNC usz vertex_program_utils::get_vertex_program_ucode_hash(const RSXVertexProgram &program)
|
||||
{
|
||||
// Checksum as hash with rotated data
|
||||
const void* instbuffer = program.data.data();
|
||||
u32 instIndex = 0;
|
||||
usz acc0 = 0;
|
||||
usz acc1 = 0;
|
||||
|
||||
do
|
||||
#ifdef ARCH_X64
|
||||
if (utils::has_avx512_icl())
|
||||
{
|
||||
if (program.instruction_mask[instIndex])
|
||||
// Load all elements of the instruction_mask bitset
|
||||
const __m512i* instMask512 = reinterpret_cast<const __m512i*>(&program.instruction_mask);
|
||||
const __m128i* instMask128 = reinterpret_cast<const __m128i*>(&program.instruction_mask);
|
||||
|
||||
const __m512i lowerMask = _mm512_loadu_si512(instMask512);
|
||||
const __m128i upper128 = _mm_loadu_si128(instMask128 + 4);
|
||||
const __m512i upperMask = _mm512_zextsi128_si512(upper128);
|
||||
|
||||
__m512i maskIndex = _mm512_setzero_si512();
|
||||
const __m512i negativeOnes = _mm512_set1_epi64(-1);
|
||||
|
||||
// Special masks to test against bitset
|
||||
const __m512i testMask0 = _mm512_set_epi64(
|
||||
0x0808080808080808,
|
||||
0x0808080808080808,
|
||||
0x0404040404040404,
|
||||
0x0404040404040404,
|
||||
0x0202020202020202,
|
||||
0x0202020202020202,
|
||||
0x0101010101010101,
|
||||
0x0101010101010101);
|
||||
|
||||
const __m512i testMask1 = _mm512_set_epi64(
|
||||
0x8080808080808080,
|
||||
0x8080808080808080,
|
||||
0x4040404040404040,
|
||||
0x4040404040404040,
|
||||
0x2020202020202020,
|
||||
0x2020202020202020,
|
||||
0x1010101010101010,
|
||||
0x1010101010101010);
|
||||
|
||||
const __m512i* instBuffer = reinterpret_cast<const __m512i*>(program.data.data());
|
||||
__m512i acc0 = _mm512_setzero_si512();
|
||||
__m512i acc1 = _mm512_setzero_si512();
|
||||
|
||||
__m512i rotMask0 = _mm512_set_epi64(7, 6, 5, 4, 3, 2, 1, 0);
|
||||
__m512i rotMask1 = _mm512_set_epi64(15, 14, 13, 12, 11, 10, 9, 8);
|
||||
__m512i rotMaskAdd = _mm512_set_epi64(16, 16, 16, 16, 16, 16, 16, 16);
|
||||
|
||||
u32 instIndex = 0;
|
||||
|
||||
// If there is remainder, add an extra (masked) iteration
|
||||
u32 extraIteration = (program.data.size() % 32 != 0) ? 1 : 0;
|
||||
u32 length = (program.data.size() / 32) + extraIteration;
|
||||
|
||||
// The instruction mask will prevent us from reading out of bounds, we do not need a seperate masked loop
|
||||
// for the remainder, or a scalar loop.
|
||||
while (instIndex < (length))
|
||||
{
|
||||
const auto inst = v128::loadu(instbuffer, instIndex);
|
||||
usz tmp0 = std::rotr(inst._u64[0], instIndex * 2);
|
||||
acc0 += tmp0;
|
||||
usz tmp1 = std::rotr(inst._u64[1], (instIndex * 2) + 1);
|
||||
acc1 += tmp1;
|
||||
const __m512i masks = _mm512_permutex2var_epi8(lowerMask, maskIndex, upperMask);
|
||||
const __mmask8 result0 = _mm512_test_epi64_mask(masks, testMask0);
|
||||
const __mmask8 result1 = _mm512_test_epi64_mask(masks, testMask1);
|
||||
const __m512i load0 = _mm512_maskz_loadu_epi64(result0, (instBuffer + instIndex * 2));
|
||||
const __m512i load1 = _mm512_maskz_loadu_epi64(result1, (instBuffer + (instIndex * 2)+ 1));
|
||||
|
||||
const __m512i rotated0 = _mm512_rorv_epi64(load0, rotMask0);
|
||||
const __m512i rotated1 = _mm512_rorv_epi64(load1, rotMask1);
|
||||
|
||||
acc0 = _mm512_add_epi64(acc0, rotated0);
|
||||
acc1 = _mm512_add_epi64(acc1, rotated1);
|
||||
|
||||
rotMask0 = _mm512_add_epi64(rotMask0, rotMaskAdd);
|
||||
rotMask1 = _mm512_add_epi64(rotMask1, rotMaskAdd);
|
||||
maskIndex = _mm512_sub_epi8(maskIndex, negativeOnes);
|
||||
|
||||
instIndex++;
|
||||
}
|
||||
|
||||
instIndex++;
|
||||
} while (instIndex < (program.data.size() / 4));
|
||||
const __m512i result = _mm512_add_epi64(acc0, acc1);
|
||||
return _mm512_reduce_add_epi64(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Checksum as hash with rotated data
|
||||
const void* instbuffer = program.data.data();
|
||||
u32 instIndex = 0;
|
||||
usz acc0 = 0;
|
||||
usz acc1 = 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (program.instruction_mask[instIndex])
|
||||
{
|
||||
const auto inst = v128::loadu(instbuffer, instIndex);
|
||||
usz tmp0 = std::rotr(inst._u64[0], instIndex * 2);
|
||||
acc0 += tmp0;
|
||||
usz tmp1 = std::rotr(inst._u64[1], (instIndex * 2) + 1);
|
||||
acc1 += tmp1;
|
||||
}
|
||||
|
||||
instIndex++;
|
||||
} while (instIndex < (program.data.size() / 4));
|
||||
return acc0 + acc1;
|
||||
}
|
||||
}
|
||||
|
||||
vertex_program_utils::vertex_program_metadata vertex_program_utils::analyse_vertex_program(const u32* data, u32 entry, RSXVertexProgram& dst_prog)
|
||||
{
|
||||
|
@ -350,7 +439,7 @@ usz vertex_program_storage_hash::operator()(const RSXVertexProgram &program) con
|
|||
return rpcs3::hash64(ucode_hash, metadata_hash);
|
||||
}
|
||||
|
||||
bool vertex_program_compare::operator()(const RSXVertexProgram &binary1, const RSXVertexProgram &binary2) const
|
||||
AVX512_ICL_FUNC bool vertex_program_compare::operator()(const RSXVertexProgram &binary1, const RSXVertexProgram &binary2) const
|
||||
{
|
||||
if (binary1.output_mask != binary2.output_mask)
|
||||
return false;
|
||||
|
@ -363,10 +452,88 @@ bool vertex_program_compare::operator()(const RSXVertexProgram &binary1, const R
|
|||
if (binary1.jump_table != binary2.jump_table)
|
||||
return false;
|
||||
|
||||
#ifdef ARCH_X64
|
||||
if (utils::has_avx512_icl())
|
||||
{
|
||||
// Load all elements of the instruction_mask bitset
|
||||
const __m512i* instMask512 = reinterpret_cast<const __m512i*>(&binary1.instruction_mask);
|
||||
const __m128i* instMask128 = reinterpret_cast<const __m128i*>(&binary1.instruction_mask);
|
||||
|
||||
const __m512i lowerMask = _mm512_loadu_si512(instMask512);
|
||||
const __m128i upper128 = _mm_loadu_si128(instMask128 + 4);
|
||||
const __m512i upperMask = _mm512_zextsi128_si512(upper128);
|
||||
|
||||
__m512i maskIndex = _mm512_setzero_si512();
|
||||
const __m512i negativeOnes = _mm512_set1_epi64(-1);
|
||||
|
||||
// Special masks to test against bitset
|
||||
const __m512i testMask0 = _mm512_set_epi64(
|
||||
0x0808080808080808,
|
||||
0x0808080808080808,
|
||||
0x0404040404040404,
|
||||
0x0404040404040404,
|
||||
0x0202020202020202,
|
||||
0x0202020202020202,
|
||||
0x0101010101010101,
|
||||
0x0101010101010101);
|
||||
|
||||
const __m512i testMask1 = _mm512_set_epi64(
|
||||
0x8080808080808080,
|
||||
0x8080808080808080,
|
||||
0x4040404040404040,
|
||||
0x4040404040404040,
|
||||
0x2020202020202020,
|
||||
0x2020202020202020,
|
||||
0x1010101010101010,
|
||||
0x1010101010101010);
|
||||
|
||||
const __m512i* instBuffer1 = reinterpret_cast<const __m512i*>(binary1.data.data());
|
||||
const __m512i* instBuffer2 = reinterpret_cast<const __m512i*>(binary2.data.data());
|
||||
|
||||
// If there is remainder, add an extra (masked) iteration
|
||||
u32 extraIteration = (binary1.data.size() % 32 != 0) ? 1 : 0;
|
||||
u32 length = (binary1.data.size() / 32) + extraIteration;
|
||||
|
||||
u32 instIndex = 0;
|
||||
|
||||
// The instruction mask will prevent us from reading out of bounds, we do not need a seperate masked loop
|
||||
// for the remainder, or a scalar loop.
|
||||
while (instIndex < (length))
|
||||
{
|
||||
const __m512i masks = _mm512_permutex2var_epi8(lowerMask, maskIndex, upperMask);
|
||||
|
||||
const __mmask8 result0 = _mm512_test_epi64_mask(masks, testMask0);
|
||||
const __mmask8 result1 = _mm512_test_epi64_mask(masks, testMask1);
|
||||
|
||||
const __m512i load0 = _mm512_maskz_loadu_epi64(result0, (instBuffer1 + (instIndex * 2)));
|
||||
const __m512i load1 = _mm512_maskz_loadu_epi64(result0, (instBuffer2 + (instIndex * 2)));
|
||||
const __m512i load2 = _mm512_maskz_loadu_epi64(result1, (instBuffer1 + (instIndex * 2) + 1));
|
||||
const __m512i load3 = _mm512_maskz_loadu_epi64(result1, (instBuffer2 + (instIndex * 2)+ 1));
|
||||
|
||||
const __mmask8 res0 = _mm512_cmpneq_epi64_mask(load0, load1);
|
||||
const __mmask8 res1 = _mm512_cmpneq_epi64_mask(load2, load3);
|
||||
|
||||
const u8 result = _kortestz_mask8_u8(res0, res1);
|
||||
|
||||
//kortestz will set result to 1 if all bits are zero, so invert the check for result
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
maskIndex = _mm512_sub_epi8(maskIndex, negativeOnes);
|
||||
|
||||
instIndex++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
const void* instBuffer1 = binary1.data.data();
|
||||
const void* instBuffer2 = binary2.data.data();
|
||||
usz instIndex = 0;
|
||||
for (unsigned i = 0; i < binary1.data.size() / 4; i++)
|
||||
while (instIndex < (binary1.data.size() / 4))
|
||||
{
|
||||
if (binary1.instruction_mask[instIndex])
|
||||
{
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
#include "vkutils/descriptors.h"
|
||||
#include "vkutils/data_heap.h"
|
||||
#include "vkutils/instance.hpp"
|
||||
#include "vkutils/instance.h"
|
||||
#include "vkutils/sync.h"
|
||||
#include "vkutils/swapchain.hpp"
|
||||
#include "vkutils/swapchain.h"
|
||||
|
||||
#include "VKGSRenderTypes.hpp"
|
||||
#include "VKTextureCache.h"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "device.h"
|
||||
#include "instance.hpp"
|
||||
#include "instance.h"
|
||||
#include "util/logs.hpp"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
|
|
366
rpcs3/Emu/RSX/VK/vkutils/instance.cpp
Normal file
366
rpcs3/Emu/RSX/VK/vkutils/instance.cpp
Normal file
|
@ -0,0 +1,366 @@
|
|||
#include "stdafx.h"
|
||||
#include "instance.h"
|
||||
|
||||
namespace vk
|
||||
{
|
||||
// Supported extensions
|
||||
supported_extensions::supported_extensions(enumeration_class _class, const char* layer_name, VkPhysicalDevice pdev)
|
||||
{
|
||||
u32 count;
|
||||
if (_class == enumeration_class::instance)
|
||||
{
|
||||
if (vkEnumerateInstanceExtensionProperties(layer_name, &count, nullptr) != VK_SUCCESS)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ensure(pdev);
|
||||
if (vkEnumerateDeviceExtensionProperties(pdev, layer_name, &count, nullptr) != VK_SUCCESS)
|
||||
return;
|
||||
}
|
||||
|
||||
m_vk_exts.resize(count);
|
||||
if (_class == enumeration_class::instance)
|
||||
{
|
||||
vkEnumerateInstanceExtensionProperties(layer_name, &count, m_vk_exts.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
vkEnumerateDeviceExtensionProperties(pdev, layer_name, &count, m_vk_exts.data());
|
||||
}
|
||||
}
|
||||
|
||||
bool supported_extensions::is_supported(std::string_view ext) const
|
||||
{
|
||||
return std::any_of(m_vk_exts.cbegin(), m_vk_exts.cend(), [&](const VkExtensionProperties& p) { return p.extensionName == ext; });
|
||||
}
|
||||
|
||||
// Instance
|
||||
instance::~instance()
|
||||
{
|
||||
if (m_instance)
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void instance::destroy()
|
||||
{
|
||||
if (!m_instance) return;
|
||||
|
||||
if (m_debugger)
|
||||
{
|
||||
_vkDestroyDebugReportCallback(m_instance, m_debugger, nullptr);
|
||||
m_debugger = nullptr;
|
||||
}
|
||||
|
||||
if (m_surface)
|
||||
{
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
m_surface = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
vkDestroyInstance(m_instance, nullptr);
|
||||
m_instance = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void instance::enable_debugging()
|
||||
{
|
||||
if (!g_cfg.video.debug_output) return;
|
||||
|
||||
PFN_vkDebugReportCallbackEXT callback = vk::dbgFunc;
|
||||
|
||||
_vkCreateDebugReportCallback = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(m_instance, "vkCreateDebugReportCallbackEXT"));
|
||||
_vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(m_instance, "vkDestroyDebugReportCallbackEXT"));
|
||||
|
||||
VkDebugReportCallbackCreateInfoEXT dbgCreateInfo = {};
|
||||
dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
|
||||
dbgCreateInfo.pfnCallback = callback;
|
||||
dbgCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
|
||||
|
||||
CHECK_RESULT(_vkCreateDebugReportCallback(m_instance, &dbgCreateInfo, NULL, &m_debugger));
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
bool instance::create(const char* app_name, bool fast)
|
||||
{
|
||||
// Initialize a vulkan instance
|
||||
VkApplicationInfo app = {};
|
||||
|
||||
app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
app.pApplicationName = app_name;
|
||||
app.applicationVersion = 0;
|
||||
app.pEngineName = app_name;
|
||||
app.engineVersion = 0;
|
||||
app.apiVersion = VK_API_VERSION_1_0;
|
||||
|
||||
// Set up instance information
|
||||
|
||||
std::vector<const char*> extensions;
|
||||
std::vector<const char*> layers;
|
||||
const void* next_info = nullptr;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Declare MVK variables here to ensure the lifetime within the entire scope
|
||||
const VkBool32 setting_true = VK_TRUE;
|
||||
const int32_t setting_fast_math = g_cfg.video.disable_msl_fast_math.get() ? MVK_CONFIG_FAST_MATH_NEVER : MVK_CONFIG_FAST_MATH_ON_DEMAND;
|
||||
|
||||
std::vector<VkLayerSettingEXT> mvk_settings;
|
||||
VkLayerSettingsCreateInfoEXT mvk_layer_settings_create_info{};
|
||||
#endif
|
||||
|
||||
if (!fast)
|
||||
{
|
||||
extensions_loaded = true;
|
||||
supported_extensions support(supported_extensions::instance);
|
||||
|
||||
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
||||
if (support.is_supported(VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (support.is_supported(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
|
||||
layers.push_back(kMVKMoltenVKDriverLayerName);
|
||||
|
||||
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true });
|
||||
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math });
|
||||
|
||||
mvk_layer_settings_create_info.sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT;
|
||||
mvk_layer_settings_create_info.pNext = next_info;
|
||||
mvk_layer_settings_create_info.settingCount = static_cast<uint32_t>(mvk_settings.size());
|
||||
mvk_layer_settings_create_info.pSettings = mvk_settings.data();
|
||||
|
||||
next_info = &mvk_layer_settings_create_info;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (support.is_supported(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (support.is_supported(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (g_cfg.video.renderdoc_compatiblity && support.is_supported(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(__APPLE__)
|
||||
extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
|
||||
#else
|
||||
bool found_surface_ext = false;
|
||||
#ifdef HAVE_X11
|
||||
if (support.is_supported(VK_KHR_XLIB_SURFACE_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||
found_surface_ext = true;
|
||||
}
|
||||
#endif
|
||||
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
|
||||
if (support.is_supported(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
found_surface_ext = true;
|
||||
}
|
||||
#endif //(WAYLAND)
|
||||
if (!found_surface_ext)
|
||||
{
|
||||
rsx_log.error("Could not find a supported Vulkan surface extension");
|
||||
return 0;
|
||||
}
|
||||
#endif //(WIN32, __APPLE__)
|
||||
if (g_cfg.video.debug_output)
|
||||
layers.push_back("VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
|
||||
VkInstanceCreateInfo instance_info = {};
|
||||
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
instance_info.pApplicationInfo = &app;
|
||||
instance_info.enabledLayerCount = static_cast<u32>(layers.size());
|
||||
instance_info.ppEnabledLayerNames = layers.data();
|
||||
instance_info.enabledExtensionCount = fast ? 0 : static_cast<u32>(extensions.size());
|
||||
instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data();
|
||||
instance_info.pNext = next_info;
|
||||
|
||||
if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS)
|
||||
{
|
||||
if (result == VK_ERROR_LAYER_NOT_PRESENT)
|
||||
{
|
||||
rsx_log.fatal("Could not initialize layer VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
void instance::bind()
|
||||
{
|
||||
// Register some global states
|
||||
if (m_debugger)
|
||||
{
|
||||
_vkDestroyDebugReportCallback(m_instance, m_debugger, nullptr);
|
||||
m_debugger = nullptr;
|
||||
}
|
||||
|
||||
enable_debugging();
|
||||
}
|
||||
|
||||
std::vector<physical_device>& instance::enumerate_devices()
|
||||
{
|
||||
u32 num_gpus;
|
||||
// This may fail on unsupported drivers, so just assume no devices
|
||||
if (vkEnumeratePhysicalDevices(m_instance, &num_gpus, nullptr) != VK_SUCCESS)
|
||||
return gpus;
|
||||
|
||||
if (gpus.size() != num_gpus)
|
||||
{
|
||||
std::vector<VkPhysicalDevice> pdevs(num_gpus);
|
||||
gpus.resize(num_gpus);
|
||||
|
||||
CHECK_RESULT(vkEnumeratePhysicalDevices(m_instance, &num_gpus, pdevs.data()));
|
||||
|
||||
for (u32 i = 0; i < num_gpus; ++i)
|
||||
gpus[i].create(m_instance, pdevs[i], extensions_loaded);
|
||||
}
|
||||
|
||||
return gpus;
|
||||
}
|
||||
|
||||
swapchain_base* instance::create_swapchain(display_handle_t window_handle, vk::physical_device& dev)
|
||||
{
|
||||
WSI_config surface_config
|
||||
{
|
||||
.supports_automatic_wm_reports = true
|
||||
};
|
||||
m_surface = make_WSI_surface(m_instance, window_handle, &surface_config);
|
||||
|
||||
u32 device_queues = dev.get_queue_count();
|
||||
std::vector<VkBool32> supports_present(device_queues, VK_FALSE);
|
||||
bool present_possible = true;
|
||||
|
||||
for (u32 index = 0; index < device_queues; index++)
|
||||
{
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(dev, index, m_surface, &supports_present[index]);
|
||||
}
|
||||
|
||||
u32 graphics_queue_idx = -1;
|
||||
u32 present_queue_idx = -1;
|
||||
u32 transfer_queue_idx = -1;
|
||||
|
||||
auto test_queue_family = [&](u32 index, u32 desired_flags)
|
||||
{
|
||||
if (const auto flags = dev.get_queue_properties(index).queueFlags;
|
||||
(flags & desired_flags) == desired_flags)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < device_queues; ++i)
|
||||
{
|
||||
// 1. Test for a present queue possibly one that also supports present
|
||||
if (present_queue_idx == umax && supports_present[i])
|
||||
{
|
||||
present_queue_idx = i;
|
||||
if (test_queue_family(i, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))
|
||||
{
|
||||
graphics_queue_idx = i;
|
||||
}
|
||||
}
|
||||
// 2. Check for graphics support
|
||||
else if (graphics_queue_idx == umax && test_queue_family(i, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))
|
||||
{
|
||||
graphics_queue_idx = i;
|
||||
if (supports_present[i])
|
||||
{
|
||||
present_queue_idx = i;
|
||||
}
|
||||
}
|
||||
// 3. Check if transfer + compute is available
|
||||
else if (transfer_queue_idx == umax && test_queue_family(i, VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT))
|
||||
{
|
||||
transfer_queue_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (graphics_queue_idx == umax)
|
||||
{
|
||||
rsx_log.fatal("Failed to find a suitable graphics queue");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (graphics_queue_idx != present_queue_idx)
|
||||
{
|
||||
// Separate graphics and present, use headless fallback
|
||||
present_possible = false;
|
||||
}
|
||||
|
||||
if (!present_possible)
|
||||
{
|
||||
//Native(sw) swapchain
|
||||
rsx_log.error("It is not possible for the currently selected GPU to present to the window (Likely caused by NVIDIA driver running the current display)");
|
||||
rsx_log.warning("Falling back to software present support (native windowing API)");
|
||||
auto swapchain = new swapchain_NATIVE(dev, -1, graphics_queue_idx, transfer_queue_idx);
|
||||
swapchain->create(window_handle);
|
||||
return swapchain;
|
||||
}
|
||||
|
||||
// Get the list of VkFormat's that are supported:
|
||||
u32 formatCount;
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(dev, m_surface, &formatCount, nullptr));
|
||||
|
||||
std::vector<VkSurfaceFormatKHR> surfFormats(formatCount);
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(dev, m_surface, &formatCount, surfFormats.data()));
|
||||
|
||||
VkFormat format;
|
||||
VkColorSpaceKHR color_space;
|
||||
|
||||
if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED)
|
||||
{
|
||||
format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!formatCount) fmt::throw_exception("Format count is zero!");
|
||||
format = surfFormats[0].format;
|
||||
|
||||
//Prefer BGRA8_UNORM to avoid sRGB compression (RADV)
|
||||
for (auto& surface_format : surfFormats)
|
||||
{
|
||||
if (surface_format.format == VK_FORMAT_B8G8R8A8_UNORM)
|
||||
{
|
||||
format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color_space = surfFormats[0].colorSpace;
|
||||
|
||||
return new swapchain_WSI(dev, present_queue_idx, graphics_queue_idx, transfer_queue_idx, format, m_surface, color_space, !surface_config.supports_automatic_wm_reports);
|
||||
}
|
||||
}
|
64
rpcs3/Emu/RSX/VK/vkutils/instance.h
Normal file
64
rpcs3/Emu/RSX/VK/vkutils/instance.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include "../VulkanAPI.h"
|
||||
#include "swapchain.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <MoltenVK/mvk_vulkan.h>
|
||||
#include <MoltenVK/mvk_private_api.h>
|
||||
#endif
|
||||
|
||||
namespace vk
|
||||
{
|
||||
class supported_extensions
|
||||
{
|
||||
private:
|
||||
std::vector<VkExtensionProperties> m_vk_exts;
|
||||
|
||||
public:
|
||||
enum enumeration_class
|
||||
{
|
||||
instance = 0,
|
||||
device = 1
|
||||
};
|
||||
|
||||
supported_extensions(enumeration_class _class, const char* layer_name = nullptr, VkPhysicalDevice pdev = VK_NULL_HANDLE);
|
||||
|
||||
bool is_supported(std::string_view ext) const;
|
||||
};
|
||||
|
||||
class instance
|
||||
{
|
||||
private:
|
||||
std::vector<physical_device> gpus;
|
||||
VkInstance m_instance = VK_NULL_HANDLE;
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
|
||||
PFN_vkDestroyDebugReportCallbackEXT _vkDestroyDebugReportCallback = nullptr;
|
||||
PFN_vkCreateDebugReportCallbackEXT _vkCreateDebugReportCallback = nullptr;
|
||||
VkDebugReportCallbackEXT m_debugger = nullptr;
|
||||
|
||||
bool extensions_loaded = false;
|
||||
|
||||
public:
|
||||
|
||||
instance() = default;
|
||||
|
||||
~instance();
|
||||
|
||||
void destroy();
|
||||
|
||||
void enable_debugging();
|
||||
|
||||
bool create(const char* app_name, bool fast = false);
|
||||
|
||||
void bind();
|
||||
|
||||
std::vector<physical_device>& enumerate_devices();
|
||||
|
||||
swapchain_base* create_swapchain(display_handle_t window_handle, vk::physical_device& dev);
|
||||
};
|
||||
}
|
|
@ -1,457 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../VulkanAPI.h"
|
||||
#include "swapchain.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <MoltenVK/mvk_vulkan.h>
|
||||
#include <MoltenVK/mvk_private_api.h>
|
||||
#endif
|
||||
|
||||
namespace vk
|
||||
{
|
||||
class supported_extensions
|
||||
{
|
||||
private:
|
||||
std::vector<VkExtensionProperties> m_vk_exts;
|
||||
|
||||
public:
|
||||
enum enumeration_class
|
||||
{
|
||||
instance = 0,
|
||||
device = 1
|
||||
};
|
||||
|
||||
supported_extensions(enumeration_class _class, const char* layer_name = nullptr, VkPhysicalDevice pdev = VK_NULL_HANDLE)
|
||||
{
|
||||
u32 count;
|
||||
if (_class == enumeration_class::instance)
|
||||
{
|
||||
if (vkEnumerateInstanceExtensionProperties(layer_name, &count, nullptr) != VK_SUCCESS)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ensure(pdev);
|
||||
if (vkEnumerateDeviceExtensionProperties(pdev, layer_name, &count, nullptr) != VK_SUCCESS)
|
||||
return;
|
||||
}
|
||||
|
||||
m_vk_exts.resize(count);
|
||||
if (_class == enumeration_class::instance)
|
||||
{
|
||||
vkEnumerateInstanceExtensionProperties(layer_name, &count, m_vk_exts.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
vkEnumerateDeviceExtensionProperties(pdev, layer_name, &count, m_vk_exts.data());
|
||||
}
|
||||
}
|
||||
|
||||
bool is_supported(std::string_view ext)
|
||||
{
|
||||
return std::any_of(m_vk_exts.cbegin(), m_vk_exts.cend(), [&](const VkExtensionProperties& p) { return p.extensionName == ext; });
|
||||
}
|
||||
};
|
||||
|
||||
class instance
|
||||
{
|
||||
private:
|
||||
std::vector<physical_device> gpus;
|
||||
VkInstance m_instance = VK_NULL_HANDLE;
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
|
||||
PFN_vkDestroyDebugReportCallbackEXT _vkDestroyDebugReportCallback = nullptr;
|
||||
PFN_vkCreateDebugReportCallbackEXT _vkCreateDebugReportCallback = nullptr;
|
||||
VkDebugReportCallbackEXT m_debugger = nullptr;
|
||||
|
||||
bool extensions_loaded = false;
|
||||
|
||||
public:
|
||||
|
||||
instance() = default;
|
||||
|
||||
~instance()
|
||||
{
|
||||
if (m_instance)
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void destroy()
|
||||
{
|
||||
if (!m_instance) return;
|
||||
|
||||
if (m_debugger)
|
||||
{
|
||||
_vkDestroyDebugReportCallback(m_instance, m_debugger, nullptr);
|
||||
m_debugger = nullptr;
|
||||
}
|
||||
|
||||
if (m_surface)
|
||||
{
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
m_surface = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
vkDestroyInstance(m_instance, nullptr);
|
||||
m_instance = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void enable_debugging()
|
||||
{
|
||||
if (!g_cfg.video.debug_output) return;
|
||||
|
||||
PFN_vkDebugReportCallbackEXT callback = vk::dbgFunc;
|
||||
|
||||
_vkCreateDebugReportCallback = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(m_instance, "vkCreateDebugReportCallbackEXT"));
|
||||
_vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(m_instance, "vkDestroyDebugReportCallbackEXT"));
|
||||
|
||||
VkDebugReportCallbackCreateInfoEXT dbgCreateInfo = {};
|
||||
dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
|
||||
dbgCreateInfo.pfnCallback = callback;
|
||||
dbgCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
|
||||
|
||||
CHECK_RESULT(_vkCreateDebugReportCallback(m_instance, &dbgCreateInfo, NULL, &m_debugger));
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
bool create(const char* app_name, bool fast = false)
|
||||
{
|
||||
// Initialize a vulkan instance
|
||||
VkApplicationInfo app = {};
|
||||
|
||||
app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
app.pApplicationName = app_name;
|
||||
app.applicationVersion = 0;
|
||||
app.pEngineName = app_name;
|
||||
app.engineVersion = 0;
|
||||
app.apiVersion = VK_API_VERSION_1_0;
|
||||
|
||||
// Set up instance information
|
||||
|
||||
std::vector<const char*> extensions;
|
||||
std::vector<const char*> layers;
|
||||
const void* next_info = nullptr;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Declare MVK variables here to ensure the lifetime within the entire scope
|
||||
const VkBool32 setting_true = VK_TRUE;
|
||||
const int32_t setting_fast_math = g_cfg.video.disable_msl_fast_math.get() ? MVK_CONFIG_FAST_MATH_NEVER : MVK_CONFIG_FAST_MATH_ON_DEMAND;
|
||||
|
||||
std::vector<VkLayerSettingEXT> mvk_settings;
|
||||
VkLayerSettingsCreateInfoEXT mvk_layer_settings_create_info{};
|
||||
#endif
|
||||
|
||||
if (!fast)
|
||||
{
|
||||
extensions_loaded = true;
|
||||
supported_extensions support(supported_extensions::instance);
|
||||
|
||||
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
||||
if (support.is_supported(VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (support.is_supported(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
|
||||
layers.push_back(kMVKMoltenVKDriverLayerName);
|
||||
|
||||
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true });
|
||||
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math });
|
||||
|
||||
mvk_layer_settings_create_info.sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT;
|
||||
mvk_layer_settings_create_info.pNext = next_info;
|
||||
mvk_layer_settings_create_info.settingCount = static_cast<uint32_t>(mvk_settings.size());
|
||||
mvk_layer_settings_create_info.pSettings = mvk_settings.data();
|
||||
|
||||
next_info = &mvk_layer_settings_create_info;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (support.is_supported(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (support.is_supported(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (g_cfg.video.renderdoc_compatiblity && support.is_supported(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(__APPLE__)
|
||||
extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
|
||||
#else
|
||||
bool found_surface_ext = false;
|
||||
#ifdef HAVE_X11
|
||||
if (support.is_supported(VK_KHR_XLIB_SURFACE_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||
found_surface_ext = true;
|
||||
}
|
||||
#endif
|
||||
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
|
||||
if (support.is_supported(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME))
|
||||
{
|
||||
extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
found_surface_ext = true;
|
||||
}
|
||||
#endif //(WAYLAND)
|
||||
if (!found_surface_ext)
|
||||
{
|
||||
rsx_log.error("Could not find a supported Vulkan surface extension");
|
||||
return 0;
|
||||
}
|
||||
#endif //(WIN32, __APPLE__)
|
||||
if (g_cfg.video.debug_output)
|
||||
layers.push_back("VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
|
||||
VkInstanceCreateInfo instance_info = {};
|
||||
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
instance_info.pApplicationInfo = &app;
|
||||
instance_info.enabledLayerCount = static_cast<u32>(layers.size());
|
||||
instance_info.ppEnabledLayerNames = layers.data();
|
||||
instance_info.enabledExtensionCount = fast ? 0 : static_cast<u32>(extensions.size());
|
||||
instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data();
|
||||
instance_info.pNext = next_info;
|
||||
|
||||
if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS)
|
||||
{
|
||||
if (result == VK_ERROR_LAYER_NOT_PRESENT)
|
||||
{
|
||||
rsx_log.fatal("Could not initialize layer VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
void bind()
|
||||
{
|
||||
// Register some global states
|
||||
if (m_debugger)
|
||||
{
|
||||
_vkDestroyDebugReportCallback(m_instance, m_debugger, nullptr);
|
||||
m_debugger = nullptr;
|
||||
}
|
||||
|
||||
enable_debugging();
|
||||
}
|
||||
|
||||
std::vector<physical_device>& enumerate_devices()
|
||||
{
|
||||
u32 num_gpus;
|
||||
// This may fail on unsupported drivers, so just assume no devices
|
||||
if (vkEnumeratePhysicalDevices(m_instance, &num_gpus, nullptr) != VK_SUCCESS)
|
||||
return gpus;
|
||||
|
||||
if (gpus.size() != num_gpus)
|
||||
{
|
||||
std::vector<VkPhysicalDevice> pdevs(num_gpus);
|
||||
gpus.resize(num_gpus);
|
||||
|
||||
CHECK_RESULT(vkEnumeratePhysicalDevices(m_instance, &num_gpus, pdevs.data()));
|
||||
|
||||
for (u32 i = 0; i < num_gpus; ++i)
|
||||
gpus[i].create(m_instance, pdevs[i], extensions_loaded);
|
||||
}
|
||||
|
||||
return gpus;
|
||||
}
|
||||
|
||||
swapchain_base* create_swapchain(display_handle_t window_handle, vk::physical_device& dev)
|
||||
{
|
||||
bool force_wm_reporting_off = false;
|
||||
#ifdef _WIN32
|
||||
using swapchain_NATIVE = swapchain_WIN32;
|
||||
HINSTANCE hInstance = NULL;
|
||||
|
||||
VkWin32SurfaceCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||
createInfo.hinstance = hInstance;
|
||||
createInfo.hwnd = window_handle;
|
||||
|
||||
CHECK_RESULT(vkCreateWin32SurfaceKHR(m_instance, &createInfo, NULL, &m_surface));
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
using swapchain_NATIVE = swapchain_MacOS;
|
||||
VkMacOSSurfaceCreateInfoMVK createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pView = window_handle;
|
||||
|
||||
CHECK_RESULT(vkCreateMacOSSurfaceMVK(m_instance, &createInfo, NULL, &m_surface));
|
||||
#else
|
||||
#ifdef HAVE_X11
|
||||
using swapchain_NATIVE = swapchain_X11;
|
||||
#else
|
||||
using swapchain_NATIVE = swapchain_Wayland;
|
||||
#endif
|
||||
|
||||
std::visit([&](auto&& p)
|
||||
{
|
||||
using T = std::decay_t<decltype(p)>;
|
||||
|
||||
#ifdef HAVE_X11
|
||||
if constexpr (std::is_same_v<T, std::pair<Display*, Window>>)
|
||||
{
|
||||
VkXlibSurfaceCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
|
||||
createInfo.dpy = p.first;
|
||||
createInfo.window = p.second;
|
||||
CHECK_RESULT(vkCreateXlibSurfaceKHR(this->m_instance, &createInfo, nullptr, &m_surface));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
#ifdef HAVE_WAYLAND
|
||||
if constexpr (std::is_same_v<T, std::pair<wl_display*, wl_surface*>>)
|
||||
{
|
||||
VkWaylandSurfaceCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
|
||||
createInfo.display = p.first;
|
||||
createInfo.surface = p.second;
|
||||
CHECK_RESULT(vkCreateWaylandSurfaceKHR(this->m_instance, &createInfo, nullptr, &m_surface));
|
||||
force_wm_reporting_off = true;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
static_assert(std::conditional_t<true, std::false_type, T>::value, "Unhandled window_handle type in std::variant");
|
||||
}
|
||||
}, window_handle);
|
||||
#endif
|
||||
|
||||
u32 device_queues = dev.get_queue_count();
|
||||
std::vector<VkBool32> supports_present(device_queues, VK_FALSE);
|
||||
bool present_possible = true;
|
||||
|
||||
for (u32 index = 0; index < device_queues; index++)
|
||||
{
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(dev, index, m_surface, &supports_present[index]);
|
||||
}
|
||||
|
||||
u32 graphics_queue_idx = -1;
|
||||
u32 present_queue_idx = -1;
|
||||
u32 transfer_queue_idx = -1;
|
||||
|
||||
auto test_queue_family = [&](u32 index, u32 desired_flags)
|
||||
{
|
||||
if (const auto flags = dev.get_queue_properties(index).queueFlags;
|
||||
(flags & desired_flags) == desired_flags)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < device_queues; ++i)
|
||||
{
|
||||
// 1. Test for a present queue possibly one that also supports present
|
||||
if (present_queue_idx == umax && supports_present[i])
|
||||
{
|
||||
present_queue_idx = i;
|
||||
if (test_queue_family(i, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))
|
||||
{
|
||||
graphics_queue_idx = i;
|
||||
}
|
||||
}
|
||||
// 2. Check for graphics support
|
||||
else if (graphics_queue_idx == umax && test_queue_family(i, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))
|
||||
{
|
||||
graphics_queue_idx = i;
|
||||
if (supports_present[i])
|
||||
{
|
||||
present_queue_idx = i;
|
||||
}
|
||||
}
|
||||
// 3. Check if transfer + compute is available
|
||||
else if (transfer_queue_idx == umax && test_queue_family(i, VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT))
|
||||
{
|
||||
transfer_queue_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (graphics_queue_idx == umax)
|
||||
{
|
||||
rsx_log.fatal("Failed to find a suitable graphics queue");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (graphics_queue_idx != present_queue_idx)
|
||||
{
|
||||
// Separate graphics and present, use headless fallback
|
||||
present_possible = false;
|
||||
}
|
||||
|
||||
if (!present_possible)
|
||||
{
|
||||
//Native(sw) swapchain
|
||||
rsx_log.error("It is not possible for the currently selected GPU to present to the window (Likely caused by NVIDIA driver running the current display)");
|
||||
rsx_log.warning("Falling back to software present support (native windowing API)");
|
||||
auto swapchain = new swapchain_NATIVE(dev, -1, graphics_queue_idx, transfer_queue_idx);
|
||||
swapchain->create(window_handle);
|
||||
return swapchain;
|
||||
}
|
||||
|
||||
// Get the list of VkFormat's that are supported:
|
||||
u32 formatCount;
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(dev, m_surface, &formatCount, nullptr));
|
||||
|
||||
std::vector<VkSurfaceFormatKHR> surfFormats(formatCount);
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(dev, m_surface, &formatCount, surfFormats.data()));
|
||||
|
||||
VkFormat format;
|
||||
VkColorSpaceKHR color_space;
|
||||
|
||||
if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED)
|
||||
{
|
||||
format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!formatCount) fmt::throw_exception("Format count is zero!");
|
||||
format = surfFormats[0].format;
|
||||
|
||||
//Prefer BGRA8_UNORM to avoid sRGB compression (RADV)
|
||||
for (auto& surface_format : surfFormats)
|
||||
{
|
||||
if (surface_format.format == VK_FORMAT_B8G8R8A8_UNORM)
|
||||
{
|
||||
format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color_space = surfFormats[0].colorSpace;
|
||||
|
||||
return new swapchain_WSI(dev, present_queue_idx, graphics_queue_idx, transfer_queue_idx, format, m_surface, color_space, force_wm_reporting_off);
|
||||
}
|
||||
};
|
||||
}
|
354
rpcs3/Emu/RSX/VK/vkutils/swapchain.cpp
Normal file
354
rpcs3/Emu/RSX/VK/vkutils/swapchain.cpp
Normal file
|
@ -0,0 +1,354 @@
|
|||
#include "stdafx.h"
|
||||
#include "swapchain.h"
|
||||
|
||||
namespace vk
|
||||
{
|
||||
// Swapchain image RPCS3
|
||||
swapchain_image_RPCS3::swapchain_image_RPCS3(render_device& dev, const memory_type_mapping& memory_map, u32 width, u32 height)
|
||||
:image(dev, memory_map.device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VK_IMAGE_TYPE_2D, VK_FORMAT_B8G8R8A8_UNORM, width, height, 1, 1, 1,
|
||||
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 0, VMM_ALLOCATION_POOL_SWAPCHAIN)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
current_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
|
||||
m_dma_buffer = std::make_unique<buffer>(dev, m_width * m_height * 4, memory_map.host_visible_coherent,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT, 0, VMM_ALLOCATION_POOL_SWAPCHAIN);
|
||||
}
|
||||
|
||||
void swapchain_image_RPCS3::do_dma_transfer(command_buffer& cmd)
|
||||
{
|
||||
VkBufferImageCopy copyRegion = {};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.bufferRowLength = m_width;
|
||||
copyRegion.bufferImageHeight = m_height;
|
||||
copyRegion.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
|
||||
copyRegion.imageOffset = {};
|
||||
copyRegion.imageExtent = { m_width, m_height, 1 };
|
||||
|
||||
change_layout(cmd, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
vkCmdCopyImageToBuffer(cmd, value, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_dma_buffer->value, 1, ©Region);
|
||||
change_layout(cmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
}
|
||||
|
||||
u32 swapchain_image_RPCS3::get_required_memory_size() const
|
||||
{
|
||||
return m_width * m_height * 4;
|
||||
}
|
||||
|
||||
void* swapchain_image_RPCS3::get_pixels()
|
||||
{
|
||||
return m_dma_buffer->map(0, VK_WHOLE_SIZE);
|
||||
}
|
||||
|
||||
void swapchain_image_RPCS3::free_pixels()
|
||||
{
|
||||
m_dma_buffer->unmap();
|
||||
}
|
||||
|
||||
// swapchain BASE
|
||||
swapchain_base::swapchain_base(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format)
|
||||
{
|
||||
dev.create(gpu, graphics_queue, present_queue, transfer_queue);
|
||||
m_surface_format = format;
|
||||
}
|
||||
|
||||
// NATIVE swapchain base
|
||||
VkResult native_swapchain_base::acquire_next_swapchain_image(VkSemaphore /*semaphore*/, u64 /*timeout*/, u32* result)
|
||||
{
|
||||
u32 index = 0;
|
||||
for (auto& p : swapchain_images)
|
||||
{
|
||||
if (!p.first)
|
||||
{
|
||||
p.first = true;
|
||||
*result = index;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
return VK_NOT_READY;
|
||||
}
|
||||
|
||||
void native_swapchain_base::init_swapchain_images(render_device& dev, u32 preferred_count)
|
||||
{
|
||||
swapchain_images.resize(preferred_count);
|
||||
for (auto& img : swapchain_images)
|
||||
{
|
||||
img.second = std::make_unique<swapchain_image_RPCS3>(dev, dev.get_memory_mapping(), m_width, m_height);
|
||||
img.first = false;
|
||||
}
|
||||
}
|
||||
|
||||
// WSI implementation
|
||||
void swapchain_WSI::init_swapchain_images(render_device& dev, u32 /*preferred_count*/)
|
||||
{
|
||||
u32 nb_swap_images = 0;
|
||||
_vkGetSwapchainImagesKHR(dev, m_vk_swapchain, &nb_swap_images, nullptr);
|
||||
|
||||
if (!nb_swap_images) fmt::throw_exception("Driver returned 0 images for swapchain");
|
||||
|
||||
std::vector<VkImage> vk_images;
|
||||
vk_images.resize(nb_swap_images);
|
||||
_vkGetSwapchainImagesKHR(dev, m_vk_swapchain, &nb_swap_images, vk_images.data());
|
||||
|
||||
swapchain_images.resize(nb_swap_images);
|
||||
for (u32 i = 0; i < nb_swap_images; ++i)
|
||||
{
|
||||
swapchain_images[i].value = vk_images[i];
|
||||
}
|
||||
}
|
||||
|
||||
swapchain_WSI::swapchain_WSI(vk::physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format, VkSurfaceKHR surface, VkColorSpaceKHR color_space, bool force_wm_reporting_off)
|
||||
: WSI_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{
|
||||
_vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
|
||||
_vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
|
||||
_vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
|
||||
_vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
|
||||
_vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
|
||||
|
||||
m_surface = surface;
|
||||
m_color_space = color_space;
|
||||
|
||||
if (!force_wm_reporting_off)
|
||||
{
|
||||
switch (gpu.get_driver_vendor())
|
||||
{
|
||||
case driver_vendor::AMD:
|
||||
case driver_vendor::INTEL:
|
||||
case driver_vendor::RADV:
|
||||
case driver_vendor::MVK:
|
||||
break;
|
||||
case driver_vendor::ANV:
|
||||
case driver_vendor::NVIDIA:
|
||||
m_wm_reports_flag = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void swapchain_WSI::destroy(bool)
|
||||
{
|
||||
if (VkDevice pdev = dev)
|
||||
{
|
||||
if (m_vk_swapchain)
|
||||
{
|
||||
_vkDestroySwapchainKHR(pdev, m_vk_swapchain, nullptr);
|
||||
}
|
||||
|
||||
dev.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<VkSurfaceCapabilitiesKHR, bool> swapchain_WSI::init_surface_capabilities()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (g_cfg.video.vk.exclusive_fullscreen_mode != vk_exclusive_fs_mode::unspecified && dev.get_surface_capabilities_2_support())
|
||||
{
|
||||
HMONITOR hmonitor = MonitorFromWindow(window_handle, MONITOR_DEFAULTTOPRIMARY);
|
||||
if (hmonitor)
|
||||
{
|
||||
VkSurfaceCapabilities2KHR pSurfaceCapabilities = {};
|
||||
pSurfaceCapabilities.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR;
|
||||
|
||||
VkPhysicalDeviceSurfaceInfo2KHR pSurfaceInfo = {};
|
||||
pSurfaceInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR;
|
||||
pSurfaceInfo.surface = m_surface;
|
||||
|
||||
VkSurfaceCapabilitiesFullScreenExclusiveEXT full_screen_exclusive_capabilities = {};
|
||||
VkSurfaceFullScreenExclusiveWin32InfoEXT full_screen_exclusive_win32_info = {};
|
||||
full_screen_exclusive_capabilities.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT;
|
||||
|
||||
pSurfaceCapabilities.pNext = &full_screen_exclusive_capabilities;
|
||||
|
||||
full_screen_exclusive_win32_info.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT;
|
||||
full_screen_exclusive_win32_info.hmonitor = hmonitor;
|
||||
|
||||
pSurfaceInfo.pNext = &full_screen_exclusive_win32_info;
|
||||
|
||||
auto getPhysicalDeviceSurfaceCapabilities2KHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR>(
|
||||
vkGetInstanceProcAddr(dev.gpu(), "vkGetPhysicalDeviceSurfaceCapabilities2KHR")
|
||||
);
|
||||
ensure(getPhysicalDeviceSurfaceCapabilities2KHR);
|
||||
CHECK_RESULT(getPhysicalDeviceSurfaceCapabilities2KHR(dev.gpu(), &pSurfaceInfo, &pSurfaceCapabilities));
|
||||
|
||||
return { pSurfaceCapabilities.surfaceCapabilities, !!full_screen_exclusive_capabilities.fullScreenExclusiveSupported };
|
||||
}
|
||||
else
|
||||
{
|
||||
rsx_log.warning("Swapchain: failed to get monitor for the window");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
VkSurfaceCapabilitiesKHR surface_descriptors = {};
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.gpu(), m_surface, &surface_descriptors));
|
||||
return { surface_descriptors, false };
|
||||
}
|
||||
|
||||
bool swapchain_WSI::init()
|
||||
{
|
||||
if (dev.get_present_queue() == VK_NULL_HANDLE)
|
||||
{
|
||||
rsx_log.error("Cannot create WSI swapchain without a present queue");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkSwapchainKHR old_swapchain = m_vk_swapchain;
|
||||
vk::physical_device& gpu = const_cast<vk::physical_device&>(dev.gpu());
|
||||
|
||||
auto [surface_descriptors, should_specify_exclusive_full_screen_mode] = init_surface_capabilities();
|
||||
|
||||
if (surface_descriptors.maxImageExtent.width < m_width ||
|
||||
surface_descriptors.maxImageExtent.height < m_height)
|
||||
{
|
||||
rsx_log.error("Swapchain: Swapchain creation failed because dimensions cannot fit. Max = %d, %d, Requested = %d, %d",
|
||||
surface_descriptors.maxImageExtent.width, surface_descriptors.maxImageExtent.height, m_width, m_height);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (surface_descriptors.currentExtent.width != umax)
|
||||
{
|
||||
if (surface_descriptors.currentExtent.width == 0 || surface_descriptors.currentExtent.height == 0)
|
||||
{
|
||||
rsx_log.warning("Swapchain: Current surface extent is a null region. Is the window minimized?");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = surface_descriptors.currentExtent.width;
|
||||
m_height = surface_descriptors.currentExtent.height;
|
||||
}
|
||||
|
||||
u32 nb_available_modes = 0;
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, m_surface, &nb_available_modes, nullptr));
|
||||
|
||||
std::vector<VkPresentModeKHR> present_modes(nb_available_modes);
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, m_surface, &nb_available_modes, present_modes.data()));
|
||||
|
||||
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
std::vector<VkPresentModeKHR> preferred_modes;
|
||||
|
||||
if (!g_cfg.video.vk.force_fifo)
|
||||
{
|
||||
// List of preferred modes in decreasing desirability
|
||||
// NOTE: Always picks "triple-buffered vsync" types if possible
|
||||
if (!g_cfg.video.vsync)
|
||||
{
|
||||
preferred_modes = { VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR };
|
||||
}
|
||||
}
|
||||
|
||||
bool mode_found = false;
|
||||
for (VkPresentModeKHR preferred_mode : preferred_modes)
|
||||
{
|
||||
//Search for this mode in supported modes
|
||||
for (VkPresentModeKHR mode : present_modes)
|
||||
{
|
||||
if (mode == preferred_mode)
|
||||
{
|
||||
swapchain_present_mode = mode;
|
||||
mode_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode_found)
|
||||
break;
|
||||
}
|
||||
|
||||
rsx_log.notice("Swapchain: present mode %d in use.", static_cast<int>(swapchain_present_mode));
|
||||
|
||||
u32 nb_swap_images = surface_descriptors.minImageCount + 1;
|
||||
if (surface_descriptors.maxImageCount > 0)
|
||||
{
|
||||
//Try to negotiate for a triple buffer setup
|
||||
//In cases where the front-buffer isnt available for present, its better to have a spare surface
|
||||
nb_swap_images = std::max(surface_descriptors.minImageCount + 2u, 3u);
|
||||
|
||||
if (nb_swap_images > surface_descriptors.maxImageCount)
|
||||
{
|
||||
// Application must settle for fewer images than desired:
|
||||
nb_swap_images = surface_descriptors.maxImageCount;
|
||||
}
|
||||
}
|
||||
|
||||
VkSurfaceTransformFlagBitsKHR pre_transform = surface_descriptors.currentTransform;
|
||||
if (surface_descriptors.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
|
||||
pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
|
||||
VkSwapchainCreateInfoKHR swap_info = {};
|
||||
swap_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
swap_info.surface = m_surface;
|
||||
swap_info.minImageCount = nb_swap_images;
|
||||
swap_info.imageFormat = m_surface_format;
|
||||
swap_info.imageColorSpace = m_color_space;
|
||||
|
||||
swap_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
swap_info.preTransform = pre_transform;
|
||||
swap_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
swap_info.imageArrayLayers = 1;
|
||||
swap_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
swap_info.presentMode = swapchain_present_mode;
|
||||
swap_info.oldSwapchain = old_swapchain;
|
||||
swap_info.clipped = true;
|
||||
|
||||
swap_info.imageExtent.width = std::max(m_width, surface_descriptors.minImageExtent.width);
|
||||
swap_info.imageExtent.height = std::max(m_height, surface_descriptors.minImageExtent.height);
|
||||
|
||||
#ifdef _WIN32
|
||||
VkSurfaceFullScreenExclusiveInfoEXT full_screen_exclusive_info = {};
|
||||
if (should_specify_exclusive_full_screen_mode)
|
||||
{
|
||||
vk_exclusive_fs_mode fs_mode = g_cfg.video.vk.exclusive_fullscreen_mode;
|
||||
ensure(fs_mode == vk_exclusive_fs_mode::enable || fs_mode == vk_exclusive_fs_mode::disable);
|
||||
|
||||
full_screen_exclusive_info.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT;
|
||||
full_screen_exclusive_info.fullScreenExclusive =
|
||||
fs_mode == vk_exclusive_fs_mode::enable ? VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT : VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT;
|
||||
|
||||
swap_info.pNext = &full_screen_exclusive_info;
|
||||
}
|
||||
|
||||
rsx_log.notice("Swapchain: requesting full screen exclusive mode %d.", static_cast<int>(full_screen_exclusive_info.fullScreenExclusive));
|
||||
#endif
|
||||
|
||||
_vkCreateSwapchainKHR(dev, &swap_info, nullptr, &m_vk_swapchain);
|
||||
|
||||
if (old_swapchain)
|
||||
{
|
||||
if (!swapchain_images.empty())
|
||||
{
|
||||
swapchain_images.clear();
|
||||
}
|
||||
|
||||
_vkDestroySwapchainKHR(dev, old_swapchain, nullptr);
|
||||
}
|
||||
|
||||
init_swapchain_images(dev);
|
||||
return true;
|
||||
}
|
||||
|
||||
VkResult swapchain_WSI::present(VkSemaphore semaphore, u32 image)
|
||||
{
|
||||
VkPresentInfoKHR present = {};
|
||||
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
present.pNext = nullptr;
|
||||
present.swapchainCount = 1;
|
||||
present.pSwapchains = &m_vk_swapchain;
|
||||
present.pImageIndices = ℑ
|
||||
|
||||
if (semaphore != VK_NULL_HANDLE)
|
||||
{
|
||||
present.waitSemaphoreCount = 1;
|
||||
present.pWaitSemaphores = &semaphore;
|
||||
}
|
||||
|
||||
return _vkQueuePresentKHR(dev.get_present_queue(), &present);
|
||||
}
|
||||
}
|
11
rpcs3/Emu/RSX/VK/vkutils/swapchain.h
Normal file
11
rpcs3/Emu/RSX/VK/vkutils/swapchain.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#if defined (_WIN32)
|
||||
#include "swapchain_win32.hpp"
|
||||
#elif defined (ANDROID)
|
||||
#include "swapchain_android.hpp"
|
||||
#elif defined (__APPLE__)
|
||||
#include "swapchain_macos.hpp"
|
||||
#else // Both linux and BSD families
|
||||
#include "swapchain_unix.hpp"
|
||||
#endif
|
|
@ -1,786 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xutil.h>
|
||||
#endif
|
||||
|
||||
#include "../../display.h"
|
||||
#include "../VulkanAPI.h"
|
||||
#include "image.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace vk
|
||||
{
|
||||
struct swapchain_image_WSI
|
||||
{
|
||||
VkImage value = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
class swapchain_image_RPCS3 : public image
|
||||
{
|
||||
std::unique_ptr<buffer> m_dma_buffer;
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
|
||||
public:
|
||||
swapchain_image_RPCS3(render_device& dev, const memory_type_mapping& memory_map, u32 width, u32 height)
|
||||
:image(dev, memory_map.device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VK_IMAGE_TYPE_2D, VK_FORMAT_B8G8R8A8_UNORM, width, height, 1, 1, 1,
|
||||
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 0, VMM_ALLOCATION_POOL_SWAPCHAIN)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
current_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
|
||||
m_dma_buffer = std::make_unique<buffer>(dev, m_width * m_height * 4, memory_map.host_visible_coherent,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT, 0, VMM_ALLOCATION_POOL_SWAPCHAIN);
|
||||
}
|
||||
|
||||
void do_dma_transfer(command_buffer& cmd)
|
||||
{
|
||||
VkBufferImageCopy copyRegion = {};
|
||||
copyRegion.bufferOffset = 0;
|
||||
copyRegion.bufferRowLength = m_width;
|
||||
copyRegion.bufferImageHeight = m_height;
|
||||
copyRegion.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
|
||||
copyRegion.imageOffset = {};
|
||||
copyRegion.imageExtent = { m_width, m_height, 1 };
|
||||
|
||||
change_layout(cmd, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
vkCmdCopyImageToBuffer(cmd, value, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_dma_buffer->value, 1, ©Region);
|
||||
change_layout(cmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
}
|
||||
|
||||
u32 get_required_memory_size() const
|
||||
{
|
||||
return m_width * m_height * 4;
|
||||
}
|
||||
|
||||
void* get_pixels()
|
||||
{
|
||||
return m_dma_buffer->map(0, VK_WHOLE_SIZE);
|
||||
}
|
||||
|
||||
void free_pixels()
|
||||
{
|
||||
m_dma_buffer->unmap();
|
||||
}
|
||||
};
|
||||
|
||||
class swapchain_base
|
||||
{
|
||||
protected:
|
||||
render_device dev;
|
||||
|
||||
display_handle_t window_handle{};
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
VkFormat m_surface_format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
virtual void init_swapchain_images(render_device& dev, u32 count) = 0;
|
||||
|
||||
public:
|
||||
swapchain_base(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
{
|
||||
dev.create(gpu, graphics_queue, present_queue, transfer_queue);
|
||||
m_surface_format = format;
|
||||
}
|
||||
|
||||
virtual ~swapchain_base() = default;
|
||||
|
||||
virtual void create(display_handle_t& handle) = 0;
|
||||
virtual void destroy(bool full = true) = 0;
|
||||
virtual bool init() = 0;
|
||||
|
||||
virtual u32 get_swap_image_count() const = 0;
|
||||
virtual VkImage get_image(u32 index) = 0;
|
||||
virtual VkResult acquire_next_swapchain_image(VkSemaphore semaphore, u64 timeout, u32* result) = 0;
|
||||
virtual void end_frame(command_buffer& cmd, u32 index) = 0;
|
||||
virtual VkResult present(VkSemaphore semaphore, u32 index) = 0;
|
||||
virtual VkImageLayout get_optimal_present_layout() = 0;
|
||||
|
||||
virtual bool supports_automatic_wm_reports() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool init(u32 w, u32 h)
|
||||
{
|
||||
m_width = w;
|
||||
m_height = h;
|
||||
return init();
|
||||
}
|
||||
|
||||
const vk::render_device& get_device()
|
||||
{
|
||||
return dev;
|
||||
}
|
||||
|
||||
VkFormat get_surface_format()
|
||||
{
|
||||
return m_surface_format;
|
||||
}
|
||||
|
||||
bool is_headless() const
|
||||
{
|
||||
return (dev.get_present_queue() == VK_NULL_HANDLE);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class abstract_swapchain_impl : public swapchain_base
|
||||
{
|
||||
protected:
|
||||
std::vector<T> swapchain_images;
|
||||
|
||||
public:
|
||||
abstract_swapchain_impl(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~abstract_swapchain_impl() override = default;
|
||||
|
||||
u32 get_swap_image_count() const override
|
||||
{
|
||||
return ::size32(swapchain_images);
|
||||
}
|
||||
|
||||
using swapchain_base::init;
|
||||
};
|
||||
|
||||
using native_swapchain_base = abstract_swapchain_impl<std::pair<bool, std::unique_ptr<swapchain_image_RPCS3>>>;
|
||||
using WSI_swapchain_base = abstract_swapchain_impl<swapchain_image_WSI>;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
class swapchain_WIN32 : public native_swapchain_base
|
||||
{
|
||||
HDC hDstDC = NULL;
|
||||
HDC hSrcDC = NULL;
|
||||
HBITMAP hDIB = NULL;
|
||||
LPVOID hPtr = NULL;
|
||||
|
||||
public:
|
||||
swapchain_WIN32(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: native_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~swapchain_WIN32() {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (hDIB || hSrcDC)
|
||||
destroy(false);
|
||||
|
||||
RECT rect;
|
||||
GetClientRect(window_handle, &rect);
|
||||
m_width = rect.right - rect.left;
|
||||
m_height = rect.bottom - rect.top;
|
||||
|
||||
if (m_width == 0 || m_height == 0)
|
||||
{
|
||||
rsx_log.error("Invalid window dimensions %d x %d", m_width, m_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
BITMAPINFO bitmap = {};
|
||||
bitmap.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bitmap.bmiHeader.biWidth = m_width;
|
||||
bitmap.bmiHeader.biHeight = m_height * -1;
|
||||
bitmap.bmiHeader.biPlanes = 1;
|
||||
bitmap.bmiHeader.biBitCount = 32;
|
||||
bitmap.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
hSrcDC = CreateCompatibleDC(hDstDC);
|
||||
hDIB = CreateDIBSection(hSrcDC, &bitmap, DIB_RGB_COLORS, &hPtr, NULL, 0);
|
||||
SelectObject(hSrcDC, hDIB);
|
||||
init_swapchain_images(dev, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
void create(display_handle_t& handle) override
|
||||
{
|
||||
window_handle = handle;
|
||||
hDstDC = GetDC(handle);
|
||||
}
|
||||
|
||||
void destroy(bool full = true) override
|
||||
{
|
||||
DeleteObject(hDIB);
|
||||
DeleteDC(hSrcDC);
|
||||
hDIB = NULL;
|
||||
hSrcDC = NULL;
|
||||
|
||||
swapchain_images.clear();
|
||||
|
||||
if (full)
|
||||
{
|
||||
ReleaseDC(window_handle, hDstDC);
|
||||
hDstDC = NULL;
|
||||
|
||||
dev.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore /*semaphore*/, u32 image) override
|
||||
{
|
||||
auto& src = swapchain_images[image];
|
||||
GdiFlush();
|
||||
|
||||
if (hSrcDC)
|
||||
{
|
||||
memcpy(hPtr, src.second->get_pixels(), src.second->get_required_memory_size());
|
||||
BitBlt(hDstDC, 0, 0, m_width, m_height, hSrcDC, 0, 0, SRCCOPY);
|
||||
src.second->free_pixels();
|
||||
}
|
||||
|
||||
src.first = false;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
class swapchain_MacOS : public native_swapchain_base
|
||||
{
|
||||
void* nsView = nullptr;
|
||||
|
||||
public:
|
||||
swapchain_MacOS(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: native_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~swapchain_MacOS() {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
//TODO: get from `nsView`
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
|
||||
if (m_width == 0 || m_height == 0)
|
||||
{
|
||||
rsx_log.error("Invalid window dimensions %d x %d", m_width, m_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
init_swapchain_images(dev, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
void create(display_handle_t& window_handle) override
|
||||
{
|
||||
nsView = window_handle;
|
||||
}
|
||||
|
||||
void destroy(bool full = true) override
|
||||
{
|
||||
swapchain_images.clear();
|
||||
|
||||
if (full)
|
||||
dev.destroy();
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore /*semaphore*/, u32 /*index*/) override
|
||||
{
|
||||
fmt::throw_exception("Native macOS swapchain is not implemented yet!");
|
||||
}
|
||||
#elif defined(HAVE_X11)
|
||||
|
||||
class swapchain_X11 : public native_swapchain_base
|
||||
{
|
||||
Display* display = nullptr;
|
||||
Window window = 0;
|
||||
XImage* pixmap = nullptr;
|
||||
GC gc = nullptr;
|
||||
int bit_depth = 24;
|
||||
|
||||
public:
|
||||
swapchain_X11(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: native_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~swapchain_X11() override = default;
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (pixmap)
|
||||
destroy(false);
|
||||
|
||||
Window root;
|
||||
int x, y;
|
||||
u32 w = 0, h = 0, border, depth;
|
||||
|
||||
if (XGetGeometry(display, window, &root, &x, &y, &w, &h, &border, &depth))
|
||||
{
|
||||
m_width = w;
|
||||
m_height = h;
|
||||
bit_depth = depth;
|
||||
}
|
||||
|
||||
if (m_width == 0 || m_height == 0)
|
||||
{
|
||||
rsx_log.error("Invalid window dimensions %d x %d", m_width, m_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
XVisualInfo visual{};
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
if (!XMatchVisualInfo(display, DefaultScreen(display), bit_depth, TrueColor, &visual))
|
||||
#pragma GCC diagnostic pop
|
||||
{
|
||||
rsx_log.error("Could not find matching visual info!");
|
||||
return false;
|
||||
}
|
||||
|
||||
pixmap = XCreateImage(display, visual.visual, visual.depth, ZPixmap, 0, nullptr, m_width, m_height, 32, 0);
|
||||
init_swapchain_images(dev, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
void create(display_handle_t& window_handle) override
|
||||
{
|
||||
std::visit([&](auto&& p)
|
||||
{
|
||||
using T = std::decay_t<decltype(p)>;
|
||||
if constexpr (std::is_same_v<T, std::pair<Display*, Window>>)
|
||||
{
|
||||
display = p.first;
|
||||
window = p.second;
|
||||
}
|
||||
}, window_handle);
|
||||
|
||||
if (display == NULL)
|
||||
{
|
||||
rsx_log.fatal("Could not create virtual display on this window protocol (Wayland?)");
|
||||
return;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
gc = DefaultGC(display, DefaultScreen(display));
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
void destroy(bool full = true) override
|
||||
{
|
||||
pixmap->data = nullptr;
|
||||
XDestroyImage(pixmap);
|
||||
pixmap = NULL;
|
||||
|
||||
swapchain_images.clear();
|
||||
|
||||
if (full)
|
||||
dev.destroy();
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore /*semaphore*/, u32 index) override
|
||||
{
|
||||
auto& src = swapchain_images[index];
|
||||
if (pixmap)
|
||||
{
|
||||
pixmap->data = static_cast<char*>(src.second->get_pixels());
|
||||
|
||||
XPutImage(display, window, gc, pixmap, 0, 0, 0, 0, m_width, m_height);
|
||||
XFlush(display);
|
||||
|
||||
src.second->free_pixels();
|
||||
}
|
||||
|
||||
//Release reference
|
||||
src.first = false;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
#else
|
||||
|
||||
class swapchain_Wayland : public native_swapchain_base
|
||||
{
|
||||
|
||||
public:
|
||||
swapchain_Wayland(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: native_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~swapchain_Wayland() {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
fmt::throw_exception("Native Wayland swapchain is not implemented yet!");
|
||||
}
|
||||
|
||||
void create(display_handle_t& window_handle) override
|
||||
{
|
||||
fmt::throw_exception("Native Wayland swapchain is not implemented yet!");
|
||||
}
|
||||
|
||||
void destroy(bool full = true) override
|
||||
{
|
||||
fmt::throw_exception("Native Wayland swapchain is not implemented yet!");
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore /*semaphore*/, u32 index) override
|
||||
{
|
||||
fmt::throw_exception("Native Wayland swapchain is not implemented yet!");
|
||||
}
|
||||
#endif
|
||||
|
||||
VkResult acquire_next_swapchain_image(VkSemaphore /*semaphore*/, u64 /*timeout*/, u32* result) override
|
||||
{
|
||||
u32 index = 0;
|
||||
for (auto& p : swapchain_images)
|
||||
{
|
||||
if (!p.first)
|
||||
{
|
||||
p.first = true;
|
||||
*result = index;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
return VK_NOT_READY;
|
||||
}
|
||||
|
||||
void end_frame(command_buffer& cmd, u32 index) override
|
||||
{
|
||||
swapchain_images[index].second->do_dma_transfer(cmd);
|
||||
}
|
||||
|
||||
VkImage get_image(u32 index) override
|
||||
{
|
||||
return swapchain_images[index].second->value;
|
||||
}
|
||||
|
||||
VkImageLayout get_optimal_present_layout() override
|
||||
{
|
||||
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
}
|
||||
|
||||
protected:
|
||||
void init_swapchain_images(render_device& dev, u32 preferred_count) override
|
||||
{
|
||||
swapchain_images.resize(preferred_count);
|
||||
for (auto& img : swapchain_images)
|
||||
{
|
||||
img.second = std::make_unique<swapchain_image_RPCS3>(dev, dev.get_memory_mapping(), m_width, m_height);
|
||||
img.first = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class swapchain_WSI : public WSI_swapchain_base
|
||||
{
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
VkColorSpaceKHR m_color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
VkSwapchainKHR m_vk_swapchain = nullptr;
|
||||
|
||||
PFN_vkCreateSwapchainKHR _vkCreateSwapchainKHR = nullptr;
|
||||
PFN_vkDestroySwapchainKHR _vkDestroySwapchainKHR = nullptr;
|
||||
PFN_vkGetSwapchainImagesKHR _vkGetSwapchainImagesKHR = nullptr;
|
||||
PFN_vkAcquireNextImageKHR _vkAcquireNextImageKHR = nullptr;
|
||||
PFN_vkQueuePresentKHR _vkQueuePresentKHR = nullptr;
|
||||
|
||||
bool m_wm_reports_flag = false;
|
||||
|
||||
protected:
|
||||
void init_swapchain_images(render_device& dev, u32 /*preferred_count*/ = 0) override
|
||||
{
|
||||
u32 nb_swap_images = 0;
|
||||
_vkGetSwapchainImagesKHR(dev, m_vk_swapchain, &nb_swap_images, nullptr);
|
||||
|
||||
if (!nb_swap_images) fmt::throw_exception("Driver returned 0 images for swapchain");
|
||||
|
||||
std::vector<VkImage> vk_images;
|
||||
vk_images.resize(nb_swap_images);
|
||||
_vkGetSwapchainImagesKHR(dev, m_vk_swapchain, &nb_swap_images, vk_images.data());
|
||||
|
||||
swapchain_images.resize(nb_swap_images);
|
||||
for (u32 i = 0; i < nb_swap_images; ++i)
|
||||
{
|
||||
swapchain_images[i].value = vk_images[i];
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
swapchain_WSI(vk::physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format, VkSurfaceKHR surface, VkColorSpaceKHR color_space, bool force_wm_reporting_off)
|
||||
: WSI_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{
|
||||
_vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
|
||||
_vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
|
||||
_vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
|
||||
_vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
|
||||
_vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
|
||||
|
||||
m_surface = surface;
|
||||
m_color_space = color_space;
|
||||
|
||||
if (!force_wm_reporting_off)
|
||||
{
|
||||
switch (gpu.get_driver_vendor())
|
||||
{
|
||||
case driver_vendor::AMD:
|
||||
case driver_vendor::INTEL:
|
||||
case driver_vendor::RADV:
|
||||
case driver_vendor::MVK:
|
||||
break;
|
||||
case driver_vendor::ANV:
|
||||
case driver_vendor::NVIDIA:
|
||||
m_wm_reports_flag = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~swapchain_WSI() override = default;
|
||||
|
||||
void create(display_handle_t&) override
|
||||
{}
|
||||
|
||||
void destroy(bool = true) override
|
||||
{
|
||||
if (VkDevice pdev = dev)
|
||||
{
|
||||
if (m_vk_swapchain)
|
||||
{
|
||||
_vkDestroySwapchainKHR(pdev, m_vk_swapchain, nullptr);
|
||||
}
|
||||
|
||||
dev.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<VkSurfaceCapabilitiesKHR, bool> init_surface_capabilities()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (g_cfg.video.vk.exclusive_fullscreen_mode != vk_exclusive_fs_mode::unspecified && dev.get_surface_capabilities_2_support())
|
||||
{
|
||||
HMONITOR hmonitor = MonitorFromWindow(window_handle, MONITOR_DEFAULTTOPRIMARY);
|
||||
if (hmonitor)
|
||||
{
|
||||
VkSurfaceCapabilities2KHR pSurfaceCapabilities = {};
|
||||
pSurfaceCapabilities.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR;
|
||||
|
||||
VkPhysicalDeviceSurfaceInfo2KHR pSurfaceInfo = {};
|
||||
pSurfaceInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR;
|
||||
pSurfaceInfo.surface = m_surface;
|
||||
|
||||
VkSurfaceCapabilitiesFullScreenExclusiveEXT full_screen_exclusive_capabilities = {};
|
||||
VkSurfaceFullScreenExclusiveWin32InfoEXT full_screen_exclusive_win32_info = {};
|
||||
full_screen_exclusive_capabilities.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT;
|
||||
|
||||
pSurfaceCapabilities.pNext = &full_screen_exclusive_capabilities;
|
||||
|
||||
full_screen_exclusive_win32_info.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT;
|
||||
full_screen_exclusive_win32_info.hmonitor = hmonitor;
|
||||
|
||||
pSurfaceInfo.pNext = &full_screen_exclusive_win32_info;
|
||||
|
||||
auto getPhysicalDeviceSurfaceCapabilities2KHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR>(
|
||||
vkGetInstanceProcAddr(dev.gpu(), "vkGetPhysicalDeviceSurfaceCapabilities2KHR")
|
||||
);
|
||||
ensure(getPhysicalDeviceSurfaceCapabilities2KHR);
|
||||
CHECK_RESULT(getPhysicalDeviceSurfaceCapabilities2KHR(dev.gpu(), &pSurfaceInfo, &pSurfaceCapabilities));
|
||||
|
||||
return { pSurfaceCapabilities.surfaceCapabilities, !!full_screen_exclusive_capabilities.fullScreenExclusiveSupported };
|
||||
}
|
||||
else
|
||||
{
|
||||
rsx_log.warning("Swapchain: failed to get monitor for the window");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
VkSurfaceCapabilitiesKHR surface_descriptors = {};
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.gpu(), m_surface, &surface_descriptors));
|
||||
return { surface_descriptors, false };
|
||||
}
|
||||
|
||||
using WSI_swapchain_base::init;
|
||||
bool init() override
|
||||
{
|
||||
if (dev.get_present_queue() == VK_NULL_HANDLE)
|
||||
{
|
||||
rsx_log.error("Cannot create WSI swapchain without a present queue");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkSwapchainKHR old_swapchain = m_vk_swapchain;
|
||||
vk::physical_device& gpu = const_cast<vk::physical_device&>(dev.gpu());
|
||||
|
||||
auto [surface_descriptors, should_specify_exclusive_full_screen_mode] = init_surface_capabilities();
|
||||
|
||||
if (surface_descriptors.maxImageExtent.width < m_width ||
|
||||
surface_descriptors.maxImageExtent.height < m_height)
|
||||
{
|
||||
rsx_log.error("Swapchain: Swapchain creation failed because dimensions cannot fit. Max = %d, %d, Requested = %d, %d",
|
||||
surface_descriptors.maxImageExtent.width, surface_descriptors.maxImageExtent.height, m_width, m_height);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (surface_descriptors.currentExtent.width != umax)
|
||||
{
|
||||
if (surface_descriptors.currentExtent.width == 0 || surface_descriptors.currentExtent.height == 0)
|
||||
{
|
||||
rsx_log.warning("Swapchain: Current surface extent is a null region. Is the window minimized?");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = surface_descriptors.currentExtent.width;
|
||||
m_height = surface_descriptors.currentExtent.height;
|
||||
}
|
||||
|
||||
u32 nb_available_modes = 0;
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, m_surface, &nb_available_modes, nullptr));
|
||||
|
||||
std::vector<VkPresentModeKHR> present_modes(nb_available_modes);
|
||||
CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, m_surface, &nb_available_modes, present_modes.data()));
|
||||
|
||||
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
std::vector<VkPresentModeKHR> preferred_modes;
|
||||
|
||||
if (!g_cfg.video.vk.force_fifo)
|
||||
{
|
||||
// List of preferred modes in decreasing desirability
|
||||
// NOTE: Always picks "triple-buffered vsync" types if possible
|
||||
if (!g_cfg.video.vsync)
|
||||
{
|
||||
preferred_modes = { VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR };
|
||||
}
|
||||
}
|
||||
|
||||
bool mode_found = false;
|
||||
for (VkPresentModeKHR preferred_mode : preferred_modes)
|
||||
{
|
||||
//Search for this mode in supported modes
|
||||
for (VkPresentModeKHR mode : present_modes)
|
||||
{
|
||||
if (mode == preferred_mode)
|
||||
{
|
||||
swapchain_present_mode = mode;
|
||||
mode_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode_found)
|
||||
break;
|
||||
}
|
||||
|
||||
rsx_log.notice("Swapchain: present mode %d in use.", static_cast<int>(swapchain_present_mode));
|
||||
|
||||
u32 nb_swap_images = surface_descriptors.minImageCount + 1;
|
||||
if (surface_descriptors.maxImageCount > 0)
|
||||
{
|
||||
//Try to negotiate for a triple buffer setup
|
||||
//In cases where the front-buffer isnt available for present, its better to have a spare surface
|
||||
nb_swap_images = std::max(surface_descriptors.minImageCount + 2u, 3u);
|
||||
|
||||
if (nb_swap_images > surface_descriptors.maxImageCount)
|
||||
{
|
||||
// Application must settle for fewer images than desired:
|
||||
nb_swap_images = surface_descriptors.maxImageCount;
|
||||
}
|
||||
}
|
||||
|
||||
VkSurfaceTransformFlagBitsKHR pre_transform = surface_descriptors.currentTransform;
|
||||
if (surface_descriptors.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
|
||||
pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
|
||||
VkSwapchainCreateInfoKHR swap_info = {};
|
||||
swap_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
swap_info.surface = m_surface;
|
||||
swap_info.minImageCount = nb_swap_images;
|
||||
swap_info.imageFormat = m_surface_format;
|
||||
swap_info.imageColorSpace = m_color_space;
|
||||
|
||||
swap_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
swap_info.preTransform = pre_transform;
|
||||
swap_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
swap_info.imageArrayLayers = 1;
|
||||
swap_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
swap_info.presentMode = swapchain_present_mode;
|
||||
swap_info.oldSwapchain = old_swapchain;
|
||||
swap_info.clipped = true;
|
||||
|
||||
swap_info.imageExtent.width = std::max(m_width, surface_descriptors.minImageExtent.width);
|
||||
swap_info.imageExtent.height = std::max(m_height, surface_descriptors.minImageExtent.height);
|
||||
|
||||
#ifdef _WIN32
|
||||
VkSurfaceFullScreenExclusiveInfoEXT full_screen_exclusive_info = {};
|
||||
if (should_specify_exclusive_full_screen_mode)
|
||||
{
|
||||
vk_exclusive_fs_mode fs_mode = g_cfg.video.vk.exclusive_fullscreen_mode;
|
||||
ensure(fs_mode == vk_exclusive_fs_mode::enable || fs_mode == vk_exclusive_fs_mode::disable);
|
||||
|
||||
full_screen_exclusive_info.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT;
|
||||
full_screen_exclusive_info.fullScreenExclusive =
|
||||
fs_mode == vk_exclusive_fs_mode::enable ? VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT : VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT;
|
||||
|
||||
swap_info.pNext = &full_screen_exclusive_info;
|
||||
}
|
||||
|
||||
rsx_log.notice("Swapchain: requesting full screen exclusive mode %d.", static_cast<int>(full_screen_exclusive_info.fullScreenExclusive));
|
||||
#endif
|
||||
|
||||
_vkCreateSwapchainKHR(dev, &swap_info, nullptr, &m_vk_swapchain);
|
||||
|
||||
if (old_swapchain)
|
||||
{
|
||||
if (!swapchain_images.empty())
|
||||
{
|
||||
swapchain_images.clear();
|
||||
}
|
||||
|
||||
_vkDestroySwapchainKHR(dev, old_swapchain, nullptr);
|
||||
}
|
||||
|
||||
init_swapchain_images(dev);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool supports_automatic_wm_reports() const override
|
||||
{
|
||||
return m_wm_reports_flag;
|
||||
}
|
||||
|
||||
VkResult acquire_next_swapchain_image(VkSemaphore semaphore, u64 timeout, u32* result) override
|
||||
{
|
||||
return vkAcquireNextImageKHR(dev, m_vk_swapchain, timeout, semaphore, VK_NULL_HANDLE, result);
|
||||
}
|
||||
|
||||
void end_frame(command_buffer& /*cmd*/, u32 /*index*/) override
|
||||
{
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore semaphore, u32 image) override
|
||||
{
|
||||
VkPresentInfoKHR present = {};
|
||||
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
present.pNext = nullptr;
|
||||
present.swapchainCount = 1;
|
||||
present.pSwapchains = &m_vk_swapchain;
|
||||
present.pImageIndices = ℑ
|
||||
|
||||
if (semaphore != VK_NULL_HANDLE)
|
||||
{
|
||||
present.waitSemaphoreCount = 1;
|
||||
present.pWaitSemaphores = &semaphore;
|
||||
}
|
||||
|
||||
return _vkQueuePresentKHR(dev.get_present_queue(), &present);
|
||||
}
|
||||
|
||||
VkImage get_image(u32 index) override
|
||||
{
|
||||
return swapchain_images[index].value;
|
||||
}
|
||||
|
||||
VkImageLayout get_optimal_present_layout() override
|
||||
{
|
||||
return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
}
|
||||
};
|
||||
}
|
19
rpcs3/Emu/RSX/VK/vkutils/swapchain_android.hpp
Normal file
19
rpcs3/Emu/RSX/VK/vkutils/swapchain_android.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "swapchain_core.h"
|
||||
|
||||
namespace vk
|
||||
{
|
||||
#if defined(ANDROID)
|
||||
using swapchain_ANDROID = native_swapchain_base;
|
||||
using swapchain_NATIVE = swapchain_ANDROID;
|
||||
|
||||
// TODO: Implement this
|
||||
[[maybe_unused]] static
|
||||
VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/)
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
227
rpcs3/Emu/RSX/VK/vkutils/swapchain_core.h
Normal file
227
rpcs3/Emu/RSX/VK/vkutils/swapchain_core.h
Normal file
|
@ -0,0 +1,227 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xutil.h>
|
||||
#endif
|
||||
|
||||
#include "../../display.h"
|
||||
#include "../VulkanAPI.h"
|
||||
#include "image.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace vk
|
||||
{
|
||||
struct swapchain_image_WSI
|
||||
{
|
||||
VkImage value = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
class swapchain_image_RPCS3 : public image
|
||||
{
|
||||
std::unique_ptr<buffer> m_dma_buffer;
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
|
||||
public:
|
||||
swapchain_image_RPCS3(render_device& dev, const memory_type_mapping& memory_map, u32 width, u32 height);
|
||||
|
||||
void do_dma_transfer(command_buffer& cmd);
|
||||
|
||||
u32 get_required_memory_size() const;
|
||||
|
||||
void* get_pixels();
|
||||
|
||||
void free_pixels();
|
||||
};
|
||||
|
||||
class swapchain_base
|
||||
{
|
||||
protected:
|
||||
render_device dev;
|
||||
|
||||
display_handle_t window_handle{};
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
VkFormat m_surface_format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
virtual void init_swapchain_images(render_device& dev, u32 count) = 0;
|
||||
|
||||
public:
|
||||
swapchain_base(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM);
|
||||
|
||||
virtual ~swapchain_base() = default;
|
||||
|
||||
virtual void create(display_handle_t& handle) = 0;
|
||||
virtual void destroy(bool full = true) = 0;
|
||||
virtual bool init() = 0;
|
||||
|
||||
virtual u32 get_swap_image_count() const = 0;
|
||||
virtual VkImage get_image(u32 index) = 0;
|
||||
virtual VkResult acquire_next_swapchain_image(VkSemaphore semaphore, u64 timeout, u32* result) = 0;
|
||||
virtual void end_frame(command_buffer& cmd, u32 index) = 0;
|
||||
virtual VkResult present(VkSemaphore semaphore, u32 index) = 0;
|
||||
virtual VkImageLayout get_optimal_present_layout() const = 0;
|
||||
|
||||
virtual bool supports_automatic_wm_reports() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool init(u32 w, u32 h)
|
||||
{
|
||||
m_width = w;
|
||||
m_height = h;
|
||||
return init();
|
||||
}
|
||||
|
||||
const vk::render_device& get_device()
|
||||
{
|
||||
return dev;
|
||||
}
|
||||
|
||||
VkFormat get_surface_format() const
|
||||
{
|
||||
return m_surface_format;
|
||||
}
|
||||
|
||||
bool is_headless() const
|
||||
{
|
||||
return (dev.get_present_queue() == VK_NULL_HANDLE);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class abstract_swapchain_impl : public swapchain_base
|
||||
{
|
||||
protected:
|
||||
std::vector<T> swapchain_images;
|
||||
|
||||
public:
|
||||
abstract_swapchain_impl(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~abstract_swapchain_impl() override = default;
|
||||
|
||||
u32 get_swap_image_count() const override
|
||||
{
|
||||
return ::size32(swapchain_images);
|
||||
}
|
||||
|
||||
using swapchain_base::init;
|
||||
};
|
||||
|
||||
using WSI_swapchain_base = abstract_swapchain_impl<swapchain_image_WSI>;
|
||||
|
||||
class native_swapchain_base : public abstract_swapchain_impl<std::pair<bool, std::unique_ptr<swapchain_image_RPCS3>>>
|
||||
{
|
||||
public:
|
||||
using abstract_swapchain_impl::abstract_swapchain_impl;
|
||||
|
||||
VkResult acquire_next_swapchain_image(VkSemaphore semaphore, u64 timeout, u32* result) override;
|
||||
|
||||
// Clients must implement these methods to render without WSI support
|
||||
bool init() override
|
||||
{
|
||||
fmt::throw_exception("Native swapchain is not implemented yet!");
|
||||
}
|
||||
|
||||
void create(display_handle_t& /*window_handle*/) override
|
||||
{
|
||||
fmt::throw_exception("Native swapchain is not implemented yet!");
|
||||
}
|
||||
|
||||
void destroy(bool /*full*/ = true) override
|
||||
{
|
||||
fmt::throw_exception("Native swapchain is not implemented yet!");
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore /*semaphore*/, u32 /*index*/) override
|
||||
{
|
||||
fmt::throw_exception("Native swapchain is not implemented yet!");
|
||||
}
|
||||
|
||||
// Generic accessors
|
||||
void end_frame(command_buffer& cmd, u32 index) override
|
||||
{
|
||||
swapchain_images[index].second->do_dma_transfer(cmd);
|
||||
}
|
||||
|
||||
VkImage get_image(u32 index) override
|
||||
{
|
||||
return swapchain_images[index].second->value;
|
||||
}
|
||||
|
||||
VkImageLayout get_optimal_present_layout() const override
|
||||
{
|
||||
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
}
|
||||
|
||||
protected:
|
||||
void init_swapchain_images(render_device& dev, u32 preferred_count) override;
|
||||
};
|
||||
|
||||
class swapchain_WSI : public WSI_swapchain_base
|
||||
{
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
VkColorSpaceKHR m_color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
VkSwapchainKHR m_vk_swapchain = nullptr;
|
||||
|
||||
PFN_vkCreateSwapchainKHR _vkCreateSwapchainKHR = nullptr;
|
||||
PFN_vkDestroySwapchainKHR _vkDestroySwapchainKHR = nullptr;
|
||||
PFN_vkGetSwapchainImagesKHR _vkGetSwapchainImagesKHR = nullptr;
|
||||
PFN_vkAcquireNextImageKHR _vkAcquireNextImageKHR = nullptr;
|
||||
PFN_vkQueuePresentKHR _vkQueuePresentKHR = nullptr;
|
||||
|
||||
bool m_wm_reports_flag = false;
|
||||
|
||||
protected:
|
||||
void init_swapchain_images(render_device& dev, u32 preferred_count = 0) override;
|
||||
|
||||
public:
|
||||
swapchain_WSI(vk::physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format, VkSurfaceKHR surface, VkColorSpaceKHR color_space, bool force_wm_reporting_off);
|
||||
|
||||
~swapchain_WSI() override = default;
|
||||
|
||||
void create(display_handle_t&) override
|
||||
{}
|
||||
|
||||
void destroy(bool = true) override;
|
||||
|
||||
std::pair<VkSurfaceCapabilitiesKHR, bool> init_surface_capabilities();
|
||||
|
||||
using WSI_swapchain_base::init;
|
||||
bool init() override;
|
||||
|
||||
bool supports_automatic_wm_reports() const override
|
||||
{
|
||||
return m_wm_reports_flag;
|
||||
}
|
||||
|
||||
VkResult acquire_next_swapchain_image(VkSemaphore semaphore, u64 timeout, u32* result) override
|
||||
{
|
||||
return vkAcquireNextImageKHR(dev, m_vk_swapchain, timeout, semaphore, VK_NULL_HANDLE, result);
|
||||
}
|
||||
|
||||
void end_frame(command_buffer& /*cmd*/, u32 /*index*/) override
|
||||
{}
|
||||
|
||||
VkResult present(VkSemaphore semaphore, u32 image) override;
|
||||
|
||||
VkImage get_image(u32 index) override
|
||||
{
|
||||
return swapchain_images[index].value;
|
||||
}
|
||||
|
||||
VkImageLayout get_optimal_present_layout() const override
|
||||
{
|
||||
return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
}
|
||||
};
|
||||
|
||||
struct WSI_config
|
||||
{
|
||||
bool supports_automatic_wm_reports = true;
|
||||
};
|
||||
}
|
23
rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp
Normal file
23
rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "swapchain_core.h"
|
||||
|
||||
namespace vk
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
using swapchain_MacOS = native_swapchain_base;
|
||||
using swapchain_NATIVE = swapchain_MacOS;
|
||||
|
||||
[[maybe_unused]] static
|
||||
VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/)
|
||||
{
|
||||
VkSurfaceKHR result = VK_NULL_HANDLE;
|
||||
VkMacOSSurfaceCreateInfoMVK createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pView = window_handle;
|
||||
|
||||
CHECK_RESULT(vkCreateMacOSSurfaceMVK(vk_instance, &createInfo, NULL, &result));
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
172
rpcs3/Emu/RSX/VK/vkutils/swapchain_unix.hpp
Normal file
172
rpcs3/Emu/RSX/VK/vkutils/swapchain_unix.hpp
Normal file
|
@ -0,0 +1,172 @@
|
|||
#pragma once
|
||||
|
||||
#include "swapchain_core.h"
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xutil.h>
|
||||
#endif
|
||||
|
||||
namespace vk
|
||||
{
|
||||
#if defined(HAVE_X11)
|
||||
|
||||
class swapchain_X11 : public native_swapchain_base
|
||||
{
|
||||
Display* display = nullptr;
|
||||
Window window = 0;
|
||||
XImage* pixmap = nullptr;
|
||||
GC gc = nullptr;
|
||||
int bit_depth = 24;
|
||||
|
||||
public:
|
||||
swapchain_X11(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: native_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~swapchain_X11() override = default;
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (pixmap)
|
||||
destroy(false);
|
||||
|
||||
Window root;
|
||||
int x, y;
|
||||
u32 w = 0, h = 0, border, depth;
|
||||
|
||||
if (XGetGeometry(display, window, &root, &x, &y, &w, &h, &border, &depth))
|
||||
{
|
||||
m_width = w;
|
||||
m_height = h;
|
||||
bit_depth = depth;
|
||||
}
|
||||
|
||||
if (m_width == 0 || m_height == 0)
|
||||
{
|
||||
rsx_log.error("Invalid window dimensions %d x %d", m_width, m_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
XVisualInfo visual{};
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
if (!XMatchVisualInfo(display, DefaultScreen(display), bit_depth, TrueColor, &visual))
|
||||
#pragma GCC diagnostic pop
|
||||
{
|
||||
rsx_log.error("Could not find matching visual info!");
|
||||
return false;
|
||||
}
|
||||
|
||||
pixmap = XCreateImage(display, visual.visual, visual.depth, ZPixmap, 0, nullptr, m_width, m_height, 32, 0);
|
||||
init_swapchain_images(dev, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
void create(display_handle_t& window_handle) override
|
||||
{
|
||||
std::visit([&](auto&& p)
|
||||
{
|
||||
using T = std::decay_t<decltype(p)>;
|
||||
if constexpr (std::is_same_v<T, std::pair<Display*, Window>>)
|
||||
{
|
||||
display = p.first;
|
||||
window = p.second;
|
||||
}
|
||||
}, window_handle);
|
||||
|
||||
if (display == NULL)
|
||||
{
|
||||
rsx_log.fatal("Could not create virtual display on this window protocol (Wayland?)");
|
||||
return;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
gc = DefaultGC(display, DefaultScreen(display));
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
void destroy(bool full = true) override
|
||||
{
|
||||
pixmap->data = nullptr;
|
||||
XDestroyImage(pixmap);
|
||||
pixmap = NULL;
|
||||
|
||||
swapchain_images.clear();
|
||||
|
||||
if (full)
|
||||
dev.destroy();
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore /*semaphore*/, u32 index) override
|
||||
{
|
||||
auto& src = swapchain_images[index];
|
||||
if (pixmap)
|
||||
{
|
||||
pixmap->data = static_cast<char*>(src.second->get_pixels());
|
||||
|
||||
XPutImage(display, window, gc, pixmap, 0, 0, 0, 0, m_width, m_height);
|
||||
XFlush(display);
|
||||
|
||||
src.second->free_pixels();
|
||||
}
|
||||
|
||||
//Release reference
|
||||
src.first = false;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
using swapchain_NATIVE = swapchain_X11;
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_WAYLAND)
|
||||
using swapchain_Wayland = native_swapchain_base;
|
||||
|
||||
#ifndef HAVE_X11
|
||||
using swapchain_NATIVE = swapchain_Wayland;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
[[maybe_unused]] static
|
||||
VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* config)
|
||||
{
|
||||
VkSurfaceKHR result = VK_NULL_HANDLE;
|
||||
|
||||
std::visit([&](auto&& p)
|
||||
{
|
||||
using T = std::decay_t<decltype(p)>;
|
||||
|
||||
#ifdef HAVE_X11
|
||||
if constexpr (std::is_same_v<T, std::pair<Display*, Window>>)
|
||||
{
|
||||
VkXlibSurfaceCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
|
||||
createInfo.dpy = p.first;
|
||||
createInfo.window = p.second;
|
||||
CHECK_RESULT(vkCreateXlibSurfaceKHR(vk_instance, &createInfo, nullptr, &result));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
#ifdef HAVE_WAYLAND
|
||||
if constexpr (std::is_same_v<T, std::pair<wl_display*, wl_surface*>>)
|
||||
{
|
||||
VkWaylandSurfaceCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
|
||||
createInfo.display = p.first;
|
||||
createInfo.surface = p.second;
|
||||
CHECK_RESULT(vkCreateWaylandSurfaceKHR(vk_instance, &createInfo, nullptr, &result));
|
||||
config->supports_automatic_wm_reports = false;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
static_assert(std::conditional_t<true, std::false_type, T>::value, "Unhandled window_handle type in std::variant");
|
||||
}
|
||||
}, window_handle);
|
||||
|
||||
return ensure(result, "Failed to initialize Vulkan display surface");
|
||||
}
|
||||
}
|
110
rpcs3/Emu/RSX/VK/vkutils/swapchain_win32.hpp
Normal file
110
rpcs3/Emu/RSX/VK/vkutils/swapchain_win32.hpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#pragma once
|
||||
|
||||
#include "swapchain_core.h"
|
||||
|
||||
namespace vk
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
class swapchain_WIN32 : public native_swapchain_base
|
||||
{
|
||||
HDC hDstDC = NULL;
|
||||
HDC hSrcDC = NULL;
|
||||
HBITMAP hDIB = NULL;
|
||||
LPVOID hPtr = NULL;
|
||||
|
||||
public:
|
||||
swapchain_WIN32(physical_device& gpu, u32 present_queue, u32 graphics_queue, u32 transfer_queue, VkFormat format = VK_FORMAT_B8G8R8A8_UNORM)
|
||||
: native_swapchain_base(gpu, present_queue, graphics_queue, transfer_queue, format)
|
||||
{}
|
||||
|
||||
~swapchain_WIN32() {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (hDIB || hSrcDC)
|
||||
destroy(false);
|
||||
|
||||
RECT rect;
|
||||
GetClientRect(window_handle, &rect);
|
||||
m_width = rect.right - rect.left;
|
||||
m_height = rect.bottom - rect.top;
|
||||
|
||||
if (m_width == 0 || m_height == 0)
|
||||
{
|
||||
rsx_log.error("Invalid window dimensions %d x %d", m_width, m_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
BITMAPINFO bitmap = {};
|
||||
bitmap.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bitmap.bmiHeader.biWidth = m_width;
|
||||
bitmap.bmiHeader.biHeight = m_height * -1;
|
||||
bitmap.bmiHeader.biPlanes = 1;
|
||||
bitmap.bmiHeader.biBitCount = 32;
|
||||
bitmap.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
hSrcDC = CreateCompatibleDC(hDstDC);
|
||||
hDIB = CreateDIBSection(hSrcDC, &bitmap, DIB_RGB_COLORS, &hPtr, NULL, 0);
|
||||
SelectObject(hSrcDC, hDIB);
|
||||
init_swapchain_images(dev, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
void create(display_handle_t& handle) override
|
||||
{
|
||||
window_handle = handle;
|
||||
hDstDC = GetDC(handle);
|
||||
}
|
||||
|
||||
void destroy(bool full = true) override
|
||||
{
|
||||
DeleteObject(hDIB);
|
||||
DeleteDC(hSrcDC);
|
||||
hDIB = NULL;
|
||||
hSrcDC = NULL;
|
||||
|
||||
swapchain_images.clear();
|
||||
|
||||
if (full)
|
||||
{
|
||||
ReleaseDC(window_handle, hDstDC);
|
||||
hDstDC = NULL;
|
||||
|
||||
dev.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
VkResult present(VkSemaphore /*semaphore*/, u32 image) override
|
||||
{
|
||||
auto& src = swapchain_images[image];
|
||||
GdiFlush();
|
||||
|
||||
if (hSrcDC)
|
||||
{
|
||||
memcpy(hPtr, src.second->get_pixels(), src.second->get_required_memory_size());
|
||||
BitBlt(hDstDC, 0, 0, m_width, m_height, hSrcDC, 0, 0, SRCCOPY);
|
||||
src.second->free_pixels();
|
||||
}
|
||||
|
||||
src.first = false;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
using swapchain_NATIVE = swapchain_WIN32;
|
||||
|
||||
[[maybe_unused]] static
|
||||
VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/)
|
||||
{
|
||||
HINSTANCE hInstance = NULL;
|
||||
VkSurfaceKHR result = VK_NULL_HANDLE;
|
||||
|
||||
VkWin32SurfaceCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||
createInfo.hinstance = hInstance;
|
||||
createInfo.hwnd = window_handle;
|
||||
CHECK_RESULT(vkCreateWin32SurfaceKHR(vk_instance, &createInfo, NULL, &result));
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -276,6 +276,8 @@ enum class localized_string_id
|
|||
HOME_MENU_TROPHY_LOCKED_TITLE,
|
||||
HOME_MENU_TROPHY_HIDDEN_TITLE,
|
||||
HOME_MENU_TROPHY_HIDDEN_DESCRIPTION,
|
||||
HOME_MENU_TROPHY_SHOW_HIDDEN_TROPHIES,
|
||||
HOME_MENU_TROPHY_HIDE_HIDDEN_TROPHIES,
|
||||
HOME_MENU_TROPHY_PLATINUM_RELEVANT,
|
||||
HOME_MENU_TROPHY_GRADE_BRONZE,
|
||||
HOME_MENU_TROPHY_GRADE_SILVER,
|
||||
|
|
|
@ -49,7 +49,12 @@
|
|||
<ClInclude Include="Emu\RSX\VK\vkutils\image.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\image_helpers.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\scratch.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_android.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_core.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_unix.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_macos.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_win32.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\sync.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\graphics_pipeline_state.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\memory.h" />
|
||||
|
@ -58,7 +63,7 @@
|
|||
<ClInclude Include="Emu\RSX\VK\vkutils\query_pool.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\sampler.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\shared.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\instance.hpp" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\instance.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\VKVertexProgram.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\VulkanAPI.h" />
|
||||
</ItemGroup>
|
||||
|
@ -94,7 +99,9 @@
|
|||
<ClCompile Include="Emu\RSX\VK\vkutils\data_heap.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\image.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\image_helpers.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\instance.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\scratch.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\swapchain.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\sync.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\memory.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\descriptors.cpp" />
|
||||
|
|
|
@ -73,6 +73,12 @@
|
|||
<Filter>upscalers\fsr1</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\RSX\VK\VKCommonPipelineLayout.cpp" />
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\instance.cpp">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\RSX\VK\vkutils\swapchain.cpp">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Emu\RSX\VK\VKCommonDecompiler.h" />
|
||||
|
@ -124,7 +130,7 @@
|
|||
<ClInclude Include="Emu\RSX\VK\vkutils\device.h">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\instance.hpp">
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\instance.h">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\memory.h">
|
||||
|
@ -139,7 +145,7 @@
|
|||
<ClInclude Include="Emu\RSX\VK\vkutils\image.h">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain.hpp">
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain.h">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\descriptors.h">
|
||||
|
@ -175,6 +181,21 @@
|
|||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\VKCommonPipelineLayout.h" />
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_win32.hpp">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_unix.hpp">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_macos.hpp">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_android.hpp">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\VK\vkutils\swapchain_core.h">
|
||||
<Filter>vkutils</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="vkutils">
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace rpcs3
|
|||
// Currently accessible by Windows and Linux build scripts, see implementations when doing MACOSX
|
||||
const utils::version& get_version()
|
||||
{
|
||||
static constexpr utils::version version{ 0, 0, 34, utils::version_type::alpha, 1, RPCS3_GIT_VERSION };
|
||||
static constexpr utils::version version{ 0, 0, 35, utils::version_type::alpha, 1, RPCS3_GIT_VERSION };
|
||||
return version;
|
||||
}
|
||||
|
||||
|
|
|
@ -297,6 +297,8 @@ private:
|
|||
case localized_string_id::HOME_MENU_TROPHY_LOCKED_TITLE: return tr("Locked trophy: %0").arg(std::forward<Args>(args)...);
|
||||
case localized_string_id::HOME_MENU_TROPHY_HIDDEN_TITLE: return tr("Hidden trophy");
|
||||
case localized_string_id::HOME_MENU_TROPHY_HIDDEN_DESCRIPTION: return tr("This trophy is hidden");
|
||||
case localized_string_id::HOME_MENU_TROPHY_SHOW_HIDDEN_TROPHIES: return tr("Show hidden trophies");
|
||||
case localized_string_id::HOME_MENU_TROPHY_HIDE_HIDDEN_TROPHIES: return tr("Hide hidden trophies");
|
||||
case localized_string_id::HOME_MENU_TROPHY_PLATINUM_RELEVANT: return tr("Platinum relevant");
|
||||
case localized_string_id::HOME_MENU_TROPHY_GRADE_BRONZE: return tr("Bronze", "Trophy type");
|
||||
case localized_string_id::HOME_MENU_TROPHY_GRADE_SILVER: return tr("Silver", "Trophy type");
|
||||
|
|
|
@ -549,13 +549,12 @@ void main_window::show_boot_error(game_boot_result status)
|
|||
}
|
||||
const QString link = tr("<br /><br />For information on setting up the emulator and dumping your PS3 games, read the <a %0 href=\"https://rpcs3.net/quickstart\">quickstart guide</a>.").arg(gui::utils::get_link_style());
|
||||
|
||||
QMessageBox* msg = new QMessageBox();
|
||||
QMessageBox* msg = new QMessageBox(this);
|
||||
msg->setWindowTitle(tr("Boot Failed"));
|
||||
msg->setIcon(QMessageBox::Critical);
|
||||
msg->setTextFormat(Qt::RichText);
|
||||
msg->setStandardButtons(QMessageBox::Ok);
|
||||
msg->setText(tr("Booting failed: %1 %2").arg(message).arg(link));
|
||||
msg->setParent(this);
|
||||
msg->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msg->open();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include "Utilities/Thread.h"
|
||||
|
||||
#if defined(HAVE_VULKAN)
|
||||
#include "Emu/RSX/VK/vkutils/instance.hpp"
|
||||
#include "Emu/RSX/VK/vkutils/instance.h"
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
|
|
Loading…
Add table
Reference in a new issue