Compare commits
23 Commits
test-donot
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
27b3ac58c5
|
|||
|
6609c6ddcd
|
|||
| 5fa81dae7a | |||
| b3eba6a275 | |||
| c486334c78 | |||
| 7dc30a5e2d | |||
| b16cece0a6 | |||
| 19bbd9894e | |||
|
3bb0d29aa1
|
|||
| cf4f813145 | |||
|
3290ed80d8
|
|||
| a71a82d3b7 | |||
| bd1d270e97 | |||
| 664ff7cc85 | |||
| b5c86787ab | |||
|
|
cfae726289 | ||
|
|
bb94cff886 | ||
|
|
370997f42e | ||
|
|
5213cc5689 | ||
|
|
bc9af86269 | ||
|
|
00ec67d65b | ||
|
|
fbd28a9d34 | ||
|
|
3413fbd9da |
159
.gitea/workflows/build-appimage.yml
Normal file
159
.gitea/workflows/build-appimage.yml
Normal file
@@ -0,0 +1,159 @@
|
||||
name: Build Linux AppImage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
release:
|
||||
types: [created, published]
|
||||
|
||||
jobs:
|
||||
build-appimage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
git \
|
||||
curl \
|
||||
zip \
|
||||
unzip \
|
||||
xvfb \
|
||||
qt6-base-dev \
|
||||
qt6-base-private-dev \
|
||||
qt6-svg-dev \
|
||||
qt6-multimedia-dev \
|
||||
qt6-tools-dev \
|
||||
libglew-dev \
|
||||
libglfw3-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libudev-dev \
|
||||
libavcodec-dev \
|
||||
libavformat-dev \
|
||||
libswscale-dev \
|
||||
libavfilter-dev \
|
||||
libfmt-dev \
|
||||
libgl1-mesa-dev \
|
||||
libxrandr-dev \
|
||||
libxi-dev \
|
||||
libxcursor-dev \
|
||||
libxinerama-dev \
|
||||
libsamplerate-dev \
|
||||
libasound2-dev \
|
||||
libpulse-dev \
|
||||
libsndio-dev \
|
||||
nlohmann-json3-dev \
|
||||
libboost-context-dev \
|
||||
libboost-filesystem-dev \
|
||||
libzstd-dev \
|
||||
liblz4-dev \
|
||||
libsdl2-dev \
|
||||
catch2 \
|
||||
libvulkan-dev \
|
||||
glslang-tools \
|
||||
spirv-tools \
|
||||
zsync \
|
||||
wget
|
||||
|
||||
- name: Clean build directory
|
||||
run: rm -rf build
|
||||
|
||||
- name: Build Eden
|
||||
run: |
|
||||
export TARGET=appimage
|
||||
export USE_MULTIMEDIA=false
|
||||
.ci/linux/build.sh amd64
|
||||
env:
|
||||
NPROC: 2
|
||||
|
||||
- name: Package AppImage
|
||||
run: |
|
||||
export USE_MULTIMEDIA=false
|
||||
.ci/linux/package.sh amd64
|
||||
|
||||
- name: Get version info
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "commit=${COMMIT}" >> $GITHUB_OUTPUT
|
||||
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: jdlo-eden-linux
|
||||
path: |
|
||||
Eden-*.AppImage
|
||||
Eden-*.AppImage.zsync
|
||||
retention-days: 30
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create release info
|
||||
run: |
|
||||
echo "# Build Information" > build-info.txt
|
||||
echo "Version: ${{ steps.version.outputs.version }}" >> build-info.txt
|
||||
echo "Commit: ${{ steps.version.outputs.commit }}" >> build-info.txt
|
||||
echo "Build Date: ${{ steps.version.outputs.date }}" >> build-info.txt
|
||||
echo "Architecture: amd64-v3" >> build-info.txt
|
||||
echo "Multimedia: disabled" >> build-info.txt
|
||||
ls -lh Eden-*.AppImage >> build-info.txt
|
||||
cat build-info.txt
|
||||
|
||||
- name: Upload build info
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-info
|
||||
path: build-info.txt
|
||||
retention-days: 30
|
||||
|
||||
- name: Prepare release files
|
||||
if: github.event_name == 'release'
|
||||
id: prepare
|
||||
run: |
|
||||
# Find and rename files to simpler names
|
||||
APPIMAGE_FILE=$(ls Eden-*.AppImage | head -n 1)
|
||||
ZSYNC_FILE=$(ls Eden-*.AppImage.zsync | head -n 1)
|
||||
|
||||
cp "$APPIMAGE_FILE" jdlo-eden.AppImage
|
||||
cp "$ZSYNC_FILE" jdlo-eden.AppImage.zsync
|
||||
|
||||
# Generate MD5 hash
|
||||
MD5_HASH=$(md5sum "jdlo-eden.AppImage" | awk '{print $1}')
|
||||
|
||||
# Create release body with MD5 info
|
||||
cat > release-body.md <<EOF
|
||||
Thanks @arknost for the Linux version
|
||||
|
||||
\`jdlo-eden.AppImage\` MD5: \`$MD5_HASH\`
|
||||
EOF
|
||||
|
||||
cat release-body.md
|
||||
echo "md5_hash=$MD5_HASH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload files to Release
|
||||
if: github.event_name == 'release'
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
with:
|
||||
files: |-
|
||||
jdlo-eden.AppImage
|
||||
jdlo-eden.AppImage.zsync
|
||||
body_path: release-body.md
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -158,7 +158,7 @@ set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundl
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
set(EXT_DEFAULT OFF)
|
||||
if (MSVC OR ANDROID)
|
||||
if (MSVC OR ANDROID OR APPLE)
|
||||
set(EXT_DEFAULT ON)
|
||||
endif()
|
||||
option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT})
|
||||
@@ -425,7 +425,7 @@ if (YUZU_USE_CPM)
|
||||
endif()
|
||||
|
||||
# fmt
|
||||
AddJsonPackage(fmt)
|
||||
AddJsonPackage(NAME fmt BUNDLED_PACKAGE ON)
|
||||
|
||||
# lz4
|
||||
AddJsonPackage(lz4)
|
||||
@@ -530,7 +530,9 @@ if (APPLE)
|
||||
# Umbrella framework for everything GUI-related
|
||||
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
||||
find_library(IOKIT_LIBRARY IOKit REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||
find_library(COREVIDEO_LIBRARY CoreVideo REQUIRED)
|
||||
find_library(VIDEOTOOLBOX_LIBRARY VideoToolbox REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY} ${VIDEOTOOLBOX_LIBRARY})
|
||||
elseif (WIN32)
|
||||
# Target Windows 10
|
||||
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
|
||||
@@ -563,7 +565,7 @@ find_package(VulkanUtilityLibraries)
|
||||
find_package(SimpleIni)
|
||||
find_package(SPIRV-Tools)
|
||||
find_package(sirit)
|
||||
find_package(gamemode)
|
||||
find_package(gamemode)
|
||||
|
||||
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
|
||||
find_package(xbyak)
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
"package": "VVL",
|
||||
"repo": "KhronosGroup/Vulkan-ValidationLayers",
|
||||
"tag": "vulkan-sdk-%VERSION%",
|
||||
"git_version": "1.4.328.1",
|
||||
"git_version": "1.4.335.0",
|
||||
"artifact": "android-binaries-%VERSION%.zip",
|
||||
"hash": "5ec895a453cb7c2f156830b9766953a0c2bd44dea99e6a3dac4160305041ccd3e87534b4ce0bd102392178d2a8eca48411856298f9395e60117cdfe89f72137e"
|
||||
"hash": "48167c4a17736301bd08f9290f41830443e1f18cce8ad867fc6f289b49e18b40e93c9850b377951af82f51b5b6d7313aa6a884fc5df79f5ce3df82696c1c1244"
|
||||
}
|
||||
}
|
||||
|
||||
4
externals/cpmfile.json
vendored
4
externals/cpmfile.json
vendored
@@ -119,10 +119,10 @@
|
||||
"package": "VulkanUtilityLibraries",
|
||||
"repo": "scripts/VulkanUtilityHeaders",
|
||||
"tag": "%VERSION%",
|
||||
"git_version": "1.4.328",
|
||||
"git_version": "1.4.335",
|
||||
"artifact": "VulkanUtilityHeaders.tar.zst",
|
||||
"git_host": "git.crueter.xyz",
|
||||
"hash": "9922217b39faf73cd4fc1510f2fdba14a49aa5c0d77f9ee24ee0512cef16b234d0cabc83c1fec861fa5df1d43e7f086ca9b6501753899119f39c5ca530cb0dae"
|
||||
"hash": "16dac0e6586702580c4279e4cd37ffe3cf909c93eb31b5069da7af36436d47b270a9cbaac953bb66c22ed12ed67ffa096688599267f307dfb62be1bc09f79833"
|
||||
},
|
||||
"spirv-tools": {
|
||||
"package": "SPIRV-Tools",
|
||||
|
||||
1
externals/ffmpeg/CMakeLists.txt
vendored
1
externals/ffmpeg/CMakeLists.txt
vendored
@@ -217,7 +217,6 @@ else()
|
||||
--disable-ffmpeg
|
||||
--disable-ffprobe
|
||||
--disable-network
|
||||
--disable-postproc
|
||||
--disable-swresample
|
||||
--enable-decoder=h264
|
||||
--enable-decoder=vp8
|
||||
|
||||
10
externals/ffmpeg/cpmfile.json
vendored
10
externals/ffmpeg/cpmfile.json
vendored
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"ffmpeg": {
|
||||
"repo": "FFmpeg/FFmpeg",
|
||||
"sha": "5e56937b74",
|
||||
"hash": "9ab0457dcd6ce6359b5053c1662f57910d332f68ca0cca9d4134d858464840917027374de3d97e0863c3a7daaea2fe4f4cd17d1c6d8e7f740f4ad91e71c2932b",
|
||||
"tag": "n6.1",
|
||||
"bundled": true
|
||||
},
|
||||
"ffmpeg-ci": {
|
||||
"ci": true,
|
||||
"package": "FFmpeg",
|
||||
"name": "ffmpeg",
|
||||
"repo": "crueter-ci/FFmpeg",
|
||||
"version": "8.0.1-5e56937b74",
|
||||
"repo": "FFmpeg/FFmpeg",
|
||||
"tag": "n6.1",
|
||||
"version": "6.1",
|
||||
"min_version": "4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ plugins {
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||
id("com.github.triplet.play") version "3.8.6"
|
||||
id("idea")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,8 @@ plugins {
|
||||
*/
|
||||
val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toInt()
|
||||
|
||||
val edenDir = project(":Eden").projectDir
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
android {
|
||||
namespace = "org.yuzu.yuzu_emu"
|
||||
@@ -36,7 +39,6 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -241,11 +243,17 @@ android {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.22.1"
|
||||
path = file("../../../CMakeLists.txt")
|
||||
path = file("${edenDir}/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
// Inclusion to exclude build/ dir from non-Android
|
||||
excludeDirs.add(file("${edenDir}/build"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Delete>("ktlintReset", fun Delete.() {
|
||||
delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint"))
|
||||
@@ -346,7 +354,7 @@ fun getGitVersion(): String {
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val artifactsDir = layout.projectDirectory.dir("../../../artifacts")
|
||||
val artifactsDir = layout.projectDirectory.dir("${edenDir}/artifacts")
|
||||
val outputsDir = layout.buildDirectory.dir("outputs").get()
|
||||
|
||||
android.applicationVariants.forEach { variant ->
|
||||
|
||||
@@ -14,6 +14,8 @@ android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
|
||||
# Android Gradle plugin 8.0.2
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
android.native.buildOutput=verbose
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -19,3 +22,6 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
include("Eden")
|
||||
project(":Eden").projectDir = file("../..")
|
||||
|
||||
@@ -34,8 +34,8 @@ struct Member {
|
||||
struct RoomInformation {
|
||||
std::string name; ///< Name of the server
|
||||
std::string description; ///< Server description
|
||||
u32 member_slots; ///< Maximum number of members in this room
|
||||
u16 port; ///< The port of this room
|
||||
u32 member_slots{}; ///< Maximum number of members in this room
|
||||
u16 port{}; ///< The port of this room
|
||||
GameInfo preferred_game; ///< Game to advertise that you want to play
|
||||
std::string host_username; ///< Forum username of the host
|
||||
};
|
||||
@@ -46,8 +46,8 @@ struct Room {
|
||||
std::string id;
|
||||
std::string verify_uid; ///< UID used for verification
|
||||
std::string ip;
|
||||
u32 net_version;
|
||||
bool has_password;
|
||||
u32 net_version{};
|
||||
bool has_password = false;
|
||||
|
||||
std::vector<Member> members;
|
||||
};
|
||||
|
||||
@@ -578,10 +578,7 @@ public:
|
||||
#endif
|
||||
int flags = (fd > 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
|
||||
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
|
||||
ASSERT_MSG(ret != MAP_FAILED,
|
||||
"mmap(virt_off=0x{:X}, host_off=0x{:X}, len=0x{:X}, virt_size=0x{:X}, backing_size=0x{:X}, perms=0x{:X}) failed: {}",
|
||||
virtual_offset, host_offset, length, virtual_size, backing_size,
|
||||
static_cast<u32>(perms), strerror(errno));
|
||||
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
|
||||
}
|
||||
|
||||
void Unmap(size_t virtual_offset, size_t length) {
|
||||
|
||||
@@ -466,7 +466,7 @@ struct Values {
|
||||
true};
|
||||
SwitchableSetting<bool> async_presentation{linkage,
|
||||
#ifdef ANDROID
|
||||
false,
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
|
||||
@@ -130,17 +130,6 @@ public:
|
||||
ResetStorageBit(id.index);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Contains(SlotId id) const noexcept {
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
const size_t word = id.index / 64;
|
||||
if (word >= stored_bitset.size()) {
|
||||
return false;
|
||||
}
|
||||
return ((stored_bitset[word] >> (id.index % 64)) & 1) != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] Iterator begin() noexcept {
|
||||
const auto it = std::ranges::find_if(stored_bitset, [](u64 value) { return value != 0; });
|
||||
if (it == stored_bitset.end()) {
|
||||
|
||||
@@ -17,8 +17,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
@@ -45,7 +43,11 @@ add_library(core STATIC
|
||||
device_memory.cpp
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory_manager.inc
|
||||
internal_network/legacy_online.cpp
|
||||
internal_network/legacy_online.h
|
||||
file_sys/bis_factory.cpp
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
|
||||
@@ -391,28 +391,15 @@ const std::size_t CACHE_PAGE_SIZE = 4096;
|
||||
|
||||
void ArmNce::ClearInstructionCache() {
|
||||
#ifdef __aarch64__
|
||||
// Use IC IALLU to actually invalidate L1 instruction cache
|
||||
// Ensure all previous memory operations complete
|
||||
asm volatile("dsb ish\n"
|
||||
"ic iallu\n"
|
||||
"dsb ish\n"
|
||||
"isb" ::: "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
// Invalidate instruction cache for specific range instead of full flush
|
||||
constexpr u64 cache_line_size = 64;
|
||||
const u64 aligned_addr = addr & ~(cache_line_size - 1);
|
||||
const u64 end_addr = (addr + size + cache_line_size - 1) & ~(cache_line_size - 1);
|
||||
|
||||
asm volatile("dsb ish" ::: "memory");
|
||||
for (u64 i = aligned_addr; i < end_addr; i += cache_line_size) {
|
||||
asm volatile("ic ivau, %0" :: "r"(i) : "memory");
|
||||
}
|
||||
asm volatile("dsb ish\n"
|
||||
"isb" ::: "memory");
|
||||
#endif
|
||||
this->ClearInstructionCache();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <array>
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "core/hle/service/services.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
@@ -137,6 +138,14 @@ struct System::Impl {
|
||||
kernel.SetMulticore(is_multicore);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
|
||||
// Start Legacy Online Service (UDP port 6000 + HTTP port 8080 for mobile app)
|
||||
if (!legacy_online) {
|
||||
legacy_online = std::make_unique<Network::LegacyOnlineService>();
|
||||
legacy_online->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void ReinitializeIfNecessary(System& system) {
|
||||
@@ -293,6 +302,48 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
void LoadOverrides(u64 programId) const {
|
||||
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
|
||||
LOG_INFO(Core, "GPU Vendor: {}", vendor);
|
||||
|
||||
// Reset all per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
// Insert PC overrides here
|
||||
|
||||
#ifdef ANDROID
|
||||
// Example on how to set a setting based on the program ID and vendor
|
||||
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
|
||||
// Settings::values.example = true;
|
||||
}
|
||||
|
||||
// Example array of program IDs
|
||||
const std::array<u64, 10> example_array = {
|
||||
//0xprogramId
|
||||
0x0004000000033400, // Game 1
|
||||
0x0004000000033500 // Game 2
|
||||
// And so on
|
||||
};
|
||||
|
||||
for (auto id : example_array) {
|
||||
if (programId == id) {
|
||||
// Settings::values.example = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Ninja Gaiden Ragebound
|
||||
constexpr u64 ngr = 0x0100781020710000ULL;
|
||||
|
||||
if (programId == ngr) {
|
||||
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
}
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
@@ -378,8 +429,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
LoadOverrides(program_id);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
@@ -428,6 +478,12 @@ struct System::Impl {
|
||||
stop_event = {};
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (legacy_online) {
|
||||
// Keep legacy_online running for the emulator's lifetime
|
||||
// legacy_online->Stop();
|
||||
// legacy_online.reset();
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
@@ -517,6 +573,9 @@ struct System::Impl {
|
||||
/// Network instance
|
||||
Network::NetworkInstance network_instance;
|
||||
|
||||
/// Legacy Online Service
|
||||
std::unique_ptr<Network::LegacyOnlineService> legacy_online;
|
||||
|
||||
/// Debugger
|
||||
std::unique_ptr<Core::Debugger> debugger;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -55,39 +57,114 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
return dir->CreateDirectoryRelative(save_directory);
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
u64 target_program_id = meta.program_id;
|
||||
// CRITICAL FIX: If the game requests Cache/Temp with ProgramID 0 (generic),
|
||||
// we MUST redirect it to the actual running TitleID, otherwise it looks in '.../0000000000000000'.
|
||||
if ((meta.type == SaveDataType::Cache || meta.type == SaveDataType::Temporary) && target_program_id == 0) {
|
||||
target_program_id = system.GetApplicationProcessProgramID();
|
||||
LOG_INFO(Service_FS, "Redirecting generic Cache request (ID 0) to active TitleID: {:016X}", target_program_id);
|
||||
}
|
||||
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, target_program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto out = dir->GetDirectoryRelative(save_directory);
|
||||
|
||||
if (out == nullptr) {
|
||||
LOG_WARNING(Service_FS, "Cache/Save path NOT FOUND: '{}'. Auto-create={}", save_directory, auto_create);
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "Cache/Save path FOUND: '{}'", save_directory);
|
||||
}
|
||||
|
||||
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
|
||||
LOG_INFO(Service_FS, "Auto-creating save directory...");
|
||||
return Create(space, meta);
|
||||
}
|
||||
|
||||
if (out != nullptr) {
|
||||
// Some emulators (Ryujinx) or even different firmware versions may rely on the commit
|
||||
// directories /0 or /1 being present for cache or save data.
|
||||
// We prioritizing /1 as it usually implies a newer commit if both exist,
|
||||
// but /0 is what's commonly used by Ryujinx for cache.
|
||||
|
||||
// Ryujinx behavior: If 0 exists and 1 does not, copy 0 to 1.
|
||||
auto dir_0 = out->GetSubdirectory("0");
|
||||
auto dir_1 = out->GetSubdirectory("1");
|
||||
|
||||
if (dir_0) LOG_INFO(Service_FS, "Found subdirectory '0' in save path.");
|
||||
if (dir_1) LOG_INFO(Service_FS, "Found subdirectory '1' in save path.");
|
||||
|
||||
if (dir_0 != nullptr && dir_1 == nullptr) {
|
||||
// User requested removal of auto-copy 0->1 logic.
|
||||
// We simply don't create/copy '1' if it's missing. We rely on fallback to '0'.
|
||||
LOG_INFO(Service_FS, "Ryujinx structure detected: '0' exists, '1' missing. Skipping copy (User Request).");
|
||||
// VfsRawCopyD(dir_0, dir_1); // REMOVED
|
||||
}
|
||||
|
||||
// Check for 'Addressables' and 'Addressables2' and delete 'json.cache' if present.
|
||||
// This is a specific workaround for games (e.g. Just Dance) that freeze if they find old cache metadata.
|
||||
// We force them to regenerate it.
|
||||
const auto CleanCache = [](VirtualDir root) {
|
||||
if (root == nullptr) return;
|
||||
const char* subdirs[] = {"Addressables", "Addressables2"};
|
||||
for (const char* subdir_name : subdirs) {
|
||||
auto subdir = root->GetSubdirectory(subdir_name);
|
||||
if (subdir != nullptr) {
|
||||
if (subdir->DeleteFile("json.cache")) {
|
||||
LOG_INFO(Service_FS, "Deleted stale 'json.cache' in '{}'", subdir_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VirtualDir commit_root = nullptr;
|
||||
if (dir_1 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '1' as Commit Root.");
|
||||
commit_root = dir_1;
|
||||
} else if (dir_0 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '0' as Commit Root.");
|
||||
commit_root = dir_0;
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "No '0' or '1' subdirectories found. Using parent folder as Commit Root.");
|
||||
commit_root = out;
|
||||
}
|
||||
|
||||
CleanCache(commit_root);
|
||||
|
||||
// Implement SD_Cache.XXXX logic for Cache Storage
|
||||
if (meta.type == SaveDataType::Cache) {
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
|
||||
auto sd_cache_dir = commit_root->GetSubdirectory(sd_cache_name);
|
||||
if (sd_cache_dir != nullptr) {
|
||||
LOG_INFO(Service_FS, "Found SD_Cache directory: '{}'", sd_cache_name);
|
||||
return sd_cache_dir;
|
||||
} else if (auto_create) {
|
||||
LOG_INFO(Service_FS, "Auto-creating SD_Cache directory: '{}'", sd_cache_name);
|
||||
return commit_root->CreateSubdirectory(sd_cache_name);
|
||||
} else {
|
||||
LOG_WARNING(Service_FS, "SD_Cache directory '{}' not found in commit root.", sd_cache_name);
|
||||
// Fallback? Or return nullptr?
|
||||
// If the user strictly wants SD_Cache, we should probably return nullptr if not found/created.
|
||||
// But for now, returning the commit_root might be safer for legacy compatibility IF SD_Cache isn't strictly enforced for existing saves?
|
||||
// User said "SD_Cache now represents which CacheStorage it will be". This implies correct behavior is to use SD_Cache.
|
||||
// If we return commit_root, it's CacheStorage_0 (conceptually) or just "everything".
|
||||
// Let's assume we return nullptr if not found, to trigger creation logic or error.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return commit_root;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
|
||||
return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
const auto path = GetSaveDataSpaceIdPath(space);
|
||||
// Ensure the directory exists, otherwise FindAllSaves fails.
|
||||
return GetOrCreateDirectoryRelative(dir, path);
|
||||
// return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
@@ -96,12 +173,12 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
return "/system/";
|
||||
case SaveDataSpaceId::User:
|
||||
case SaveDataSpaceId::SdUser:
|
||||
case SaveDataSpaceId::Temporary: // Map into User so we can find the save/ folder
|
||||
return "/user/";
|
||||
case SaveDataSpaceId::Temporary:
|
||||
return "/temp/";
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
|
||||
// ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
LOG_WARNING(Service_FS, "Unrecognized SaveDataSpaceId: {:02X}, defaulting to /user/", static_cast<u8>(space));
|
||||
return "/user/";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +214,9 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::Temporary:
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
// Unified Cache/Temporary Path: Always use save/cache/{TitleID}
|
||||
// This simplifies user instructions and avoids UUID/Permission issues.
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
case SaveDataType::Cache:
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
default:
|
||||
@@ -147,6 +225,40 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
}
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto created_dir = dir->CreateDirectoryRelative(save_directory);
|
||||
|
||||
// For Cache storage, enforce the new hierarchy: .../1/SD_Cache.XXXX
|
||||
if (meta.type == SaveDataType::Cache && created_dir != nullptr) {
|
||||
// Ensure commit directory '1' exists
|
||||
auto commit_dir = created_dir->GetSubdirectory("1");
|
||||
if (commit_dir == nullptr) {
|
||||
commit_dir = created_dir->CreateSubdirectory("1");
|
||||
}
|
||||
|
||||
if (commit_dir != nullptr) {
|
||||
// Create SD_Cache.XXXX
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
|
||||
return commit_dir->CreateSubdirectory(sd_cache_name);
|
||||
}
|
||||
}
|
||||
|
||||
return created_dir;
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future) {
|
||||
if (future) {
|
||||
Common::UUID uuid;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Core::Frontend {
|
||||
struct CabinetParameters {
|
||||
Service::NFP::TagInfo tag_info;
|
||||
Service::NFP::RegisterInfo register_info;
|
||||
Service::NFP::CabinetMode mode;
|
||||
Service::NFP::CabinetMode mode{};
|
||||
};
|
||||
|
||||
using CabinetCallback = std::function<void(bool, const std::string&)>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -20,9 +23,9 @@ struct KeyboardInitializeParameters {
|
||||
std::u16string initial_text;
|
||||
char16_t left_optional_symbol_key;
|
||||
char16_t right_optional_symbol_key;
|
||||
u32 max_text_length;
|
||||
u32 min_text_length;
|
||||
s32 initial_cursor_position;
|
||||
u32 max_text_length{};
|
||||
u32 min_text_length{};
|
||||
s32 initial_cursor_position{};
|
||||
Service::AM::Frontend::SwkbdType type;
|
||||
Service::AM::Frontend::SwkbdPasswordMode password_mode;
|
||||
Service::AM::Frontend::SwkbdTextDrawType text_draw_type;
|
||||
@@ -34,12 +37,12 @@ struct KeyboardInitializeParameters {
|
||||
};
|
||||
|
||||
struct InlineAppearParameters {
|
||||
u32 max_text_length;
|
||||
u32 min_text_length;
|
||||
f32 key_top_scale_x;
|
||||
f32 key_top_scale_y;
|
||||
f32 key_top_translate_x;
|
||||
f32 key_top_translate_y;
|
||||
u32 max_text_length{};
|
||||
u32 min_text_length{};
|
||||
f32 key_top_scale_x{};
|
||||
f32 key_top_scale_y{};
|
||||
f32 key_top_translate_x{};
|
||||
f32 key_top_translate_y{};
|
||||
Service::AM::Frontend::SwkbdType type;
|
||||
Service::AM::Frontend::SwkbdKeyDisableFlags key_disable_flags;
|
||||
bool key_top_as_floating;
|
||||
@@ -50,7 +53,7 @@ struct InlineAppearParameters {
|
||||
|
||||
struct InlineTextParameters {
|
||||
std::u16string input_text;
|
||||
s32 cursor_position;
|
||||
s32 cursor_position{};
|
||||
};
|
||||
|
||||
class SoftwareKeyboardApplet : public Applet {
|
||||
|
||||
@@ -686,6 +686,16 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
||||
using EdenPath = Common::FS::EdenPath;
|
||||
const auto sdmc_dir_path = Common::FS::GetEdenPath(EdenPath::SDMCDir);
|
||||
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
|
||||
|
||||
// If the NAND user save location doesn't exist but the SDMC contains
|
||||
// Nintendo/save (common portable save structure), create a host-side
|
||||
// symlink so the emulator will see those saves under the expected NAND path.
|
||||
// This helps users who placed saves under `<eden>/user/sdmc/Nintendo/save/...`.
|
||||
// SDMC to NAND sync logic REMOVED as per user request.
|
||||
// The emulator will no longer attempt to symlink or copy "Nintendo/save" or "SD_Cache.0000"
|
||||
// from SDMC to the NAND user save directory.
|
||||
// Users must ensure their save/cache structure is valid within the NAND directory itself
|
||||
// if that is what they intend to use, or rely on the game creating it.
|
||||
const auto rw_mode = FileSys::OpenMode::ReadWrite;
|
||||
|
||||
auto nand_directory =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -14,7 +14,7 @@ namespace Service::FileSystem {
|
||||
|
||||
ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space)
|
||||
FileSys::SaveDataSpaceId space, bool cache_only)
|
||||
: ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{
|
||||
save_data_controller_} {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -22,7 +22,7 @@ ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
FindAllSaves(space);
|
||||
FindAllSaves(space, cache_only);
|
||||
}
|
||||
|
||||
ISaveDataInfoReader::~ISaveDataInfoReader() = default;
|
||||
@@ -63,7 +63,7 @@ Result ISaveDataInfoReader::ReadSaveDataInfo(
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only) {
|
||||
FileSys::VirtualDir save_root{};
|
||||
const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space);
|
||||
|
||||
@@ -74,8 +74,12 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
|
||||
for (const auto& type : save_root->GetSubdirectories()) {
|
||||
if (type->GetName() == "save") {
|
||||
FindNormalSaves(space, type);
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary) {
|
||||
if (cache_only) {
|
||||
FindCacheSaves(space, type);
|
||||
} else {
|
||||
FindNormalSaves(space, type);
|
||||
}
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary && !cache_only) {
|
||||
FindTemporaryStorageSaves(space, type);
|
||||
}
|
||||
}
|
||||
@@ -84,6 +88,11 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& save_id : type->GetSubdirectories()) {
|
||||
// Skip cache directory in normal scans
|
||||
if (save_id->GetName() == "cache") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& user_id : save_id->GetSubdirectories()) {
|
||||
// Skip non user id subdirectories
|
||||
if (user_id->GetName().size() != 0x20) {
|
||||
@@ -132,6 +141,96 @@ void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindCacheSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
const auto cache_dir = type->GetSubdirectory("cache");
|
||||
if (cache_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& title_id_dir : cache_dir->GetSubdirectories()) {
|
||||
const auto title_id = stoull_be(title_id_dir->GetName());
|
||||
// Simple validation: TitleID should be non-zero
|
||||
if (title_id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine commit root (priority to "1", then "0", then self)
|
||||
auto commit_root = title_id_dir->GetSubdirectory("1");
|
||||
if (commit_root == nullptr) {
|
||||
commit_root = title_id_dir->GetSubdirectory("0");
|
||||
}
|
||||
// If neither exists, we might fall back to title_id_dir itself if users put SD_Cache directly there,
|
||||
// but based on SaveDataFactory we expect it inside 0 or 1.
|
||||
if (commit_root == nullptr) {
|
||||
// Check if SD_Cache exists directly in title_dir (legacy/fallback)
|
||||
bool has_sd_cache_root = false;
|
||||
for (const auto& sub : title_id_dir->GetSubdirectories()) {
|
||||
if (sub->GetName().find("SD_Cache.") == 0) {
|
||||
has_sd_cache_root = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_sd_cache_root) {
|
||||
commit_root = title_id_dir;
|
||||
} else {
|
||||
continue; // No valid storage found
|
||||
}
|
||||
}
|
||||
|
||||
bool found_any = false;
|
||||
for (const auto& sd_cache_dir : commit_root->GetSubdirectories()) {
|
||||
const std::string& name = sd_cache_dir->GetName();
|
||||
if (name.find("SD_Cache.") == 0) {
|
||||
// Parse index from "SD_Cache.XXXX" (hexadecimal)
|
||||
u64 index = 0;
|
||||
try {
|
||||
if (name.size() > 9) {
|
||||
index = std::stoull(name.substr(9), nullptr, 16); // Base 16 for hex
|
||||
}
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
sd_cache_dir->GetSize(),
|
||||
static_cast<u16>(index), // Correct index with cast
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
found_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for legacy "flat" cache if no SD_Cache folders found?
|
||||
// If the user specific structure IS enforced, maybe we don't fallback.
|
||||
// But if they have existing cache without the folder, it is effectively index 0.
|
||||
if (!found_any) {
|
||||
// Treat the entire commit_root as index 0 (Legacy behavior)
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
commit_root->GetSize(),
|
||||
0, // index 0
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& user_id : type->GetSubdirectories()) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
|
||||
public:
|
||||
explicit ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space);
|
||||
FileSys::SaveDataSpaceId space, bool cache_only = false);
|
||||
~ISaveDataInfoReader() override;
|
||||
|
||||
struct SaveDataInfo {
|
||||
@@ -38,8 +38,9 @@ public:
|
||||
OutArray<SaveDataInfo, BufferAttr_HipcMapAlias> out_entries);
|
||||
|
||||
private:
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space);
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only);
|
||||
void FindNormalSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindCacheSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
|
||||
std::shared_ptr<SaveDataController> save_data_controller;
|
||||
|
||||
@@ -353,10 +353,10 @@ Result FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(
|
||||
|
||||
Result FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(
|
||||
OutInterface<ISaveDataInfoReader> out_interface) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
*out_interface = std::make_shared<ISaveDataInfoReader>(system, save_data_controller,
|
||||
FileSys::SaveDataSpaceId::Temporary);
|
||||
FileSys::SaveDataSpaceId::User, true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
@@ -46,8 +42,16 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
|
||||
const auto& message_header = context.GetDomainMessageHeader();
|
||||
const auto object_id = message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field, which indicates
|
||||
// this is not actually a proper domain request
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, treat it as a regular session request
|
||||
return session_handler != nullptr;
|
||||
}
|
||||
|
||||
if (object_id > DomainHandlerCount()) {
|
||||
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
|
||||
return false;
|
||||
}
|
||||
return !DomainHandler(object_id - 1).expired();
|
||||
@@ -95,7 +99,18 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
const u32 object_id{domain_message_header.object_id};
|
||||
const u32 object_id = domain_message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, handle as regular session request
|
||||
LOG_DEBUG(IPC, "Detected magic number 0x{:08X} in object_id, treating as regular session request. Command={}",
|
||||
object_id, context.GetCommand());
|
||||
return session_handler->HandleSyncRequest(*server_session, context);
|
||||
}
|
||||
|
||||
switch (domain_message_header.command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > this->DomainHandlerCount()) {
|
||||
@@ -191,7 +206,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
buffer_w_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
|
||||
}
|
||||
|
||||
buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
|
||||
const auto buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
|
||||
|
||||
if (!command_header->IsTipc()) {
|
||||
// Padding to align to 16 bytes
|
||||
@@ -204,7 +219,17 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
// Check if the next value is actually a magic number (SFCI/SFCO)
|
||||
// Some games send these in the object_id field, indicating this is not a domain request
|
||||
const u32 possible_object_id = src_cmdbuf[rp.GetCurrentOffset() + 1]; // object_id is second field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
|
||||
if (possible_object_id != sfci_magic && possible_object_id != sfco_magic) {
|
||||
// This is a proper domain request
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
}
|
||||
// If it's a magic number, skip reading the domain header entirely
|
||||
} else {
|
||||
if (GetManager()->IsDomain()) {
|
||||
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
|
||||
@@ -224,9 +249,15 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
}
|
||||
|
||||
if (incoming) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
// Only check magic if we have a valid data payload header
|
||||
// When domain header is skipped (SFCI in object_id), the structure is different
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
}
|
||||
} else {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,15 +329,7 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer() {
|
||||
}
|
||||
|
||||
// Write the domain objects to the command buffer, these go after the raw untranslated data.
|
||||
|
||||
if (buffer_c_offset != 0 && !buffer_c_descriptors.empty()) {
|
||||
constexpr u32 WORDS_PER_DESCRIPTOR = sizeof(IPC::BufferDescriptorC) / sizeof(u32);
|
||||
u32 descriptor_offset = buffer_c_offset;
|
||||
for (const auto& descriptor : buffer_c_descriptors) {
|
||||
std::memcpy(&cmd_buf[descriptor_offset], &descriptor, sizeof(descriptor));
|
||||
descriptor_offset += WORDS_PER_DESCRIPTOR;
|
||||
}
|
||||
}
|
||||
// TODO(Subv): This completely ignores C buffers.
|
||||
|
||||
if (GetManager()->IsDomain()) {
|
||||
current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size());
|
||||
@@ -405,14 +428,10 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
|
||||
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
|
||||
BufferDescriptorB()[buffer_index].Size()};
|
||||
const std::size_t buffer_size{GetWriteBufferSize(buffer_index)};
|
||||
if (buffer_size == 0) {
|
||||
LOG_WARNING(Core, "WriteBuffer target index {} has zero capacity", buffer_index);
|
||||
return 0;
|
||||
}
|
||||
if (size > buffer_size) {
|
||||
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
|
||||
size, buffer_size);
|
||||
size = buffer_size;
|
||||
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
|
||||
buffer_size);
|
||||
size = buffer_size; // TODO(bunnei): This needs to be HW tested
|
||||
}
|
||||
|
||||
if (is_buffer_b) {
|
||||
@@ -434,25 +453,15 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
|
||||
|
||||
std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size,
|
||||
std::size_t buffer_index) const {
|
||||
if (buffer_index >= BufferDescriptorB().size()) {
|
||||
LOG_WARNING(Core, "WriteBufferB invalid buffer index {}", buffer_index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
LOG_WARNING(Core, "skip empty buffer write (B)");
|
||||
if (buffer_index >= BufferDescriptorB().size() || size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto buffer_size{BufferDescriptorB()[buffer_index].Size()};
|
||||
if (buffer_size == 0) {
|
||||
LOG_WARNING(Core, "WriteBufferB target index {} has zero capacity", buffer_index);
|
||||
return 0;
|
||||
}
|
||||
if (size > buffer_size) {
|
||||
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
|
||||
size, buffer_size);
|
||||
size = buffer_size;
|
||||
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
|
||||
buffer_size);
|
||||
size = buffer_size; // TODO(bunnei): This needs to be HW tested
|
||||
}
|
||||
|
||||
memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
|
||||
@@ -461,25 +470,15 @@ std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size
|
||||
|
||||
std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size,
|
||||
std::size_t buffer_index) const {
|
||||
if (buffer_index >= BufferDescriptorC().size()) {
|
||||
LOG_WARNING(Core, "WriteBufferC invalid buffer index {}", buffer_index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
LOG_WARNING(Core, "skip empty buffer write (C)");
|
||||
if (buffer_index >= BufferDescriptorC().size() || size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto buffer_size{BufferDescriptorC()[buffer_index].Size()};
|
||||
if (buffer_size == 0) {
|
||||
LOG_WARNING(Core, "WriteBufferC target index {} has zero capacity", buffer_index);
|
||||
return 0;
|
||||
}
|
||||
if (size > buffer_size) {
|
||||
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
|
||||
size, buffer_size);
|
||||
size = buffer_size;
|
||||
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
|
||||
buffer_size);
|
||||
size = buffer_size; // TODO(bunnei): This needs to be HW tested
|
||||
}
|
||||
|
||||
memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
|
||||
@@ -509,20 +508,12 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
BufferDescriptorB().size() > buffer_index, { return 0; },
|
||||
"BufferDescriptorB invalid buffer_index {}", buffer_index);
|
||||
const auto size = BufferDescriptorB()[buffer_index].Size();
|
||||
if (size == 0) {
|
||||
LOG_WARNING(Core, "BufferDescriptorB index {} has zero size", buffer_index);
|
||||
}
|
||||
return size;
|
||||
return BufferDescriptorB()[buffer_index].Size();
|
||||
} else {
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
BufferDescriptorC().size() > buffer_index, { return 0; },
|
||||
"BufferDescriptorC invalid buffer_index {}", buffer_index);
|
||||
const auto size = BufferDescriptorC()[buffer_index].Size();
|
||||
if (size == 0) {
|
||||
LOG_WARNING(Core, "BufferDescriptorC index {} has zero size", buffer_index);
|
||||
}
|
||||
return size;
|
||||
return BufferDescriptorC()[buffer_index].Size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -425,7 +422,6 @@ private:
|
||||
u32 data_payload_offset{};
|
||||
u32 handles_offset{};
|
||||
u32 domain_offset{};
|
||||
u32 buffer_c_offset{};
|
||||
|
||||
std::weak_ptr<SessionRequestManager> manager{};
|
||||
bool is_deferred{false};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -79,7 +82,7 @@ using DeviceHandle = u64;
|
||||
|
||||
// This is nn::nfc::TagInfo
|
||||
struct TagInfo {
|
||||
UniqueSerialNumber uuid;
|
||||
UniqueSerialNumber uuid{};
|
||||
u8 uuid_length;
|
||||
INSERT_PADDING_BYTES(0x15);
|
||||
NfcProtocol protocol;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -315,7 +318,7 @@ static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
|
||||
// This is nn::nfp::RegisterInfo
|
||||
struct RegisterInfo {
|
||||
Service::Mii::CharInfo mii_char_info;
|
||||
WriteDate creation_date;
|
||||
WriteDate creation_date{};
|
||||
AmiiboName amiibo_name;
|
||||
u8 font_region;
|
||||
INSERT_PADDING_BYTES(0x7A);
|
||||
|
||||
@@ -164,7 +164,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
|
||||
// Rebuild shared fonts from data ncas or synthesize
|
||||
|
||||
impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE);
|
||||
for (auto font : SHARED_FONTS) {
|
||||
for (auto& font : SHARED_FONTS) {
|
||||
FileSys::VirtualFile romfs;
|
||||
const auto nca =
|
||||
nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
|
||||
@@ -261,7 +261,7 @@ Result IPlatformServiceManager::GetSharedFontInOrderOfPriority(
|
||||
out_font_sizes.size(), impl->shared_font_regions.size()});
|
||||
|
||||
for (size_t i = 0; i < max_size; i++) {
|
||||
auto region = impl->GetSharedFontRegion(i);
|
||||
auto& region = impl->GetSharedFontRegion(i);
|
||||
|
||||
out_font_codes[i] = static_cast<u32>(i);
|
||||
out_font_offsets[i] = region.offset;
|
||||
|
||||
@@ -66,7 +66,9 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
|
||||
|
||||
void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
|
||||
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
|
||||
system.SetNVDECActive(true);
|
||||
if (!system.GetNVDECActive()) {
|
||||
system.SetNVDECActive(true);
|
||||
}
|
||||
sessions[fd] = session_id;
|
||||
host1x.StartDevice(fd, Tegra::Host1x::ChannelType::NvDec, channel_syncpoint);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -22,6 +22,9 @@
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "network/network.h"
|
||||
#include <common/settings.h>
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
using Common::Expected;
|
||||
using Common::Unexpected;
|
||||
@@ -315,7 +318,15 @@ void BSD::Fcntl(HLERequestContext& ctx) {
|
||||
const u32 cmd = rp.Pop<u32>();
|
||||
const s32 arg = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
|
||||
// Log with more detail to understand non-blocking configuration
|
||||
if (cmd == 4) { // SETFL
|
||||
bool is_nonblock = (arg & 0x800) != 0; // O_NONBLOCK
|
||||
LOG_INFO(Service, "Fcntl SETFL fd={} arg={} (non-blocking={})", fd, arg, is_nonblock);
|
||||
} else if (cmd == 3) { // GETFL
|
||||
LOG_INFO(Service, "Fcntl GETFL fd={}", fd);
|
||||
} else {
|
||||
LOG_INFO(Service, "Fcntl fd={} cmd={} arg={}", fd, cmd, arg);
|
||||
}
|
||||
|
||||
const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
|
||||
|
||||
@@ -802,33 +813,40 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
|
||||
|
||||
switch (optname) {
|
||||
case OptName::REUSEADDR:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} REUSEADDR={}", fd, value);
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid REUSEADDR value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
return Translate(socket->SetReuseAddr(value != 0));
|
||||
case OptName::KEEPALIVE:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} KEEPALIVE={}", fd, value);
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid KEEPALIVE value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
return Translate(socket->SetKeepAlive(value != 0));
|
||||
case OptName::BROADCAST:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} BROADCAST={}", fd, value);
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid BROADCAST value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
return Translate(socket->SetBroadcast(value != 0));
|
||||
case OptName::SNDBUF:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} SNDBUF={}", fd, value);
|
||||
return Translate(socket->SetSndBuf(value));
|
||||
case OptName::RCVBUF:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} RCVBUF={}", fd, value);
|
||||
return Translate(socket->SetRcvBuf(value));
|
||||
case OptName::SNDTIMEO:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} SNDTIMEO={}", fd, value);
|
||||
return Translate(socket->SetSndTimeo(value));
|
||||
case OptName::RCVTIMEO:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} RCVTIMEO={}", fd, value);
|
||||
return Translate(socket->SetRcvTimeo(value));
|
||||
case OptName::NOSIGPIPE:
|
||||
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
|
||||
LOG_INFO(Service, "SetSockOpt fd={} NOSIGPIPE={}", fd, value);
|
||||
return Errno::SUCCESS;
|
||||
default:
|
||||
LOG_WARNING(Service, "(STUBBED) Unimplemented optname={} (0x{:x}), returning INVAL",
|
||||
@@ -919,12 +937,134 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
|
||||
return {ret, bsd_errno};
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> message) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> message) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
|
||||
const size_t original_size = message.size();
|
||||
|
||||
// Inspect for Authorization header to inject custom token (HTTP/Localhost support)
|
||||
const std::string_view data_view(reinterpret_cast<const char*>(message.data()),
|
||||
message.size());
|
||||
|
||||
// Optimized check: only look if it looks like an HTTP request with auth
|
||||
// We do a case-insensitive search for the specific header pattern
|
||||
std::string request_str(data_view);
|
||||
std::string request_lower = Common::ToLower(request_str);
|
||||
|
||||
size_t auth_pos = request_lower.find("authorization: switch t=");
|
||||
|
||||
if (auth_pos != std::string::npos) {
|
||||
LOG_INFO(Service,
|
||||
"BSDJ: Found 'Authorization: switch t=' in SendImpl. Injecting custom auth.");
|
||||
|
||||
const auto auth_file_path =
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "jdlo_auth.ini";
|
||||
Common::FS::IOFile auth_file(auth_file_path, Common::FS::FileAccessMode::Read,
|
||||
Common::FS::FileType::TextFile);
|
||||
|
||||
if (auth_file.IsOpen()) {
|
||||
std::vector<u8> file_content_vec(auth_file.GetSize());
|
||||
if (auth_file.Read(file_content_vec) == file_content_vec.size()) {
|
||||
std::string encoded_content(file_content_vec.begin(), file_content_vec.end());
|
||||
|
||||
// Simple Base64 Decoder
|
||||
auto DecodeBase64 = [](std::string_view input) -> std::string {
|
||||
static const int T[256] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
|
||||
|
||||
std::string out;
|
||||
int val = 0, valb = -8;
|
||||
for (unsigned char c : input) {
|
||||
if (T[c] == -1)
|
||||
break;
|
||||
val = (val << 6) + T[c];
|
||||
valb += 6;
|
||||
if (valb >= 0) {
|
||||
out.push_back(char((val >> valb) & 0xFF));
|
||||
valb -= 8;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// Decode the INI content
|
||||
std::string file_content = DecodeBase64(encoded_content);
|
||||
|
||||
// Find TickedId
|
||||
std::string auth_token;
|
||||
static constexpr std::string_view KeyName = "TickedId=";
|
||||
size_t key_pos = file_content.find(KeyName);
|
||||
|
||||
if (key_pos != std::string::npos) {
|
||||
size_t value_start = key_pos + KeyName.size();
|
||||
size_t value_end = file_content.find_first_of("\r\n", value_start);
|
||||
if (value_end == std::string::npos) {
|
||||
value_end = file_content.size();
|
||||
}
|
||||
auth_token = file_content.substr(value_start, value_end - value_start);
|
||||
}
|
||||
|
||||
if (!auth_token.empty()) {
|
||||
// Ensure the token has the correct prefix "uplaypc_v1 t="
|
||||
if (auth_token.find("uplaypc_v1 t=") == std::string::npos) {
|
||||
auth_token = "uplaypc_v1 t=" + auth_token;
|
||||
}
|
||||
|
||||
// Find end of the line
|
||||
size_t end_pos = request_str.find("\r\n", auth_pos);
|
||||
if (end_pos != std::string::npos) {
|
||||
bool is_header_start = (auth_pos == 0) || (request_str[auth_pos - 1] == '\n');
|
||||
|
||||
if (is_header_start) {
|
||||
LOG_INFO(Service, "BSDJ: Injecting token (TickedId): {}...",
|
||||
auth_token.substr(0, 20));
|
||||
|
||||
std::string new_header = "Authorization: " + auth_token;
|
||||
request_str.replace(auth_pos, end_pos - auth_pos, new_header);
|
||||
|
||||
// Send the MODIFIED message
|
||||
std::span<const u8> new_message(
|
||||
reinterpret_cast<const u8*>(request_str.data()),
|
||||
request_str.size());
|
||||
auto result = Translate(
|
||||
file_descriptors[fd]->socket->Send(new_message, flags));
|
||||
LOG_CRITICAL(
|
||||
Service,
|
||||
"SendImpl (Modified) real_sent={} original_size={} errno={}",
|
||||
result.first, original_size, static_cast<int>(result.second));
|
||||
|
||||
// Mask the return size: If we successfully sent the larger buffer,
|
||||
// tell the guest we sent exactly what they asked for.
|
||||
if (result.first > 0 && result.second == Errno::SUCCESS) {
|
||||
// Return original size to prevent guest confusion
|
||||
return {static_cast<s32>(original_size), Errno::SUCCESS};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LOG_WARNING(Service, "jdlo_auth.ini not found");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service, "SendImpl called: fd={}, size={} bytes, flags={}", fd, message.size(),
|
||||
flags);
|
||||
auto result = Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
LOG_CRITICAL(Service, "SendImpl result: sent={} bytes, errno={}", result.first,
|
||||
static_cast<int>(result.second));
|
||||
return result;
|
||||
}
|
||||
return Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> message,
|
||||
std::span<const u8> addr) {
|
||||
|
||||
@@ -60,12 +60,29 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting NSD host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static std::string ResolveImpl(const std::string& fqdn_in) {
|
||||
// The real implementation makes various substitutions.
|
||||
// For now we just return the string as-is, which is good enough when not
|
||||
// connecting to real Nintendo servers.
|
||||
LOG_WARNING(Service, "(STUBBED) called, fqdn_in={}", fqdn_in);
|
||||
return fqdn_in;
|
||||
|
||||
return GetRedirectedHost(fqdn_in);
|
||||
}
|
||||
|
||||
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {
|
||||
|
||||
@@ -29,13 +29,13 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
|
||||
{4, nullptr, "GetHostStringErrorRequest"},
|
||||
{5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"},
|
||||
{6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
|
||||
{7, nullptr, "GetNameInfoRequest"},
|
||||
{8, nullptr, "RequestCancelHandleRequest"},
|
||||
{7, &SFDNSRES::GetNameInfoRequest, "GetNameInfoRequest"},
|
||||
{8, &SFDNSRES::RequestCancelHandleRequest, "RequestCancelHandleRequest"},
|
||||
{9, nullptr, "CancelRequest"},
|
||||
{10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"},
|
||||
{11, nullptr, "GetHostByAddrRequestWithOptions"},
|
||||
{12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
|
||||
{13, nullptr, "GetNameInfoRequestWithOptions"},
|
||||
{13, &SFDNSRES::GetNameInfoRequestWithOptions, "GetNameInfoRequestWithOptions"},
|
||||
{14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"},
|
||||
{15, nullptr, "ResolverGetOptionRequest"},
|
||||
};
|
||||
@@ -66,6 +66,21 @@ static bool IsBlockedHost(const std::string& host) {
|
||||
[&host](const std::string& domain) { return host.find(domain) != std::string::npos; });
|
||||
}
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) {
|
||||
// These combinations have been verified on console (but are not
|
||||
// exhaustive).
|
||||
@@ -163,7 +178,8 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
|
||||
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
@@ -281,7 +297,8 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
|
||||
// before looking up.
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
if (IsBlockedHost(host)) {
|
||||
@@ -364,6 +381,120 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void SFDNSRES::GetNameInfoRequest(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 cancel_handle = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service, "called. flags={}, cancel_handle={}, process_id={}", flags, cancel_handle,
|
||||
process_id);
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::GetNameInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
u32 flags;
|
||||
u32 interface_index;
|
||||
u64 process_id;
|
||||
u32 padding; // 0x14 + 4 = 0x18? No. 0x14 aligned to 8 bytes?
|
||||
// Wait, sizeof(InputParameters) == 0x14.
|
||||
};
|
||||
// Derived from partial snippets:
|
||||
// u32 flags, u32 interface_index, u64 process_id.
|
||||
// 4 + 4 + 8 = 16 bytes (0x10).
|
||||
// The previous prompt had static_assert size 0x14.
|
||||
// Maybe a u32 padding?
|
||||
|
||||
// Let's rely on standard layout.
|
||||
// I will use manual popping for safety if struct definition is unknown.
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 interface_index = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
(void)flags;
|
||||
(void)interface_index;
|
||||
(void)process_id;
|
||||
// If there was padding, it might be implicitly popped or ignored.
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
@@ -372,4 +503,24 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void SFDNSRES::RequestCancelHandleRequest(HLERequestContext& ctx) {
|
||||
// This is just a stub for now.
|
||||
// In a real implementation this would likely cancel a pending request represented by the handle.
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
struct InputParameters {
|
||||
u32 cancel_handle;
|
||||
};
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto input = rp.PopRaw<InputParameters>();
|
||||
|
||||
LOG_DEBUG(Service, "cancel_handle={}", input.cancel_handle);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(0); // cancel_handle (response seems to echo it or return a new one? usually just an error code or unrelated)
|
||||
// Actually based on typical patterns, it probably returns an error code (bsd_errno).
|
||||
rb.Push<s32>(0); // bsd_errno
|
||||
}
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -22,7 +22,10 @@ private:
|
||||
void GetHostByNameRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequest(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetNameInfoRequest(HLERequestContext& ctx);
|
||||
void GetNameInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void ResolverSetOptionRequest(HLERequestContext& ctx);
|
||||
void RequestCancelHandleRequest(HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -16,10 +16,12 @@ enum class Errno : u32 {
|
||||
SUCCESS = 0,
|
||||
BADF = 9,
|
||||
AGAIN = 11,
|
||||
ACCES = 13,
|
||||
INVAL = 22,
|
||||
MFILE = 24,
|
||||
PIPE = 32,
|
||||
MSGSIZE = 90,
|
||||
ADDRINUSE = 98,
|
||||
CONNABORTED = 103,
|
||||
CONNRESET = 104,
|
||||
NOTCONN = 107,
|
||||
|
||||
@@ -37,6 +37,10 @@ Errno Translate(Network::Errno value) {
|
||||
return Errno::CONNRESET;
|
||||
case Network::Errno::INPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case Network::Errno::ACCES:
|
||||
return Errno::ACCES;
|
||||
case Network::Errno::ADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
|
||||
return Errno::SUCCESS;
|
||||
@@ -261,7 +265,9 @@ PollEvents Translate(Network::PollEvents flags) {
|
||||
Network::SockAddrIn Translate(SockAddrIn value) {
|
||||
// Note: 6 is incorrect, but can be passed by homebrew (because libnx sets
|
||||
// sin_len to 6 when deserializing getaddrinfo results).
|
||||
ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6);
|
||||
if (value.len != 0 && value.len != sizeof(value) && value.len != 6) {
|
||||
LOG_WARNING(Service, "Unexpected SockAddrIn length={}", value.len);
|
||||
}
|
||||
|
||||
return {
|
||||
.family = Translate(static_cast<Domain>(value.family)),
|
||||
|
||||
@@ -117,15 +117,20 @@ public:
|
||||
RegisterHandlers(functions);
|
||||
|
||||
shared_data->connection_count++;
|
||||
LOG_CRITICAL(Service_SSL, "ISslConnection created! Total connections: {}", shared_data->connection_count);
|
||||
}
|
||||
|
||||
~ISslConnection() {
|
||||
shared_data->connection_count--;
|
||||
if (fd_to_close.has_value()) {
|
||||
const s32 fd = *fd_to_close;
|
||||
if (!do_not_close_socket) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"do_not_close_socket was changed after setting socket; is this right?");
|
||||
if (do_not_close_socket) {
|
||||
// If we aren't supposed to close the socket, but we have an fd_to_close,
|
||||
// that means the configuration changed after we took ownership.
|
||||
// This is weird but we should probably honor the flag.
|
||||
// However, the original valid logic seemed to imply we duped the socket
|
||||
// and should close our dup... but let's stick to what the flag says.
|
||||
LOG_INFO(Service_SSL, "do_not_close_socket is true, skipping close of fd {}", fd);
|
||||
} else {
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
if (bsd) {
|
||||
@@ -269,16 +274,17 @@ private:
|
||||
}
|
||||
|
||||
Result PendingImpl(s32* out_pending) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||
*out_pending = 0;
|
||||
return ResultSuccess;
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
return backend->Pending(out_pending);
|
||||
}
|
||||
|
||||
void SetSocketDescriptor(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 in_fd = rp.Pop<s32>();
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor called with fd={}", in_fd);
|
||||
s32 out_fd{-1};
|
||||
const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor result: res={}, out_fd={}", res.raw, out_fd);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
rb.Push<s32>(out_fd);
|
||||
@@ -308,7 +314,9 @@ private:
|
||||
}
|
||||
|
||||
void DoHandshake(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_SSL, "DoHandshake called, socket={}", socket != nullptr);
|
||||
const Result res = DoHandshakeImpl();
|
||||
LOG_INFO(Service_SSL, "DoHandshake result: {}, did_handshake={}", res.raw, did_handshake);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
||||
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
|
||||
virtual Result Pending(s32* out_pending) = 0;
|
||||
};
|
||||
|
||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
@@ -48,28 +49,32 @@ bool OneTimeInitBIO();
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
// This is ported from httplib
|
||||
struct scope_exit {
|
||||
explicit scope_exit(std::function<void(void)> &&f)
|
||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||
explicit scope_exit(std::function<void(void)> &&f)
|
||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||
|
||||
scope_exit(scope_exit &&rhs) noexcept
|
||||
: exit_function(std::move(rhs.exit_function)),
|
||||
execute_on_destruction{rhs.execute_on_destruction} {
|
||||
rhs.release();
|
||||
}
|
||||
scope_exit(scope_exit &&rhs) noexcept
|
||||
: exit_function(std::move(rhs.exit_function)),
|
||||
execute_on_destruction{rhs.execute_on_destruction} {
|
||||
rhs.release();
|
||||
}
|
||||
|
||||
~scope_exit() {
|
||||
if (execute_on_destruction) { this->exit_function(); }
|
||||
}
|
||||
~scope_exit() {
|
||||
if (execute_on_destruction) {
|
||||
this->exit_function();
|
||||
}
|
||||
}
|
||||
|
||||
void release() { this->execute_on_destruction = false; }
|
||||
void release() {
|
||||
this->execute_on_destruction = false;
|
||||
}
|
||||
|
||||
private:
|
||||
scope_exit(const scope_exit &) = delete;
|
||||
void operator=(const scope_exit &) = delete;
|
||||
scope_exit &operator=(scope_exit &&) = delete;
|
||||
scope_exit(const scope_exit &) = delete;
|
||||
void operator=(const scope_exit &) = delete;
|
||||
scope_exit &operator=(scope_exit &&) = delete;
|
||||
|
||||
std::function<void(void)> exit_function;
|
||||
bool execute_on_destruction;
|
||||
std::function<void(void)> exit_function;
|
||||
bool execute_on_destruction;
|
||||
};
|
||||
|
||||
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
|
||||
@@ -115,6 +120,22 @@ inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Redirecting SSL host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
||||
@@ -157,7 +178,8 @@ public:
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
Result SetHostName(const std::string& hostname_in) override {
|
||||
const std::string hostname = GetRedirectedHost(hostname_in);
|
||||
if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification
|
||||
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
||||
return CheckOpenSSLErrors();
|
||||
@@ -195,6 +217,123 @@ public:
|
||||
}
|
||||
|
||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
||||
const size_t original_size = data.size();
|
||||
const std::string_view data_view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
// Log all POST requests for debugging
|
||||
if (data_view.size() > 5 && data_view.substr(0, 5) == "POST ") {
|
||||
LOG_INFO(Service_SSL, "Intercepted POST request. Length: {}. Preview: {}", data.size(), data_view.substr(0, std::min(data_view.size(), size_t(200))));
|
||||
}
|
||||
|
||||
std::string request_str(data_view);
|
||||
std::string request_lower = Common::ToLower(request_str);
|
||||
|
||||
// Look for the specific authorization header value we want to replace: "switch t="
|
||||
// We match "authorization: switch t=" case-insensitively
|
||||
size_t auth_pos = request_lower.find("authorization: switch t=");
|
||||
|
||||
if (auth_pos != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Found 'Authorization: switch t=' header. Injecting custom auth.");
|
||||
|
||||
const auto auth_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "jdlo_auth.ini";
|
||||
Common::FS::IOFile auth_file(auth_file_path, Common::FS::FileAccessMode::Read, Common::FS::FileType::TextFile);
|
||||
if (auth_file.IsOpen()) {
|
||||
std::vector<u8> file_content_vec(auth_file.GetSize());
|
||||
if (auth_file.Read(file_content_vec) == file_content_vec.size()) {
|
||||
std::string encoded_content(file_content_vec.begin(), file_content_vec.end());
|
||||
|
||||
// Simple Base64 Decoder
|
||||
auto DecodeBase64 = [](std::string_view input) -> std::string {
|
||||
static const int T[256] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
|
||||
|
||||
std::string out;
|
||||
int val = 0, valb = -8;
|
||||
for (unsigned char c : input) {
|
||||
if (T[c] == -1)
|
||||
break;
|
||||
val = (val << 6) + T[c];
|
||||
valb += 6;
|
||||
if (valb >= 0) {
|
||||
out.push_back(char((val >> valb) & 0xFF));
|
||||
valb -= 8;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// Decode the INI content
|
||||
std::string file_content = DecodeBase64(encoded_content);
|
||||
|
||||
// Find TickedId
|
||||
std::string auth_token;
|
||||
static constexpr std::string_view KeyName = "TickedId=";
|
||||
size_t key_pos = file_content.find(KeyName);
|
||||
|
||||
if (key_pos != std::string::npos) {
|
||||
size_t value_start = key_pos + KeyName.size();
|
||||
size_t value_end = file_content.find_first_of("\r\n", value_start);
|
||||
if (value_end == std::string::npos) {
|
||||
value_end = file_content.size();
|
||||
}
|
||||
auth_token = file_content.substr(value_start, value_end - value_start);
|
||||
} else {
|
||||
// Fallback: If not found, maybe it wasn't base64 or format is different?
|
||||
// Try using raw content if decode failed to produce readable key?
|
||||
// Actually, let's just stick to the decoded content.
|
||||
}
|
||||
|
||||
if (!auth_token.empty()) {
|
||||
// Ensure the token has the correct prefix "uplaypc_v1 t="
|
||||
if (auth_token.find("uplaypc_v1 t=") == std::string::npos) {
|
||||
auth_token = "uplaypc_v1 t=" + auth_token;
|
||||
}
|
||||
|
||||
LOG_INFO(Service_SSL,
|
||||
"Injecting custom Authorization from jdlo_auth.ini (decoded "
|
||||
"TickedId): {}...",
|
||||
auth_token.substr(0, 20));
|
||||
|
||||
// Find existing Authorization header position case-insensitively
|
||||
size_t header_pos = request_lower.find("\r\nauthorization: ");
|
||||
|
||||
if (header_pos != std::string::npos) {
|
||||
size_t end_pos = request_str.find("\r\n", header_pos + 2);
|
||||
if (end_pos != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Replacing existing Authorization header.");
|
||||
request_str.replace(header_pos, end_pos - header_pos,
|
||||
"\r\nAuthorization: " + auth_token);
|
||||
}
|
||||
} else {
|
||||
LOG_INFO(Service_SSL, "Appending new Authorization header.");
|
||||
size_t body_pos = request_str.find("\r\n\r\n");
|
||||
if (body_pos != std::string::npos) {
|
||||
request_str.insert(body_pos, "\r\nAuthorization: " + auth_token);
|
||||
}
|
||||
}
|
||||
|
||||
const int ret = SSL_write_ex(ssl, request_str.data(), request_str.size(), out_size);
|
||||
if (ret == 1) { // Success
|
||||
*out_size = original_size;
|
||||
}
|
||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Service_SSL, "Failed to read jdlo_auth.ini content");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_SSL, "jdlo_auth.ini not found at {}",
|
||||
Common::FS::PathToUTF8String(auth_file_path));
|
||||
}
|
||||
}
|
||||
|
||||
const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
|
||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
||||
}
|
||||
@@ -247,6 +386,28 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
if (!ssl) {
|
||||
return ResultInternalError;
|
||||
}
|
||||
int pending = SSL_pending(ssl);
|
||||
if (pending > 0) {
|
||||
*out_pending = pending;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendOpenSSL() {
|
||||
// this is null-tolerant:
|
||||
SSL_free(ssl);
|
||||
|
||||
@@ -489,6 +489,27 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
*out_pending = static_cast<s32>(cleartext_read_buf.size());
|
||||
if (*out_pending > 0) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (!ciphertext_read_buf.empty()) {
|
||||
*out_pending = 1;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendSchannel() {
|
||||
if (handshake_state != HandshakeState::Initial) {
|
||||
DeleteSecurityContext(&ctxt);
|
||||
|
||||
@@ -149,6 +149,26 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
size_t bufferSize = 0;
|
||||
OSStatus status = SSLGetBufferedReadSize(context, &bufferSize);
|
||||
if (status == 0 && bufferSize > 0) {
|
||||
*out_pending = static_cast<s32>(bufferSize);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "core/internal_network/network_interface.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
|
||||
#include <windows.h>
|
||||
#include <wlanapi.h>
|
||||
#ifdef _MSC_VER
|
||||
|
||||
701
src/core/internal_network/legacy_online.cpp
Normal file
701
src/core/internal_network/legacy_online.cpp
Normal file
@@ -0,0 +1,701 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <iomanip>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// For SHA1 and Base64
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
// Simple SHA1 implementation for WebSocket handshake
|
||||
namespace {
|
||||
|
||||
class SHA1 {
|
||||
public:
|
||||
SHA1() { reset(); }
|
||||
|
||||
void update(const uint8_t* data, size_t len) {
|
||||
while (len--) {
|
||||
buffer[buffer_size++] = *data++;
|
||||
if (buffer_size == 64) {
|
||||
process_block();
|
||||
buffer_size = 0;
|
||||
}
|
||||
total_bits += 8;
|
||||
}
|
||||
}
|
||||
|
||||
void update(const std::string& str) {
|
||||
update(reinterpret_cast<const uint8_t*>(str.data()), str.size());
|
||||
}
|
||||
|
||||
std::array<uint8_t, 20> finalize() {
|
||||
// Padding
|
||||
buffer[buffer_size++] = 0x80;
|
||||
while (buffer_size != 56) {
|
||||
if (buffer_size == 64) {
|
||||
process_block();
|
||||
buffer_size = 0;
|
||||
}
|
||||
buffer[buffer_size++] = 0;
|
||||
}
|
||||
|
||||
// Append length in bits
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
buffer[buffer_size++] = static_cast<uint8_t>(total_bits >> (i * 8));
|
||||
}
|
||||
process_block();
|
||||
|
||||
std::array<uint8_t, 20> result;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
result[i*4+0] = static_cast<uint8_t>(h[i] >> 24);
|
||||
result[i*4+1] = static_cast<uint8_t>(h[i] >> 16);
|
||||
result[i*4+2] = static_cast<uint8_t>(h[i] >> 8);
|
||||
result[i*4+3] = static_cast<uint8_t>(h[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
void reset() {
|
||||
h[0] = 0x67452301;
|
||||
h[1] = 0xEFCDAB89;
|
||||
h[2] = 0x98BADCFE;
|
||||
h[3] = 0x10325476;
|
||||
h[4] = 0xC3D2E1F0;
|
||||
buffer_size = 0;
|
||||
total_bits = 0;
|
||||
}
|
||||
|
||||
void process_block() {
|
||||
uint32_t w[80];
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
w[i] = (buffer[i*4+0] << 24) | (buffer[i*4+1] << 16) |
|
||||
(buffer[i*4+2] << 8) | buffer[i*4+3];
|
||||
}
|
||||
for (int i = 16; i < 80; ++i) {
|
||||
uint32_t t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
|
||||
w[i] = (t << 1) | (t >> 31);
|
||||
}
|
||||
|
||||
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
|
||||
|
||||
for (int i = 0; i < 80; ++i) {
|
||||
uint32_t f, k;
|
||||
if (i < 20) {
|
||||
f = (b & c) | ((~b) & d);
|
||||
k = 0x5A827999;
|
||||
} else if (i < 40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 0x6ED9EBA1;
|
||||
} else if (i < 60) {
|
||||
f = (b & c) | (b & d) | (c & d);
|
||||
k = 0x8F1BBCDC;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 0xCA62C1D6;
|
||||
}
|
||||
|
||||
uint32_t temp = ((a << 5) | (a >> 27)) + f + e + k + w[i];
|
||||
e = d;
|
||||
d = c;
|
||||
c = (b << 30) | (b >> 2);
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
h[0] += a;
|
||||
h[1] += b;
|
||||
h[2] += c;
|
||||
h[3] += d;
|
||||
h[4] += e;
|
||||
}
|
||||
|
||||
uint32_t h[5];
|
||||
uint8_t buffer[64];
|
||||
size_t buffer_size;
|
||||
uint64_t total_bits;
|
||||
};
|
||||
|
||||
std::string base64_encode(const uint8_t* data, size_t len) {
|
||||
static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::string result;
|
||||
result.reserve((len + 2) / 3 * 4);
|
||||
|
||||
for (size_t i = 0; i < len; i += 3) {
|
||||
uint32_t n = static_cast<uint32_t>(data[i]) << 16;
|
||||
if (i + 1 < len) n |= static_cast<uint32_t>(data[i + 1]) << 8;
|
||||
if (i + 2 < len) n |= static_cast<uint32_t>(data[i + 2]);
|
||||
|
||||
result += chars[(n >> 18) & 0x3F];
|
||||
result += chars[(n >> 12) & 0x3F];
|
||||
result += (i + 1 < len) ? chars[(n >> 6) & 0x3F] : '=';
|
||||
result += (i + 2 < len) ? chars[n & 0x3F] : '=';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string compute_websocket_accept(const std::string& key) {
|
||||
// WebSocket magic GUID
|
||||
const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
std::string combined = key + magic;
|
||||
|
||||
SHA1 sha1;
|
||||
sha1.update(combined);
|
||||
auto hash = sha1.finalize();
|
||||
|
||||
return base64_encode(hash.data(), hash.size());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
LegacyOnlineService::LegacyOnlineService() {
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa_data;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
|
||||
LOG_ERROR(Network, "WSAStartup failed with error: {}", WSAGetLastError());
|
||||
winsock_initialized = false;
|
||||
} else {
|
||||
winsock_initialized = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
LegacyOnlineService::~LegacyOnlineService() {
|
||||
Stop();
|
||||
#ifdef _WIN32
|
||||
if (winsock_initialized) {
|
||||
WSACleanup();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Start() {
|
||||
if (is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!winsock_initialized) {
|
||||
LOG_ERROR(Network, "Cannot start Legacy Online Service: Winsock not initialized");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
is_running = true;
|
||||
udp_worker_thread = std::thread(&LegacyOnlineService::UdpServerLoop, this);
|
||||
http_worker_thread = std::thread(&LegacyOnlineService::HttpServerLoop, this);
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Stop() {
|
||||
if (!is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_running = false;
|
||||
|
||||
if (udp_socket_fd != ~0ULL) {
|
||||
#ifdef _WIN32
|
||||
closesocket(static_cast<SOCKET>(udp_socket_fd));
|
||||
#else
|
||||
close(static_cast<int>(udp_socket_fd));
|
||||
#endif
|
||||
udp_socket_fd = ~0ULL;
|
||||
}
|
||||
|
||||
if (http_socket_fd != ~0ULL) {
|
||||
#ifdef _WIN32
|
||||
closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
}
|
||||
|
||||
if (udp_worker_thread.joinable()) {
|
||||
udp_worker_thread.join();
|
||||
}
|
||||
if (http_worker_thread.joinable()) {
|
||||
http_worker_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void LegacyOnlineService::UdpServerLoop() {
|
||||
LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", UDP_PORT);
|
||||
|
||||
auto s = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
#ifdef _WIN32
|
||||
if (s == INVALID_SOCKET) {
|
||||
#else
|
||||
if (s == -1) {
|
||||
#endif
|
||||
LOG_ERROR(Network, "Failed to create UDP socket");
|
||||
return;
|
||||
}
|
||||
udp_socket_fd = static_cast<uintptr_t>(s);
|
||||
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(static_cast<SOCKET>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(static_cast<int>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(UDP_PORT);
|
||||
|
||||
int res = -1;
|
||||
#ifdef _WIN32
|
||||
res = bind(static_cast<SOCKET>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#else
|
||||
res = bind(static_cast<int>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(udp_socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, strerror(errno));
|
||||
close(static_cast<int>(udp_socket_fd));
|
||||
#endif
|
||||
udp_socket_fd = ~0ULL;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "Legacy Online UDP Server waiting for messages...");
|
||||
|
||||
char buffer[2048];
|
||||
while (is_running) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int client_len = sizeof(client_addr);
|
||||
int len = recvfrom(static_cast<SOCKET>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#else
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
ssize_t len = recvfrom(static_cast<int>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#endif
|
||||
|
||||
if (!is_running) break;
|
||||
|
||||
if (len > 0) {
|
||||
const char* ack_msg = "ACK";
|
||||
#ifdef _WIN32
|
||||
sendto(static_cast<SOCKET>(udp_socket_fd), ack_msg, static_cast<int>(strlen(ack_msg)), 0, (sockaddr*)&client_addr, client_len);
|
||||
#else
|
||||
sendto(static_cast<int>(udp_socket_fd), ack_msg, strlen(ack_msg), 0, (sockaddr*)&client_addr, client_len);
|
||||
#endif
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (udp_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(udp_socket_fd));
|
||||
#else
|
||||
if (udp_socket_fd != ~0ULL) close(static_cast<int>(udp_socket_fd));
|
||||
#endif
|
||||
udp_socket_fd = ~0ULL;
|
||||
LOG_INFO(Network, "Legacy Online UDP Server stopped");
|
||||
}
|
||||
|
||||
void LegacyOnlineService::HttpServerLoop() {
|
||||
LOG_INFO(Network, "Starting Mobile App HTTP/WebSocket Server on port {}", HTTP_PORT);
|
||||
|
||||
auto s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
#ifdef _WIN32
|
||||
if (s == INVALID_SOCKET) {
|
||||
#else
|
||||
if (s == -1) {
|
||||
#endif
|
||||
LOG_ERROR(Network, "Failed to create HTTP socket");
|
||||
return;
|
||||
}
|
||||
http_socket_fd = static_cast<uintptr_t>(s);
|
||||
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(static_cast<SOCKET>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(static_cast<int>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(HTTP_PORT);
|
||||
|
||||
int res = -1;
|
||||
#ifdef _WIN32
|
||||
res = bind(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#else
|
||||
res = bind(static_cast<int>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, strerror(errno));
|
||||
close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
res = listen(static_cast<SOCKET>(http_socket_fd), 10);
|
||||
#else
|
||||
res = listen(static_cast<int>(http_socket_fd), 10);
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, strerror(errno));
|
||||
close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server listening on port {}...", HTTP_PORT);
|
||||
|
||||
while (is_running) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int client_len = sizeof(client_addr);
|
||||
SOCKET client_fd = accept(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
|
||||
if (client_fd == INVALID_SOCKET) {
|
||||
#else
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
int client_fd = accept(static_cast<int>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
|
||||
if (client_fd == -1) {
|
||||
#endif
|
||||
if (!is_running) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
char client_ip[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
|
||||
LOG_INFO(Network, "HTTP/WebSocket connection from {}:{}", client_ip, ntohs(client_addr.sin_port));
|
||||
|
||||
// Read HTTP request
|
||||
char buffer[4096];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
#ifdef _WIN32
|
||||
int bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||
#else
|
||||
ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||
#endif
|
||||
|
||||
if (bytes_read > 0) {
|
||||
std::string request(buffer, bytes_read);
|
||||
LOG_INFO(Network, "Request:\n{}", request);
|
||||
|
||||
// Check if this is a WebSocket upgrade request
|
||||
bool is_websocket = request.find("Upgrade: websocket") != std::string::npos;
|
||||
|
||||
if (is_websocket) {
|
||||
// Extract Sec-WebSocket-Key
|
||||
std::string ws_key;
|
||||
std::regex key_regex("Sec-WebSocket-Key: ([^\r\n]+)");
|
||||
std::smatch match;
|
||||
if (std::regex_search(request, match, key_regex)) {
|
||||
ws_key = match[1].str();
|
||||
}
|
||||
|
||||
// Extract Sec-WebSocket-Protocol
|
||||
std::string ws_protocol;
|
||||
std::regex protocol_regex("Sec-WebSocket-Protocol: ([^\r\n]+)");
|
||||
if (std::regex_search(request, match, protocol_regex)) {
|
||||
ws_protocol = match[1].str();
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "WebSocket upgrade request - Key: {}, Protocol: {}", ws_key, ws_protocol);
|
||||
|
||||
// Compute accept key
|
||||
std::string accept_key = compute_websocket_accept(ws_key);
|
||||
LOG_INFO(Network, "WebSocket Accept Key: {}", accept_key);
|
||||
|
||||
// Build WebSocket handshake response
|
||||
std::ostringstream ws_response;
|
||||
ws_response << "HTTP/1.1 101 Switching Protocols\r\n";
|
||||
ws_response << "Upgrade: websocket\r\n";
|
||||
ws_response << "Connection: Upgrade\r\n";
|
||||
ws_response << "Sec-WebSocket-Accept: " << accept_key << "\r\n";
|
||||
if (!ws_protocol.empty()) {
|
||||
ws_response << "Sec-WebSocket-Protocol: " << ws_protocol << "\r\n";
|
||||
}
|
||||
ws_response << "\r\n";
|
||||
|
||||
std::string response_str = ws_response.str();
|
||||
#ifdef _WIN32
|
||||
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
|
||||
#else
|
||||
send(client_fd, response_str.c_str(), response_str.size(), 0);
|
||||
#endif
|
||||
LOG_INFO(Network, "WebSocket handshake completed! Response:\n{}", response_str);
|
||||
|
||||
// Now handle WebSocket messages
|
||||
LOG_INFO(Network, "Mobile app WebSocket connected! Entering message loop...");
|
||||
|
||||
while (is_running) {
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
#ifdef _WIN32
|
||||
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
|
||||
#else
|
||||
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
|
||||
#endif
|
||||
if (bytes_read <= 0) {
|
||||
LOG_INFO(Network, "WebSocket connection closed");
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse WebSocket frame
|
||||
uint8_t* frame = reinterpret_cast<uint8_t*>(buffer);
|
||||
uint8_t opcode = frame[0] & 0x0F;
|
||||
bool masked = (frame[1] & 0x80) != 0;
|
||||
uint64_t payload_len = frame[1] & 0x7F;
|
||||
|
||||
size_t header_len = 2;
|
||||
if (payload_len == 126) {
|
||||
payload_len = (frame[2] << 8) | frame[3];
|
||||
header_len = 4;
|
||||
} else if (payload_len == 127) {
|
||||
header_len = 10;
|
||||
payload_len = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
payload_len = (payload_len << 8) | frame[2 + i];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t mask_key[4] = {0};
|
||||
if (masked) {
|
||||
memcpy(mask_key, frame + header_len, 4);
|
||||
header_len += 4;
|
||||
}
|
||||
|
||||
// Unmask payload
|
||||
std::string payload;
|
||||
for (size_t i = 0; i < payload_len && (header_len + i) < static_cast<size_t>(bytes_read); ++i) {
|
||||
char c = frame[header_len + i];
|
||||
if (masked) {
|
||||
c ^= mask_key[i % 4];
|
||||
}
|
||||
payload += c;
|
||||
}
|
||||
|
||||
// LOG_INFO(Network, "WebSocket message (opcode={}): {}", opcode, payload);
|
||||
|
||||
// Handle different opcodes
|
||||
if (opcode == 0x08) {
|
||||
// Close frame
|
||||
// LOG_INFO(Network, "WebSocket close frame received");
|
||||
break;
|
||||
} else if (opcode == 0x09) {
|
||||
// Ping - send pong. Theoretically should echo payload, but empty pong is usually fine.
|
||||
// LOG_INFO(Network, "WebSocket PING received. Responding with PONG.");
|
||||
uint8_t pong[2] = {0x8A, 0x00};
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(pong), 2, 0);
|
||||
#else
|
||||
send(client_fd, pong, 2, 0);
|
||||
#endif
|
||||
} else if (opcode == 0x0A) {
|
||||
// Pong (keep-alive response from client)
|
||||
// LOG_INFO(Network, "WebSocket PONG received from client.");
|
||||
} else if (opcode == 0x01 || opcode == 0x02) {
|
||||
// Text or Binary frame - process Just Dance protocol
|
||||
std::string response;
|
||||
|
||||
// Check message type and respond appropriately
|
||||
// Protocol flow based on JoyDance:
|
||||
// 1. Phone -> Console: JD_PhoneDataCmdHandshakeHello
|
||||
// 2. Console -> Phone: JD_PhoneDataCmdHandshakeContinue (with phoneID)
|
||||
// 3. Phone -> Console: JD_PhoneDataCmdSync (with phoneID)
|
||||
// 4. Console -> Phone: JD_PhoneDataCmdSyncEnd (with phoneID)
|
||||
// 5. Connected!
|
||||
|
||||
if (payload.find("JD_PhoneDataCmdHandshakeHello") != std::string::npos) {
|
||||
// Step 2: Respond with HandshakeContinue
|
||||
// PROTOCOL FIX: No "root" wrapper. Authenticated ID=1 (Int).
|
||||
// CRITICAL: App sent Freq=0. Providing configuration values.
|
||||
// CLEANUP: Removed extra status fields to prevent parsing errors.
|
||||
response = R"({"__class":"JD_PhoneDataCmdHandshakeContinue","phoneID":1,"accelAcquisitionFreqHz":50,"accelAcquisitionLatency":40,"accelMaxRange":8})";
|
||||
// LOG_INFO(Network, "Sending HandshakeContinue (id=1, cfg=50Hz)");
|
||||
|
||||
// Send HandshakeContinue
|
||||
std::vector<uint8_t> ws_frame;
|
||||
ws_frame.push_back(0x81);
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size()));
|
||||
for (char c : response) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
|
||||
#else
|
||||
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
|
||||
#endif
|
||||
// LOG_INFO(Network, "WebSocket response sent: {}", response);
|
||||
|
||||
// Removed proactive commands to verify if app accepts HandshakeContinue and sends Sync
|
||||
response.clear();
|
||||
|
||||
} else if (payload.find("JD_PhoneDataCmdSync") != std::string::npos) {
|
||||
// Step 4: Respond with SyncEnd (NO ROOT)
|
||||
// Using phoneID 1 (Int)
|
||||
response = R"({"__class":"JD_PhoneDataCmdSyncEnd","phoneID":1,"status":"ok"})";
|
||||
// LOG_INFO(Network, "Sending SyncEnd (id=1, no root) - Connection complete!");
|
||||
|
||||
// Send SyncEnd
|
||||
std::vector<uint8_t> ws_frame;
|
||||
ws_frame.push_back(0x81);
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size()));
|
||||
for (char c : response) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
|
||||
#else
|
||||
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
|
||||
#endif
|
||||
|
||||
// Step 5: Send Activation Commands immediately to authorize input and accel
|
||||
// 5a. Enable Input
|
||||
std::string cmd1 = R"({"__class":"InputSetup_ConsoleCommandData","isEnabled":1,"inputSetup":{"isEnabled":1}})";
|
||||
std::vector<uint8_t> f1; f1.push_back(0x81); f1.push_back(static_cast<uint8_t>(cmd1.size())); for(char c:cmd1) f1.push_back(static_cast<uint8_t>(c));
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(f1.data()), static_cast<int>(f1.size()), 0);
|
||||
#else
|
||||
send(client_fd, f1.data(), f1.size(), 0);
|
||||
#endif
|
||||
|
||||
// 5b. Enable Accelerometer
|
||||
std::string cmd2 = R"({"__class":"JD_EnableAccelValuesSending_ConsoleCommandData","isEnabled":1})";
|
||||
std::vector<uint8_t> f2; f2.push_back(0x81); f2.push_back(static_cast<uint8_t>(cmd2.size())); for(char c:cmd2) f2.push_back(static_cast<uint8_t>(c));
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(f2.data()), static_cast<int>(f2.size()), 0);
|
||||
#else
|
||||
send(client_fd, f2.data(), f2.size(), 0);
|
||||
#endif
|
||||
|
||||
// 5c. UI Setup (optional but good for safety)
|
||||
std::string cmd3 = R"({"__class":"JD_PhoneUiSetupData","isPopup":0,"inputSetup":{"isEnabled":1}})";
|
||||
std::vector<uint8_t> f3; f3.push_back(0x81); f3.push_back(static_cast<uint8_t>(cmd3.size())); for(char c:cmd3) f3.push_back(static_cast<uint8_t>(c));
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(f3.data()), static_cast<int>(f3.size()), 0);
|
||||
#else
|
||||
send(client_fd, f3.data(), f3.size(), 0);
|
||||
#endif
|
||||
|
||||
// LOG_INFO(Network, "Sent Activation Commands (Input, Accel, UI)");
|
||||
response.clear();
|
||||
} else if (payload.find("JD_PhoneScoringData") != std::string::npos) {
|
||||
// Accelerometer/scoring data - no response needed
|
||||
// LOG_DEBUG(Network, "Received phone scoring data");
|
||||
continue;
|
||||
} else if (payload.find("JD_Input_PhoneCommandData") != std::string::npos) {
|
||||
// Button input from phone - no response needed
|
||||
// LOG_INFO(Network, "Received phone input command");
|
||||
continue;
|
||||
} else if (payload.find("JD_Pause_PhoneCommandData") != std::string::npos) {
|
||||
// Pause command from phone
|
||||
// LOG_INFO(Network, "Received pause command from phone");
|
||||
continue;
|
||||
} else if (payload.find("JD_Custom_PhoneCommandData") != std::string::npos) {
|
||||
// Custom shortcut command
|
||||
// LOG_INFO(Network, "Received custom command from phone");
|
||||
continue;
|
||||
} else if (payload.find("JD_CancelKeyboard_PhoneCommandData") != std::string::npos) {
|
||||
// Keyboard cancelled
|
||||
// LOG_INFO(Network, "Phone cancelled keyboard");
|
||||
continue;
|
||||
} else {
|
||||
// Unknown message - log but don't respond
|
||||
// LOG_INFO(Network, "Unknown phone message type");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.empty()) {
|
||||
// Build WebSocket frame
|
||||
std::vector<uint8_t> ws_frame;
|
||||
ws_frame.push_back(0x81); // Text frame, FIN
|
||||
if (response.size() < 126) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size()));
|
||||
} else if (response.size() < 65536) {
|
||||
ws_frame.push_back(126);
|
||||
ws_frame.push_back(static_cast<uint8_t>((response.size() >> 8) & 0xFF));
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size() & 0xFF));
|
||||
}
|
||||
for (char c : response) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
|
||||
#else
|
||||
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
|
||||
#endif
|
||||
LOG_INFO(Network, "WebSocket response sent: {}", response);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular HTTP request
|
||||
std::string response_body = R"({"status":"ok","message":"Eden Mobile Bridge"})";
|
||||
std::ostringstream http_response;
|
||||
http_response << "HTTP/1.1 200 OK\r\n";
|
||||
http_response << "Content-Type: application/json\r\n";
|
||||
http_response << "Content-Length: " << response_body.size() << "\r\n";
|
||||
http_response << "Connection: close\r\n";
|
||||
http_response << "\r\n";
|
||||
http_response << response_body;
|
||||
|
||||
std::string response_str = http_response.str();
|
||||
#ifdef _WIN32
|
||||
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
|
||||
#else
|
||||
send(client_fd, response_str.c_str(), response_str.size(), 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (http_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
if (http_socket_fd != ~0ULL) close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server stopped");
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
36
src/core/internal_network/legacy_online.h
Normal file
36
src/core/internal_network/legacy_online.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace Network {
|
||||
|
||||
class LegacyOnlineService {
|
||||
public:
|
||||
LegacyOnlineService();
|
||||
~LegacyOnlineService();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void UdpServerLoop();
|
||||
void HttpServerLoop();
|
||||
|
||||
std::atomic_bool is_running{false};
|
||||
std::thread udp_worker_thread;
|
||||
std::thread http_worker_thread;
|
||||
uintptr_t udp_socket_fd{~0ULL};
|
||||
uintptr_t http_socket_fd{~0ULL};
|
||||
bool winsock_initialized{false};
|
||||
|
||||
static constexpr int UDP_PORT = 6000;
|
||||
static constexpr int HTTP_PORT = 8080;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -75,6 +75,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_socket;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -83,6 +85,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
#endif
|
||||
|
||||
switch (static_cast<Domain>(input.family)) {
|
||||
case Domain::Unspecified:
|
||||
case Domain::INET:
|
||||
result.sin_family = AF_INET;
|
||||
break;
|
||||
@@ -105,6 +108,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
LINGER MakeLinger(bool enable, u32 linger_value) {
|
||||
ASSERT(linger_value <= (std::numeric_limits<u_short>::max)());
|
||||
|
||||
@@ -124,6 +129,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
case 0:
|
||||
return Errno::SUCCESS;
|
||||
case WSAEBADF:
|
||||
case WSAENOTSOCK:
|
||||
return Errno::BADF;
|
||||
case WSAEINVAL:
|
||||
return Errno::INVAL;
|
||||
@@ -157,6 +163,10 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
return Errno::TIMEDOUT;
|
||||
case WSAEINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case WSAEACCES:
|
||||
return Errno::ACCES;
|
||||
case WSAEADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||
return Errno::OTHER;
|
||||
@@ -212,6 +222,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_pipe_fd[0];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -234,6 +246,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
|
||||
return poll(fds, static_cast<nfds_t>(nfds), timeout);
|
||||
}
|
||||
@@ -320,6 +334,8 @@ Errno GetAndLogLastError(CallType call_type = CallType::Other) {
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
switch (gai_err) {
|
||||
case 0:
|
||||
@@ -373,6 +389,8 @@ GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Domain TranslateDomainFromNative(int domain) {
|
||||
switch (domain) {
|
||||
case 0:
|
||||
@@ -607,6 +625,8 @@ Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
|
||||
const size_t num = pollfds.size();
|
||||
|
||||
@@ -684,6 +704,10 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
|
||||
fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type),
|
||||
TranslateProtocolToNative(protocol));
|
||||
if (fd != INVALID_SOCKET) {
|
||||
// Enable SO_REUSEADDR to allow port reuse after socket close
|
||||
// This prevents "Address already in use" errors when rebinding
|
||||
int reuse = 1;
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&reuse), sizeof(reuse));
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -801,6 +825,35 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::span<u8> message) {
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
|
||||
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
|
||||
if (!is_non_blocking) {
|
||||
std::vector<WSAPOLLFD> host_pollfds{
|
||||
WSAPOLLFD{fd, POLLIN, 0},
|
||||
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
|
||||
};
|
||||
|
||||
// Poll with a longer timeout (30 seconds) to wait for data
|
||||
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 30000);
|
||||
|
||||
if (host_pollfds[1].revents != 0) {
|
||||
// Interrupt signaled, return EAGAIN
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres == 0) {
|
||||
// Timeout - return AGAIN so the game can retry
|
||||
LOG_DEBUG(Network, "Recv poll timeout after 30 seconds, returning EAGAIN");
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres < 0) {
|
||||
return {-1, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
// Data is available, proceed with recv
|
||||
LOG_INFO(Network, "Recv poll detected data available!");
|
||||
}
|
||||
|
||||
const auto result =
|
||||
recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
|
||||
if (result != SOCKET_ERROR) {
|
||||
@@ -814,6 +867,35 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
|
||||
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
|
||||
if (!is_non_blocking) {
|
||||
std::vector<WSAPOLLFD> host_pollfds{
|
||||
WSAPOLLFD{fd, POLLIN, 0},
|
||||
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
|
||||
};
|
||||
|
||||
// Poll with a short timeout (5ms) to emulate Switch non-blocking/interruptible behavior
|
||||
// This prevents the game from freezing when waiting for UDP packets on the main thread
|
||||
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 5);
|
||||
|
||||
if (host_pollfds[1].revents != 0) {
|
||||
// Interrupt signaled, return EAGAIN
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres == 0) {
|
||||
// Timeout - return AGAIN so the game can continue its loop (rendering, input, etc.)
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres < 0) {
|
||||
return {-1, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
// Data is available, proceed with recvfrom
|
||||
LOG_INFO(Network, "RecvFrom poll detected data available!");
|
||||
}
|
||||
|
||||
sockaddr_in addr_in{};
|
||||
socklen_t addrlen = sizeof(addr_in);
|
||||
socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
|
||||
@@ -824,6 +906,10 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
|
||||
if (result != SOCKET_ERROR) {
|
||||
if (addr) {
|
||||
*addr = TranslateToSockAddrIn(addr_in, addrlen);
|
||||
LOG_INFO(Network, "RecvFrom received {} bytes from {}:{}",
|
||||
result, IPv4AddressToString(addr->ip), addr->portno);
|
||||
} else {
|
||||
LOG_INFO(Network, "RecvFrom received {} bytes", result);
|
||||
}
|
||||
return {static_cast<s32>(result), Errno::SUCCESS};
|
||||
}
|
||||
@@ -859,20 +945,66 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
|
||||
if (addr) {
|
||||
host_addr_in = TranslateFromSockAddrIn(*addr);
|
||||
to = &host_addr_in;
|
||||
LOG_INFO(Network, "SendTo sending {} bytes to {}:{}",
|
||||
message.size(), IPv4AddressToString(addr->ip), addr->portno);
|
||||
} else {
|
||||
LOG_INFO(Network, "SendTo sending {} bytes (no addr)", message.size());
|
||||
}
|
||||
|
||||
// Log packet content for debugging mobile app connection
|
||||
if (message.size() > 0 && message.size() <= 200) {
|
||||
std::string hex_dump;
|
||||
std::string ascii_dump;
|
||||
for (size_t i = 0; i < message.size(); ++i) {
|
||||
char hex[4];
|
||||
snprintf(hex, sizeof(hex), "%02X ", message[i]);
|
||||
hex_dump += hex;
|
||||
// Build ASCII representation
|
||||
if (message[i] >= 32 && message[i] < 127) {
|
||||
ascii_dump += static_cast<char>(message[i]);
|
||||
} else {
|
||||
ascii_dump += '.';
|
||||
}
|
||||
}
|
||||
LOG_INFO(Network, "SendTo packet HEX: {}", hex_dump);
|
||||
LOG_INFO(Network, "SendTo packet ASCII: {}", ascii_dump);
|
||||
} else if (message.size() > 200) {
|
||||
// Log first 200 bytes for large packets
|
||||
std::string hex_dump;
|
||||
std::string ascii_dump;
|
||||
for (size_t i = 0; i < 200; ++i) {
|
||||
char hex[4];
|
||||
snprintf(hex, sizeof(hex), "%02X ", message[i]);
|
||||
hex_dump += hex;
|
||||
if (message[i] >= 32 && message[i] < 127) {
|
||||
ascii_dump += static_cast<char>(message[i]);
|
||||
} else {
|
||||
ascii_dump += '.';
|
||||
}
|
||||
}
|
||||
LOG_INFO(Network, "SendTo packet HEX (first 200): {}", hex_dump);
|
||||
LOG_INFO(Network, "SendTo packet ASCII (first 200): {}", ascii_dump);
|
||||
}
|
||||
|
||||
const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
|
||||
static_cast<int>(message.size()), 0, to, to_len);
|
||||
if (result != SOCKET_ERROR) {
|
||||
LOG_INFO(Network, "SendTo success: sent {} bytes", result);
|
||||
return {static_cast<s32>(result), Errno::SUCCESS};
|
||||
}
|
||||
|
||||
LOG_ERROR(Network, "SendTo failed!");
|
||||
return {-1, GetAndLogLastError(CallType::Send)};
|
||||
}
|
||||
|
||||
Errno Socket::Close() {
|
||||
if (fd == INVALID_SOCKET) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
[[maybe_unused]] const int result = closesocket(fd);
|
||||
ASSERT(result == 0);
|
||||
if (result != 0) {
|
||||
GetAndLogLastError();
|
||||
}
|
||||
fd = INVALID_SOCKET;
|
||||
|
||||
return Errno::SUCCESS;
|
||||
|
||||
@@ -48,6 +48,8 @@ enum class Errno {
|
||||
TIMEDOUT,
|
||||
MSGSIZE,
|
||||
INPROGRESS,
|
||||
ACCES,
|
||||
ADDRINUSE,
|
||||
OTHER,
|
||||
};
|
||||
|
||||
@@ -122,8 +124,33 @@ std::optional<IPv4Address> GetHostIPv4Address();
|
||||
std::string IPv4AddressToString(IPv4Address ip_addr);
|
||||
u32 IPv4AddressToInteger(IPv4Address ip_addr);
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
// named to avoid name collision with Windows macro
|
||||
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
const std::string& host, const std::optional<std::string>& service);
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input);
|
||||
|
||||
inline std::pair<std::string, int> GetNameInfo(const SockAddrIn& addr) {
|
||||
sockaddr sa = TranslateFromSockAddrIn(addr);
|
||||
sockaddr_in addr_in{};
|
||||
std::memcpy(&addr_in, &sa, sizeof(sockaddr_in));
|
||||
|
||||
char host[1025]; // NI_MAXHOST
|
||||
int err = getnameinfo(reinterpret_cast<const sockaddr*>(&addr_in), sizeof(addr_in), host, sizeof(host), nullptr, 0, 0);
|
||||
|
||||
if (err != 0) {
|
||||
return {std::string{}, err};
|
||||
}
|
||||
|
||||
return {std::string(host), 0};
|
||||
}
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int err);
|
||||
|
||||
} // namespace Network
|
||||
|
||||
@@ -107,7 +107,7 @@ else()
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.57 REQUIRED)
|
||||
find_package(fmt 8 CONFIG)
|
||||
# find_package(fmt 8 CONFIG)
|
||||
|
||||
# Pull in externals CMakeLists for libs where available
|
||||
add_subdirectory(externals)
|
||||
|
||||
@@ -97,15 +97,15 @@ public:
|
||||
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
void InterpreterFallback(u32 pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(u32 /*pc*/, size_t /*num_instructions*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:08x} && {}) code = {:08x}", pc, num_instructions, *MemoryReadCode(pc));
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception /*exception*/) override {
|
||||
void ExceptionRaised(u32 /*pc*/, Dynarmic::A32::Exception /*exception*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:08x}) code = {:08x}", pc, *MemoryReadCode(pc));
|
||||
}
|
||||
|
||||
@@ -190,15 +190,15 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterpreterFallback(std::uint32_t pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(std::uint32_t /*pc*/, size_t /*num_instructions*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:016x} && {})", pc, num_instructions);
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(std::uint32_t pc, Dynarmic::A32::Exception) override {
|
||||
void ExceptionRaised(std::uint32_t /*pc*/, Dynarmic::A32::Exception) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,15 +105,15 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterpreterFallback(u64 pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(u64 /*pc*/, size_t /*num_instructions*/) override {
|
||||
UNREACHABLE(); // ASSERT(false&& "InterpreterFallback({:016x} && {})", pc, num_instructions);
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception /*exception*/) override {
|
||||
void ExceptionRaised(u64 /*pc*/, Dynarmic::A64::Exception /*exception*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
|
||||
}
|
||||
|
||||
@@ -208,15 +208,15 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterpreterFallback(u64 pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(u64 /*pc*/, size_t /*num_instructions*/) override {
|
||||
ASSERT(ignore_invalid_insn && "InterpreterFallback");
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception) override {
|
||||
void ExceptionRaised(u64 /*pc*/, Dynarmic::A64::Exception) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,4 +134,4 @@ target_include_directories(dynarmic_tests PRIVATE . ../src)
|
||||
target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS})
|
||||
target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
|
||||
|
||||
add_test(dynarmic_tests dynarmic_tests --durations yes)
|
||||
add_test(NAME dynarmic_tests COMMAND dynarmic_tests --durations yes)
|
||||
|
||||
@@ -48,7 +48,7 @@ private:
|
||||
void Save();
|
||||
|
||||
PlayTimeDatabase database;
|
||||
u64 running_program_id;
|
||||
u64 running_program_id{};
|
||||
std::jthread play_time_thread;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -53,7 +56,7 @@ struct ChatEntry {
|
||||
|
||||
/// Represents a system status message.
|
||||
struct StatusMessageEntry {
|
||||
StatusMessageTypes type; ///< Type of the message
|
||||
StatusMessageTypes type{}; ///< Type of the message
|
||||
/// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
|
||||
std::string nickname;
|
||||
std::string username;
|
||||
|
||||
@@ -446,10 +446,12 @@ void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
void EmitSR_WScaleFactorXY(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3c003c00u;", inst);
|
||||
}
|
||||
|
||||
void EmitSR_WScaleFactorZ(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3f800000u;", inst);
|
||||
}
|
||||
|
||||
void EmitYDirection(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
@@ -58,6 +58,9 @@ std::string FormatFloat(std::string_view value, IR::Type type) {
|
||||
if (value == "nan") {
|
||||
return "utof(0x7fc00000)";
|
||||
}
|
||||
if (value == "-nan") {
|
||||
return "utof(0xffc00000)";
|
||||
}
|
||||
if (value == "inf") {
|
||||
return "utof(0x7f800000)";
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <vector>
|
||||
#include <spirv-tools/optimizer.hpp>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||
@@ -440,23 +439,15 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
|
||||
ctx.AddExtension("SPV_KHR_shader_draw_parameters");
|
||||
ctx.AddCapability(spv::Capability::DrawParameters);
|
||||
}
|
||||
const bool stage_supports_warp = profile.SupportsWarpIntrinsics(ctx.stage);
|
||||
const bool needs_warp_intrinsics = info.uses_subgroup_vote ||
|
||||
info.uses_subgroup_invocation_id ||
|
||||
info.uses_subgroup_shuffles;
|
||||
|
||||
if (needs_warp_intrinsics && profile.support_vote && stage_supports_warp) {
|
||||
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
|
||||
info.uses_subgroup_shuffles) &&
|
||||
profile.support_vote) {
|
||||
ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
|
||||
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
|
||||
if (!profile.warp_size_potentially_larger_than_guest) {
|
||||
// vote ops are only used when not taking the long path
|
||||
ctx.AddCapability(spv::Capability::GroupNonUniformVote);
|
||||
}
|
||||
} else if (needs_warp_intrinsics && !stage_supports_warp) {
|
||||
LOG_WARNING(Shader,
|
||||
"Warp intrinsics requested in stage {} but the device does not report subgroup "
|
||||
"support; falling back to scalar approximations",
|
||||
static_cast<u32>(ctx.stage));
|
||||
}
|
||||
if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
|
||||
ctx.AddCapability(spv::Capability::Int64Atomics);
|
||||
|
||||
@@ -491,24 +491,9 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
|
||||
}
|
||||
|
||||
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
|
||||
const AttributeType output_type{ctx.runtime_info.color_output_types[index]};
|
||||
Id pointer_type{ctx.output_f32};
|
||||
Id store_value{value};
|
||||
switch (output_type) {
|
||||
case AttributeType::SignedInt:
|
||||
pointer_type = ctx.output_s32;
|
||||
store_value = ctx.OpBitcast(ctx.S32[1], value);
|
||||
break;
|
||||
case AttributeType::UnsignedInt:
|
||||
pointer_type = ctx.output_u32;
|
||||
store_value = ctx.OpBitcast(ctx.U32[1], value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const Id component_id{ctx.Const(component)};
|
||||
const Id pointer{ctx.OpAccessChain(pointer_type, ctx.frag_color.at(index), component_id)};
|
||||
ctx.OpStore(pointer, store_value);
|
||||
const Id pointer{ctx.OpAccessChain(ctx.output_f32, ctx.frag_color.at(index), component_id)};
|
||||
ctx.OpStore(pointer, value);
|
||||
}
|
||||
|
||||
void EmitSetSampleMask(EmitContext& ctx, Id value) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -198,41 +195,6 @@ Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR
|
||||
}
|
||||
}
|
||||
|
||||
Id TextureColorResultType(EmitContext& ctx, const TextureDefinition& def) {
|
||||
switch (def.component_type) {
|
||||
case SamplerComponentType::Float:
|
||||
case SamplerComponentType::Depth:
|
||||
return ctx.F32[4];
|
||||
case SamplerComponentType::Sint:
|
||||
return ctx.S32[4];
|
||||
case SamplerComponentType::Stencil:
|
||||
return ctx.U32[4];
|
||||
case SamplerComponentType::Uint:
|
||||
return ctx.U32[4];
|
||||
}
|
||||
throw InvalidArgument("Invalid sampler component type {}", def.component_type);
|
||||
}
|
||||
|
||||
Id TextureSampleResultToFloat(EmitContext& ctx, const TextureDefinition& def, Id color) {
|
||||
switch (def.component_type) {
|
||||
case SamplerComponentType::Float:
|
||||
case SamplerComponentType::Depth:
|
||||
return color;
|
||||
case SamplerComponentType::Sint:
|
||||
return ctx.OpConvertSToF(ctx.F32[4], color);
|
||||
case SamplerComponentType::Stencil:
|
||||
{
|
||||
const Id converted{ctx.OpConvertUToF(ctx.F32[4], color)};
|
||||
const Id inv255{ctx.Const(1.0f / 255.0f)};
|
||||
const Id scale{ctx.ConstantComposite(ctx.F32[4], inv255, inv255, inv255, inv255)};
|
||||
return ctx.OpFMul(ctx.F32[4], converted, scale);
|
||||
}
|
||||
case SamplerComponentType::Uint:
|
||||
return ctx.OpConvertUToF(ctx.F32[4], color);
|
||||
}
|
||||
throw InvalidArgument("Invalid sampler component type {}", def.component_type);
|
||||
}
|
||||
|
||||
Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& index) {
|
||||
if (!index.IsImmediate() || index.U32() != 0) {
|
||||
throw NotImplementedException("Indirect image indexing");
|
||||
@@ -487,39 +449,31 @@ Id EmitBoundImageWrite(EmitContext&) {
|
||||
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id bias_lc, const IR::Value& offset) {
|
||||
const auto info{inst->Flags<IR::TextureInstInfo>()};
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
const Id color_type{TextureColorResultType(ctx, def)};
|
||||
const Id texture{Texture(ctx, info, index)};
|
||||
Id color{};
|
||||
if (ctx.stage == Stage::Fragment) {
|
||||
const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0,
|
||||
bias_lc, offset);
|
||||
color = Emit(&EmitContext::OpImageSparseSampleImplicitLod,
|
||||
&EmitContext::OpImageSampleImplicitLod, ctx, inst, color_type, texture,
|
||||
coords, operands.MaskOptional(), operands.Span());
|
||||
return Emit(&EmitContext::OpImageSparseSampleImplicitLod,
|
||||
&EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4],
|
||||
Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
|
||||
} else {
|
||||
// We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as
|
||||
// if the lod was explicitly zero. This may change on Turing with implicit compute
|
||||
// derivatives
|
||||
const Id lod{ctx.Const(0.0f)};
|
||||
const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset);
|
||||
color = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
|
||||
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type, texture,
|
||||
coords, operands.Mask(), operands.Span());
|
||||
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
|
||||
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
|
||||
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
|
||||
}
|
||||
return TextureSampleResultToFloat(ctx, def, color);
|
||||
}
|
||||
|
||||
Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id lod, const IR::Value& offset) {
|
||||
const auto info{inst->Flags<IR::TextureInstInfo>()};
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
const Id color_type{TextureColorResultType(ctx, def)};
|
||||
const ImageOperands operands(ctx, false, true, false, lod, offset);
|
||||
const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod,
|
||||
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type,
|
||||
Texture(ctx, info, index), coords, operands.Mask(), operands.Span())};
|
||||
return TextureSampleResultToFloat(ctx, def, color);
|
||||
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
|
||||
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
|
||||
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
|
||||
}
|
||||
|
||||
Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
|
||||
@@ -555,18 +509,13 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va
|
||||
Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
const IR::Value& offset, const IR::Value& offset2) {
|
||||
const auto info{inst->Flags<IR::TextureInstInfo>()};
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
const Id color_type{TextureColorResultType(ctx, def)};
|
||||
const ImageOperands operands(ctx, offset, offset2);
|
||||
const Id texture{Texture(ctx, info, index)};
|
||||
if (ctx.profile.need_gather_subpixel_offset) {
|
||||
coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords);
|
||||
}
|
||||
const Id color{
|
||||
Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, color_type,
|
||||
texture, coords, ctx.Const(info.gather_component), operands.MaskOptional(),
|
||||
operands.Span())};
|
||||
return TextureSampleResultToFloat(ctx, def, color);
|
||||
return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst,
|
||||
ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component),
|
||||
operands.MaskOptional(), operands.Span());
|
||||
}
|
||||
|
||||
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
@@ -584,9 +533,6 @@ Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
|
||||
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
|
||||
Id lod, Id ms) {
|
||||
const auto info{inst->Flags<IR::TextureInstInfo>()};
|
||||
const TextureDefinition* def =
|
||||
info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index);
|
||||
const Id result_type{def ? TextureColorResultType(ctx, *def) : ctx.F32[4]};
|
||||
AddOffsetToCoordinates(ctx, info, coords, offset);
|
||||
if (info.type == TextureType::Buffer) {
|
||||
lod = Id{};
|
||||
@@ -596,13 +542,8 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
|
||||
lod = Id{};
|
||||
}
|
||||
const ImageOperands operands(lod, ms);
|
||||
Id color{Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst,
|
||||
result_type, TextureImage(ctx, info, index), coords, operands.MaskOptional(),
|
||||
operands.Span())};
|
||||
if (def) {
|
||||
color = TextureSampleResultToFloat(ctx, *def, color);
|
||||
}
|
||||
return color;
|
||||
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
|
||||
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
|
||||
}
|
||||
|
||||
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
|
||||
@@ -647,17 +588,14 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
|
||||
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id derivatives, const IR::Value& offset, Id lod_clamp) {
|
||||
const auto info{inst->Flags<IR::TextureInstInfo>()};
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
const Id color_type{TextureColorResultType(ctx, def)};
|
||||
const auto operands = info.num_derivatives == 3
|
||||
? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
|
||||
ctx.Def(offset), {}, lod_clamp)
|
||||
: ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
|
||||
info.num_derivatives, offset, lod_clamp);
|
||||
const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod,
|
||||
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type,
|
||||
Texture(ctx, info, index), coords, operands.Mask(), operands.Span())};
|
||||
return TextureSampleResultToFloat(ctx, def, color);
|
||||
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
|
||||
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
|
||||
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
|
||||
}
|
||||
|
||||
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -81,25 +78,9 @@ Id AddPartitionBase(EmitContext& ctx, Id thread_id) {
|
||||
const Id partition_base{ctx.OpShiftLeftLogical(ctx.U32[1], partition_idx, ctx.Const(5u))};
|
||||
return ctx.OpIAdd(ctx.U32[1], thread_id, partition_base);
|
||||
}
|
||||
|
||||
bool SupportsWarpIntrinsics(const EmitContext& ctx) {
|
||||
return ctx.profile.SupportsWarpIntrinsics(ctx.stage);
|
||||
}
|
||||
|
||||
void SetAlwaysInBounds(EmitContext& ctx, IR::Inst* inst) {
|
||||
SetInBoundsFlag(inst, ctx.true_value);
|
||||
}
|
||||
|
||||
Id FallbackBallotMask(EmitContext& ctx, Id pred) {
|
||||
const Id full_mask{ctx.Const(0xFFFFFFFFu)};
|
||||
return ctx.OpSelect(ctx.U32[1], pred, full_mask, ctx.u32_zero_value);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
Id EmitLaneId(EmitContext& ctx) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return ctx.u32_zero_value;
|
||||
}
|
||||
const Id id{GetThreadId(ctx)};
|
||||
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
|
||||
return id;
|
||||
@@ -108,9 +89,6 @@ Id EmitLaneId(EmitContext& ctx) {
|
||||
}
|
||||
|
||||
Id EmitVoteAll(EmitContext& ctx, Id pred) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return pred;
|
||||
}
|
||||
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
|
||||
return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred);
|
||||
}
|
||||
@@ -124,9 +102,6 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) {
|
||||
}
|
||||
|
||||
Id EmitVoteAny(EmitContext& ctx, Id pred) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return pred;
|
||||
}
|
||||
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
|
||||
return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred);
|
||||
}
|
||||
@@ -140,9 +115,6 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) {
|
||||
}
|
||||
|
||||
Id EmitVoteEqual(EmitContext& ctx, Id pred) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return pred;
|
||||
}
|
||||
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
|
||||
return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred);
|
||||
}
|
||||
@@ -157,9 +129,6 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) {
|
||||
}
|
||||
|
||||
Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return FallbackBallotMask(ctx, pred);
|
||||
}
|
||||
const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)};
|
||||
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
|
||||
return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U);
|
||||
@@ -168,46 +137,27 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
|
||||
}
|
||||
|
||||
Id EmitSubgroupEqMask(EmitContext& ctx) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return ctx.u32_zero_value;
|
||||
}
|
||||
return LoadMask(ctx, ctx.subgroup_mask_eq);
|
||||
}
|
||||
|
||||
Id EmitSubgroupLtMask(EmitContext& ctx) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return ctx.u32_zero_value;
|
||||
}
|
||||
return LoadMask(ctx, ctx.subgroup_mask_lt);
|
||||
}
|
||||
|
||||
Id EmitSubgroupLeMask(EmitContext& ctx) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return ctx.u32_zero_value;
|
||||
}
|
||||
return LoadMask(ctx, ctx.subgroup_mask_le);
|
||||
}
|
||||
|
||||
Id EmitSubgroupGtMask(EmitContext& ctx) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return ctx.u32_zero_value;
|
||||
}
|
||||
return LoadMask(ctx, ctx.subgroup_mask_gt);
|
||||
}
|
||||
|
||||
Id EmitSubgroupGeMask(EmitContext& ctx) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
return ctx.u32_zero_value;
|
||||
}
|
||||
return LoadMask(ctx, ctx.subgroup_mask_ge);
|
||||
}
|
||||
|
||||
Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||
Id segmentation_mask) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
SetAlwaysInBounds(ctx, inst);
|
||||
return value;
|
||||
}
|
||||
const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)};
|
||||
const Id thread_id{EmitLaneId(ctx)};
|
||||
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
|
||||
@@ -227,10 +177,6 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla
|
||||
|
||||
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||
Id segmentation_mask) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
SetAlwaysInBounds(ctx, inst);
|
||||
return value;
|
||||
}
|
||||
const Id thread_id{EmitLaneId(ctx)};
|
||||
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
||||
Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)};
|
||||
@@ -246,10 +192,6 @@ Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||
|
||||
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||
Id segmentation_mask) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
SetAlwaysInBounds(ctx, inst);
|
||||
return value;
|
||||
}
|
||||
const Id thread_id{EmitLaneId(ctx)};
|
||||
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
||||
Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)};
|
||||
@@ -265,10 +207,6 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
|
||||
|
||||
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
|
||||
Id segmentation_mask) {
|
||||
if (!SupportsWarpIntrinsics(ctx)) {
|
||||
SetAlwaysInBounds(ctx, inst);
|
||||
return value;
|
||||
}
|
||||
const Id thread_id{EmitLaneId(ctx)};
|
||||
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
|
||||
Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)};
|
||||
|
||||
@@ -28,41 +28,27 @@ enum class Operation {
|
||||
FPMax,
|
||||
};
|
||||
|
||||
Id ComponentScalarType(EmitContext& ctx, SamplerComponentType component_type) {
|
||||
switch (component_type) {
|
||||
case SamplerComponentType::Float:
|
||||
case SamplerComponentType::Depth:
|
||||
return ctx.F32[1];
|
||||
case SamplerComponentType::Sint:
|
||||
return ctx.S32[1];
|
||||
case SamplerComponentType::Stencil:
|
||||
return ctx.U32[1];
|
||||
case SamplerComponentType::Uint:
|
||||
return ctx.U32[1];
|
||||
}
|
||||
throw InvalidArgument("Invalid sampler component type {}", component_type);
|
||||
}
|
||||
|
||||
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc, Id sampled_type) {
|
||||
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
|
||||
const spv::ImageFormat format{spv::ImageFormat::Unknown};
|
||||
const Id type{ctx.F32[1]};
|
||||
const bool depth{desc.is_depth};
|
||||
const bool ms{desc.is_multisample};
|
||||
switch (desc.type) {
|
||||
case TextureType::Color1D:
|
||||
return ctx.TypeImage(sampled_type, spv::Dim::Dim1D, depth, false, false, 1, format);
|
||||
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, false, false, 1, format);
|
||||
case TextureType::ColorArray1D:
|
||||
return ctx.TypeImage(sampled_type, spv::Dim::Dim1D, depth, true, false, 1, format);
|
||||
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format);
|
||||
case TextureType::Color2D:
|
||||
case TextureType::Color2DRect:
|
||||
return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, depth, false, ms, 1, format);
|
||||
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, ms, 1, format);
|
||||
case TextureType::ColorArray2D:
|
||||
return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, depth, true, ms, 1, format);
|
||||
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, ms, 1, format);
|
||||
case TextureType::Color3D:
|
||||
return ctx.TypeImage(sampled_type, spv::Dim::Dim3D, depth, false, false, 1, format);
|
||||
return ctx.TypeImage(type, spv::Dim::Dim3D, depth, false, false, 1, format);
|
||||
case TextureType::ColorCube:
|
||||
return ctx.TypeImage(sampled_type, spv::Dim::Cube, depth, false, false, 1, format);
|
||||
return ctx.TypeImage(type, spv::Dim::Cube, depth, false, false, 1, format);
|
||||
case TextureType::ColorArrayCube:
|
||||
return ctx.TypeImage(sampled_type, spv::Dim::Cube, depth, true, false, 1, format);
|
||||
return ctx.TypeImage(type, spv::Dim::Cube, depth, true, false, 1, format);
|
||||
case TextureType::Buffer:
|
||||
break;
|
||||
}
|
||||
@@ -329,9 +315,6 @@ void DefineSsbos(EmitContext& ctx, StorageTypeDefinition& type_def,
|
||||
ctx.Decorate(id, spv::Decoration::Binding, binding);
|
||||
ctx.Decorate(id, spv::Decoration::DescriptorSet, 0U);
|
||||
ctx.Name(id, fmt::format("ssbo{}", index));
|
||||
if (!desc.is_written) {
|
||||
ctx.Decorate(id, spv::Decoration::NonWritable);
|
||||
}
|
||||
if (ctx.profile.supported_spirv >= 0x00010400) {
|
||||
ctx.interfaces.push_back(id);
|
||||
}
|
||||
@@ -563,7 +546,6 @@ void EmitContext::DefineCommonTypes(const Info& info) {
|
||||
|
||||
output_f32 = Name(TypePointer(spv::StorageClass::Output, F32[1]), "output_f32");
|
||||
output_u32 = Name(TypePointer(spv::StorageClass::Output, U32[1]), "output_u32");
|
||||
output_s32 = Name(TypePointer(spv::StorageClass::Output, S32[1]), "output_s32");
|
||||
|
||||
if (info.uses_int8 && profile.support_int8) {
|
||||
AddCapability(spv::Capability::Int8);
|
||||
@@ -1377,8 +1359,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
|
||||
void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_index) {
|
||||
textures.reserve(info.texture_descriptors.size());
|
||||
for (const TextureDescriptor& desc : info.texture_descriptors) {
|
||||
const Id result_type{ComponentScalarType(*this, desc.component_type)};
|
||||
const Id image_type{ImageType(*this, desc, result_type)};
|
||||
const Id image_type{ImageType(*this, desc)};
|
||||
const Id sampled_type{TypeSampledImage(image_type)};
|
||||
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, sampled_type)};
|
||||
const Id desc_type{DescType(*this, sampled_type, pointer_type, desc.count)};
|
||||
@@ -1391,10 +1372,8 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in
|
||||
.sampled_type = sampled_type,
|
||||
.pointer_type = pointer_type,
|
||||
.image_type = image_type,
|
||||
.result_type = result_type,
|
||||
.count = desc.count,
|
||||
.is_multisample = desc.is_multisample,
|
||||
.component_type = desc.component_type,
|
||||
});
|
||||
if (profile.supported_spirv >= 0x00010400) {
|
||||
interfaces.push_back(id);
|
||||
@@ -1437,7 +1416,6 @@ void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_inde
|
||||
void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
const Info& info{program.info};
|
||||
const VaryingState loads{info.loads.mask | info.passthrough.mask};
|
||||
const bool stage_supports_warp = profile.SupportsWarpIntrinsics(stage);
|
||||
|
||||
if (info.uses_workgroup_id) {
|
||||
workgroup_id = DefineInput(*this, U32[3], false, spv::BuiltIn::WorkgroupId);
|
||||
@@ -1454,37 +1432,24 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
}
|
||||
if (info.uses_sample_id) {
|
||||
sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(sample_id, spv::Decoration::Flat);
|
||||
}
|
||||
}
|
||||
if (info.uses_is_helper_invocation) {
|
||||
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
|
||||
}
|
||||
if (info.uses_subgroup_mask && stage_supports_warp) {
|
||||
if (info.uses_subgroup_mask) {
|
||||
subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR);
|
||||
subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR);
|
||||
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
|
||||
subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR);
|
||||
subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(subgroup_mask_eq, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_lt, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_le, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_gt, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_ge, spv::Decoration::Flat);
|
||||
}
|
||||
}
|
||||
if (stage_supports_warp &&
|
||||
(info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
|
||||
(profile.warp_size_potentially_larger_than_guest &&
|
||||
(info.uses_subgroup_vote || info.uses_subgroup_mask)))) {
|
||||
if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
|
||||
(profile.warp_size_potentially_larger_than_guest &&
|
||||
(info.uses_subgroup_vote || info.uses_subgroup_mask))) {
|
||||
AddCapability(spv::Capability::GroupNonUniform);
|
||||
subgroup_local_invocation_id =
|
||||
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(subgroup_local_invocation_id, spv::Decoration::Flat);
|
||||
}
|
||||
Decorate(subgroup_local_invocation_id, spv::Decoration::Flat);
|
||||
}
|
||||
if (info.uses_fswzadd) {
|
||||
const Id f32_one{Const(1.0f)};
|
||||
@@ -1496,9 +1461,6 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
}
|
||||
if (loads[IR::Attribute::PrimitiveId]) {
|
||||
primitive_id = DefineInput(*this, U32[1], false, spv::BuiltIn::PrimitiveId);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(primitive_id, spv::Decoration::Flat);
|
||||
}
|
||||
}
|
||||
if (loads[IR::Attribute::Layer]) {
|
||||
AddCapability(spv::Capability::Geometry);
|
||||
@@ -1590,21 +1552,17 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
if (stage != Stage::Fragment) {
|
||||
continue;
|
||||
}
|
||||
const bool is_integer = input_type == AttributeType::SignedInt ||
|
||||
input_type == AttributeType::UnsignedInt;
|
||||
if (is_integer) {
|
||||
switch (info.interpolation[index]) {
|
||||
case Interpolation::Smooth:
|
||||
// Default
|
||||
// Decorate(id, spv::Decoration::Smooth);
|
||||
break;
|
||||
case Interpolation::NoPerspective:
|
||||
Decorate(id, spv::Decoration::NoPerspective);
|
||||
break;
|
||||
case Interpolation::Flat:
|
||||
Decorate(id, spv::Decoration::Flat);
|
||||
} else {
|
||||
switch (info.interpolation[index]) {
|
||||
case Interpolation::Smooth:
|
||||
break;
|
||||
case Interpolation::NoPerspective:
|
||||
Decorate(id, spv::Decoration::NoPerspective);
|
||||
break;
|
||||
case Interpolation::Flat:
|
||||
Decorate(id, spv::Decoration::Flat);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stage == Stage::TessellationEval) {
|
||||
@@ -1700,18 +1658,7 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
|
||||
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
|
||||
continue;
|
||||
}
|
||||
const AttributeType output_type{runtime_info.color_output_types[index]};
|
||||
const Id vec_type = [&, output_type]() -> Id {
|
||||
switch (output_type) {
|
||||
case AttributeType::SignedInt:
|
||||
return S32[4];
|
||||
case AttributeType::UnsignedInt:
|
||||
return U32[4];
|
||||
default:
|
||||
return F32[4];
|
||||
}
|
||||
}();
|
||||
frag_color[index] = DefineOutput(*this, vec_type, std::nullopt);
|
||||
frag_color[index] = DefineOutput(*this, F32[4], std::nullopt);
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -39,10 +36,8 @@ struct TextureDefinition {
|
||||
Id sampled_type;
|
||||
Id pointer_type;
|
||||
Id image_type;
|
||||
Id result_type;
|
||||
u32 count;
|
||||
bool is_multisample;
|
||||
SamplerComponentType component_type;
|
||||
};
|
||||
|
||||
struct TextureBufferDefinition {
|
||||
@@ -249,7 +244,6 @@ public:
|
||||
|
||||
Id output_f32{};
|
||||
Id output_u32{};
|
||||
Id output_s32{};
|
||||
|
||||
Id image_buffer_type{};
|
||||
Id image_u32{};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -25,8 +22,6 @@ public:
|
||||
|
||||
[[nodiscard]] virtual TextureType ReadTextureType(u32 raw_handle) = 0;
|
||||
|
||||
[[nodiscard]] virtual SamplerComponentType ReadTextureComponentType(u32 raw_handle) = 0;
|
||||
|
||||
[[nodiscard]] virtual TexturePixelFormat ReadTexturePixelFormat(u32 raw_handle) = 0;
|
||||
|
||||
[[nodiscard]] virtual bool IsTexturePixelFormatInteger(u32 raw_handle) = 0;
|
||||
|
||||
@@ -396,10 +396,6 @@ bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf)
|
||||
return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf));
|
||||
}
|
||||
|
||||
SamplerComponentType ReadTextureComponentType(Environment& env, const ConstBufferAddr& cbuf) {
|
||||
return env.ReadTextureComponentType(GetTextureHandle(env, cbuf));
|
||||
}
|
||||
|
||||
class Descriptors {
|
||||
public:
|
||||
explicit Descriptors(TextureBufferDescriptors& texture_buffer_descriptors_,
|
||||
@@ -437,9 +433,7 @@ public:
|
||||
|
||||
u32 Add(const TextureDescriptor& desc) {
|
||||
const u32 index{Add(texture_descriptors, desc, [&desc](const auto& existing) {
|
||||
return desc.type == existing.type &&
|
||||
desc.component_type == existing.component_type &&
|
||||
desc.is_depth == existing.is_depth &&
|
||||
return desc.type == existing.type && desc.is_depth == existing.is_depth &&
|
||||
desc.has_secondary == existing.has_secondary &&
|
||||
desc.cbuf_index == existing.cbuf_index &&
|
||||
desc.cbuf_offset == existing.cbuf_offset &&
|
||||
@@ -672,12 +666,10 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
||||
.secondary_shift_left = cbuf.secondary_shift_left,
|
||||
.count = cbuf.count,
|
||||
.size_shift = DESCRIPTOR_SIZE_SHIFT,
|
||||
.component_type = ReadTextureComponentType(env, cbuf),
|
||||
});
|
||||
} else {
|
||||
index = descriptors.Add(TextureDescriptor{
|
||||
.type = flags.type,
|
||||
.component_type = ReadTextureComponentType(env, cbuf),
|
||||
.is_depth = flags.is_depth != 0,
|
||||
.is_multisample = is_multisample,
|
||||
.has_secondary = cbuf.has_secondary,
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/stage.h"
|
||||
|
||||
namespace Shader {
|
||||
|
||||
@@ -52,8 +46,6 @@ struct Profile {
|
||||
bool support_multi_viewport{};
|
||||
bool support_geometry_streams{};
|
||||
|
||||
u32 warp_stage_support_mask{std::numeric_limits<u32>::max()};
|
||||
|
||||
bool warp_size_potentially_larger_than_guest{};
|
||||
|
||||
bool lower_left_origin_mode{};
|
||||
@@ -98,11 +90,6 @@ struct Profile {
|
||||
u64 min_ssbo_alignment{};
|
||||
|
||||
u32 max_user_clip_distances{};
|
||||
|
||||
bool SupportsWarpIntrinsics(Stage stage) const {
|
||||
const u32 bit = 1u << static_cast<u32>(stage);
|
||||
return (warp_stage_support_mask & bit) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -83,7 +80,6 @@ struct TransformFeedbackVarying {
|
||||
|
||||
struct RuntimeInfo {
|
||||
std::array<AttributeType, 32> generic_input_types{};
|
||||
std::array<AttributeType, 8> color_output_types{};
|
||||
VaryingState previous_stage_stores;
|
||||
std::map<IR::Attribute, IR::Attribute> previous_stage_legacy_stores_mapping;
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -154,14 +151,6 @@ enum class ImageFormat : u32 {
|
||||
R32G32B32A32_UINT,
|
||||
};
|
||||
|
||||
enum class SamplerComponentType : u8 {
|
||||
Float,
|
||||
Sint,
|
||||
Uint,
|
||||
Depth,
|
||||
Stencil,
|
||||
};
|
||||
|
||||
enum class Interpolation {
|
||||
Smooth,
|
||||
Flat,
|
||||
@@ -194,7 +183,6 @@ struct TextureBufferDescriptor {
|
||||
u32 secondary_shift_left;
|
||||
u32 count;
|
||||
u32 size_shift;
|
||||
SamplerComponentType component_type;
|
||||
|
||||
auto operator<=>(const TextureBufferDescriptor&) const = default;
|
||||
};
|
||||
@@ -216,7 +204,6 @@ using ImageBufferDescriptors = boost::container::small_vector<ImageBufferDescrip
|
||||
|
||||
struct TextureDescriptor {
|
||||
TextureType type;
|
||||
SamplerComponentType component_type;
|
||||
bool is_depth;
|
||||
bool is_multisample;
|
||||
bool has_secondary;
|
||||
|
||||
@@ -407,12 +407,6 @@ void BufferCache<P>::SetComputeUniformBufferState(u32 mask,
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
|
||||
if constexpr (requires { runtime.ShouldLimitDynamicStorageBuffers(); }) {
|
||||
if (runtime.ShouldLimitDynamicStorageBuffers()) {
|
||||
channel_state->total_graphics_storage_buffers -=
|
||||
static_cast<u32>(std::popcount(channel_state->enabled_storage_buffers[stage]));
|
||||
}
|
||||
}
|
||||
channel_state->enabled_storage_buffers[stage] = 0;
|
||||
channel_state->written_storage_buffers[stage] = 0;
|
||||
}
|
||||
@@ -420,26 +414,8 @@ void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
|
||||
template <class P>
|
||||
bool BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
|
||||
u32 cbuf_offset, bool is_written) {
|
||||
const bool already_enabled =
|
||||
((channel_state->enabled_storage_buffers[stage] >> ssbo_index) & 1U) != 0;
|
||||
if constexpr (requires { runtime.ShouldLimitDynamicStorageBuffers(); }) {
|
||||
if (runtime.ShouldLimitDynamicStorageBuffers() && !already_enabled) {
|
||||
const u32 max_bindings = runtime.GetMaxDynamicStorageBuffers();
|
||||
if (channel_state->total_graphics_storage_buffers >= max_bindings) {
|
||||
LOG_WARNING(HW_GPU,
|
||||
"Skipping graphics storage buffer {} due to driver limit {}",
|
||||
ssbo_index, max_bindings);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
channel_state->enabled_storage_buffers[stage] |= 1U << ssbo_index;
|
||||
channel_state->written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
|
||||
if constexpr (requires { runtime.ShouldLimitDynamicStorageBuffers(); }) {
|
||||
if (runtime.ShouldLimitDynamicStorageBuffers() && !already_enabled) {
|
||||
++channel_state->total_graphics_storage_buffers;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& cbufs = maxwell3d->state.shader_stages[stage];
|
||||
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
|
||||
@@ -470,12 +446,6 @@ void BufferCache<P>::BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, G
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::UnbindComputeStorageBuffers() {
|
||||
if constexpr (requires { runtime.ShouldLimitDynamicStorageBuffers(); }) {
|
||||
if (runtime.ShouldLimitDynamicStorageBuffers()) {
|
||||
channel_state->total_compute_storage_buffers -=
|
||||
static_cast<u32>(std::popcount(channel_state->enabled_compute_storage_buffers));
|
||||
}
|
||||
}
|
||||
channel_state->enabled_compute_storage_buffers = 0;
|
||||
channel_state->written_compute_storage_buffers = 0;
|
||||
channel_state->image_compute_texture_buffers = 0;
|
||||
@@ -489,26 +459,8 @@ void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index,
|
||||
ssbo_index);
|
||||
return;
|
||||
}
|
||||
const bool already_enabled =
|
||||
((channel_state->enabled_compute_storage_buffers >> ssbo_index) & 1U) != 0;
|
||||
if constexpr (requires { runtime.ShouldLimitDynamicStorageBuffers(); }) {
|
||||
if (runtime.ShouldLimitDynamicStorageBuffers() && !already_enabled) {
|
||||
const u32 max_bindings = runtime.GetMaxDynamicStorageBuffers();
|
||||
if (channel_state->total_compute_storage_buffers >= max_bindings) {
|
||||
LOG_WARNING(HW_GPU,
|
||||
"Skipping compute storage buffer {} due to driver limit {}",
|
||||
ssbo_index, max_bindings);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
channel_state->enabled_compute_storage_buffers |= 1U << ssbo_index;
|
||||
channel_state->written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
|
||||
if constexpr (requires { runtime.ShouldLimitDynamicStorageBuffers(); }) {
|
||||
if (runtime.ShouldLimitDynamicStorageBuffers() && !already_enabled) {
|
||||
++channel_state->total_compute_storage_buffers;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& launch_desc = kepler_compute->launch_description;
|
||||
if (((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) == 0) {
|
||||
@@ -841,23 +793,9 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
||||
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
const bool has_host_buffer = binding.buffer_id != NULL_BUFFER_ID;
|
||||
const u32 offset = has_host_buffer ? buffer.Offset(device_addr) : 0;
|
||||
const bool needs_alignment_stream = [&]() {
|
||||
if constexpr (IS_OPENGL) {
|
||||
return false;
|
||||
} else {
|
||||
if (!has_host_buffer) {
|
||||
return false;
|
||||
}
|
||||
const u32 alignment = runtime.GetUniformBufferAlignment();
|
||||
return alignment > 1 && (offset % alignment) != 0;
|
||||
}
|
||||
}();
|
||||
const bool use_fast_buffer = needs_alignment_stream ||
|
||||
(has_host_buffer &&
|
||||
size <= channel_state->uniform_buffer_skip_cache_size &&
|
||||
!memory_tracker.IsRegionGpuModified(device_addr, size));
|
||||
const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
|
||||
size <= channel_state->uniform_buffer_skip_cache_size &&
|
||||
!memory_tracker.IsRegionGpuModified(device_addr, size);
|
||||
if (use_fast_buffer) {
|
||||
if constexpr (IS_OPENGL) {
|
||||
if (runtime.HasFastBufferSubData()) {
|
||||
@@ -896,6 +834,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
||||
if (!needs_bind) {
|
||||
return;
|
||||
}
|
||||
const u32 offset = buffer.Offset(device_addr);
|
||||
if constexpr (IS_OPENGL) {
|
||||
// Mark the index as dirty if offset doesn't match
|
||||
const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset();
|
||||
@@ -1012,30 +951,9 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
const u32 size =
|
||||
(std::min)(binding.size, (*channel_state->compute_uniform_buffer_sizes)[index]);
|
||||
const bool has_host_buffer = binding.buffer_id != NULL_BUFFER_ID;
|
||||
const u32 offset = has_host_buffer ? buffer.Offset(binding.device_addr) : 0;
|
||||
const bool needs_alignment_stream = [&]() {
|
||||
if constexpr (IS_OPENGL) {
|
||||
return false;
|
||||
} else {
|
||||
if (!has_host_buffer) {
|
||||
return false;
|
||||
}
|
||||
const u32 alignment = runtime.GetUniformBufferAlignment();
|
||||
return alignment > 1 && (offset % alignment) != 0;
|
||||
}
|
||||
}();
|
||||
if constexpr (!IS_OPENGL) {
|
||||
if (needs_alignment_stream) {
|
||||
const std::span<u8> span =
|
||||
runtime.BindMappedUniformBuffer(0, binding_index, size);
|
||||
device_memory.ReadBlockUnsafe(binding.device_addr, span.data(), size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SynchronizeBuffer(buffer, binding.device_addr, size);
|
||||
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, size);
|
||||
if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
|
||||
runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size);
|
||||
@@ -1787,21 +1705,26 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
|
||||
return NULL_BINDING;
|
||||
}
|
||||
|
||||
// xbzk: New size logic. Fixes MCI.
|
||||
// If ever the * comment below prove wrong, the 'if' block may be removed.
|
||||
const auto size = [&]() {
|
||||
const bool is_nvn_cbuf = cbuf_index == 0;
|
||||
// The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size.
|
||||
if (is_nvn_cbuf) {
|
||||
const u32 ssbo_size = gpu_memory->Read<u32>(ssbo_addr + 8);
|
||||
if (ssbo_size != 0) {
|
||||
return ssbo_size;
|
||||
// * The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size.
|
||||
const u64 next_qword = gpu_memory->Read<u64>(ssbo_addr + 8);
|
||||
const u32 upper_32 = static_cast<u32>(next_qword >> 32);
|
||||
// Hardware-based detection: GPU addresses have non-zero upper bits
|
||||
if (upper_32 == 0) {
|
||||
// This is a size field, not a GPU address
|
||||
return static_cast<u32>(next_qword); // Return lower_32
|
||||
}
|
||||
}
|
||||
// Other titles (notably Doom Eternal) may use STG/LDG on buffer addresses in custom defined
|
||||
// cbufs, which do not store the sizes adjacent to the addresses, so use the fully
|
||||
// mapped buffer size for now.
|
||||
// Fall through: either not NVN cbuf (Doom Eternal & +), or NVN but ssbo_addr+8 is a GPU address (MCI)
|
||||
const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
|
||||
// Cap at 8MB to prevent allocator overflow from misinterpreted addresses
|
||||
return (std::min)(memory_layout_size, static_cast<u32>(8_MiB));
|
||||
}();
|
||||
|
||||
// Alignment only applies to the offset of the buffer
|
||||
const u32 alignment = runtime.GetStorageBufferAlignment();
|
||||
const GPUVAddr aligned_gpu_addr = Common::AlignDown(gpu_addr, alignment);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -133,9 +132,6 @@ public:
|
||||
u32 enabled_compute_storage_buffers = 0;
|
||||
u32 written_compute_storage_buffers = 0;
|
||||
|
||||
u32 total_graphics_storage_buffers = 0;
|
||||
u32 total_compute_storage_buffers = 0;
|
||||
|
||||
std::array<u32, NUM_STAGES> enabled_texture_buffers{};
|
||||
std::array<u32, NUM_STAGES> written_texture_buffers{};
|
||||
std::array<u32, NUM_STAGES> image_texture_buffers{};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -238,9 +235,6 @@ constexpr Table MakeViewTable() {
|
||||
EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
|
||||
EnableRange(view, VIEW_CLASS_ASTC_12x10_RGBA);
|
||||
EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
|
||||
Enable(view, PixelFormat::D24_UNORM_S8_UINT, PixelFormat::S8_UINT);
|
||||
Enable(view, PixelFormat::S8_UINT_D24_UNORM, PixelFormat::S8_UINT);
|
||||
Enable(view, PixelFormat::D32_FLOAT_S8_UINT, PixelFormat::S8_UINT);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -17,7 +20,7 @@ void Scheduler::Push(s32 channel, CommandList&& entries) {
|
||||
std::unique_lock lk(scheduling_guard);
|
||||
auto it = channels.find(channel);
|
||||
ASSERT(it != channels.end());
|
||||
auto channel_state = it->second;
|
||||
auto& channel_state = it->second;
|
||||
gpu.BindChannel(channel_state->bind_id);
|
||||
channel_state->dma_pusher->Push(std::move(entries));
|
||||
channel_state->dma_pusher->DispatchCalls();
|
||||
|
||||
@@ -43,66 +43,90 @@ void DmaPusher::DispatchCalls() {
|
||||
|
||||
bool DmaPusher::Step() {
|
||||
if (!ib_enable || dma_pushbuffer.empty()) {
|
||||
// pushbuffer empty and IB empty or nonexistent - nothing to do
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandList& command_list = dma_pushbuffer.front();
|
||||
CommandList& command_list{dma_pushbuffer.front()};
|
||||
|
||||
const size_t prefetch_size = command_list.prefetch_command_list.size();
|
||||
const size_t command_list_size = command_list.command_lists.size();
|
||||
ASSERT_OR_EXECUTE(
|
||||
command_list.command_lists.size() || command_list.prefetch_command_list.size(), {
|
||||
// Somehow the command_list is empty, in order to avoid a crash
|
||||
// We ignore it and assume its size is 0.
|
||||
dma_pushbuffer.pop();
|
||||
dma_pushbuffer_subindex = 0;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (prefetch_size == 0 && command_list_size == 0) {
|
||||
if (command_list.prefetch_command_list.size()) {
|
||||
// Prefetched command list from nvdrv, used for things like synchronization
|
||||
ProcessCommands(VideoCommon::FixSmallVectorADL(command_list.prefetch_command_list));
|
||||
dma_pushbuffer.pop();
|
||||
dma_pushbuffer_subindex = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prefetch_size > 0) {
|
||||
ProcessCommands(command_list.prefetch_command_list);
|
||||
dma_pushbuffer.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto& current_command = command_list.command_lists[dma_pushbuffer_subindex];
|
||||
const CommandListHeader& header = current_command;
|
||||
dma_state.dma_get = header.addr;
|
||||
|
||||
if (signal_sync && !synced) {
|
||||
std::unique_lock lk(sync_mutex);
|
||||
sync_cv.wait(lk, [this]() { return synced; });
|
||||
signal_sync = false;
|
||||
synced = false;
|
||||
}
|
||||
|
||||
if (header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
|
||||
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
|
||||
}
|
||||
|
||||
if (header.size > 0) {
|
||||
if (Settings::IsDMALevelDefault() ? (Settings::IsGPULevelMedium() || Settings::IsGPULevelHigh()) : Settings::IsDMALevelSafe()) {
|
||||
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
|
||||
ProcessCommands(headers);
|
||||
} else {
|
||||
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::UnsafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
|
||||
ProcessCommands(headers);
|
||||
}
|
||||
}
|
||||
|
||||
if (++dma_pushbuffer_subindex >= command_list_size) {
|
||||
dma_pushbuffer.pop();
|
||||
dma_pushbuffer_subindex = 0;
|
||||
} else {
|
||||
signal_sync = command_list.command_lists[dma_pushbuffer_subindex].sync && Settings::values.sync_memory_operations.GetValue();
|
||||
}
|
||||
const CommandListHeader command_list_header{
|
||||
command_list.command_lists[dma_pushbuffer_subindex++]};
|
||||
|
||||
if (signal_sync) {
|
||||
rasterizer->SignalFence([this]() {
|
||||
if (signal_sync) {
|
||||
std::unique_lock lk(sync_mutex);
|
||||
sync_cv.wait(lk, [this]() { return synced; });
|
||||
signal_sync = false;
|
||||
synced = false;
|
||||
}
|
||||
|
||||
dma_state.dma_get = command_list_header.addr;
|
||||
|
||||
if (command_list_header.size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Push buffer non-empty, read a word
|
||||
if (dma_state.method >= MacroRegistersStart) {
|
||||
if (subchannels[dma_state.subchannel]) {
|
||||
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(
|
||||
dma_state.dma_get, command_list_header.size * sizeof(u32));
|
||||
}
|
||||
}
|
||||
|
||||
const auto safe_process = [&] {
|
||||
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader,
|
||||
Tegra::Memory::GuestMemoryFlags::SafeRead>
|
||||
headers(memory_manager, dma_state.dma_get, command_list_header.size,
|
||||
&command_headers);
|
||||
ProcessCommands(headers);
|
||||
};
|
||||
|
||||
const auto unsafe_process = [&] {
|
||||
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader,
|
||||
Tegra::Memory::GuestMemoryFlags::UnsafeRead>
|
||||
headers(memory_manager, dma_state.dma_get, command_list_header.size,
|
||||
&command_headers);
|
||||
ProcessCommands(headers);
|
||||
};
|
||||
|
||||
const bool use_safe = Settings::IsDMALevelDefault() ? (Settings::IsGPULevelMedium() || Settings::IsGPULevelHigh()) : Settings::IsDMALevelSafe();
|
||||
|
||||
if (use_safe) {
|
||||
safe_process();
|
||||
} else {
|
||||
unsafe_process();
|
||||
}
|
||||
|
||||
if (dma_pushbuffer_subindex >= command_list.command_lists.size()) {
|
||||
// We've gone through the current list, remove it from the queue
|
||||
dma_pushbuffer.pop();
|
||||
dma_pushbuffer_subindex = 0;
|
||||
} else if (command_list.command_lists[dma_pushbuffer_subindex].sync && Settings::values.sync_memory_operations.GetValue()) {
|
||||
signal_sync = true;
|
||||
}
|
||||
|
||||
if (signal_sync) {
|
||||
rasterizer->SignalFence([this]() {
|
||||
std::scoped_lock lk(sync_mutex);
|
||||
synced = true;
|
||||
sync_cv.notify_all();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,8 @@ public:
|
||||
if constexpr (can_async_check) {
|
||||
guard.lock();
|
||||
}
|
||||
if (Settings::IsGPULevelLow() || (Settings::IsGPULevelMedium() && !should_flush)) {
|
||||
// if ((Settings::IsGPULevelLow() || Settings::IsGPULevelMedium()) && !should_flush) {
|
||||
if (false) {
|
||||
func();
|
||||
} else {
|
||||
uncommitted_operations.emplace_back(std::move(func));
|
||||
@@ -91,10 +92,6 @@ public:
|
||||
uncommitted_operations.clear();
|
||||
}
|
||||
QueueFence(new_fence);
|
||||
//if (!new_fence->IsStubbed()) {
|
||||
// std::scoped_lock lock{texture_cache.mutex};
|
||||
// texture_cache.CommitPendingGpuAccesses(new_fence->WaitTick());
|
||||
//}
|
||||
fences.push(std::move(new_fence));
|
||||
if (should_flush) {
|
||||
rasterizer.FlushCommands();
|
||||
@@ -183,7 +180,7 @@ private:
|
||||
return;
|
||||
}
|
||||
}
|
||||
PopAsyncFlushes(current_fence->WaitTick());
|
||||
PopAsyncFlushes();
|
||||
auto operations = std::move(pending_operations.front());
|
||||
pending_operations.pop_front();
|
||||
for (auto& operation : operations) {
|
||||
@@ -218,7 +215,7 @@ private:
|
||||
if (!current_fence->IsStubbed()) {
|
||||
WaitFence(current_fence);
|
||||
}
|
||||
PopAsyncFlushes(current_fence->WaitTick());
|
||||
PopAsyncFlushes();
|
||||
for (auto& operation : current_operations) {
|
||||
operation();
|
||||
}
|
||||
@@ -241,11 +238,10 @@ private:
|
||||
query_cache.HasUncommittedFlushes();
|
||||
}
|
||||
|
||||
void PopAsyncFlushes(u64 completed_tick) {
|
||||
void PopAsyncFlushes() {
|
||||
{
|
||||
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
||||
texture_cache.PopAsyncFlushes();
|
||||
texture_cache.CompleteGpuAccesses(completed_tick);
|
||||
buffer_cache.PopAsyncFlushes();
|
||||
}
|
||||
query_cache.PopAsyncFlushes();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include <thread>
|
||||
#include "core/memory.h"
|
||||
#include "video_core/host1x/ffmpeg/ffmpeg.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -7,6 +7,9 @@
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <stdint.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
|
||||
extern "C" {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
@@ -107,9 +110,19 @@ void Vic::Execute() {
|
||||
auto output_height{config.output_surface_config.out_surface_height + 1};
|
||||
output_surface.resize_destructive(output_width * output_height);
|
||||
|
||||
// Initialize the surface with the appropriate black pixel
|
||||
Pixel black_pixel{};
|
||||
if (config.output_surface_config.out_pixel_format == VideoPixelFormat::Y8__V8U8_N420) {
|
||||
// Y=0, U=512, V=512 (10-bit), A=0
|
||||
black_pixel = {0, 512, 512, 0};
|
||||
} else {
|
||||
// R=0, G=0, B=0, A=0
|
||||
black_pixel = {0, 0, 0, 0};
|
||||
}
|
||||
std::fill(output_surface.begin(), output_surface.end(), black_pixel);
|
||||
|
||||
if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Off) [[unlikely]] {
|
||||
// Fill the frame with black, as otherwise they can have random data and be very glitchy.
|
||||
std::fill(output_surface.begin(), output_surface.end(), Pixel{});
|
||||
|
||||
} else {
|
||||
for (size_t i = 0; i < config.slot_structs.size(); i++) {
|
||||
auto& slot_config{config.slot_structs[i]};
|
||||
@@ -122,7 +135,18 @@ void Vic::Execute() {
|
||||
nvdec_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
}
|
||||
|
||||
if (auto frame = frame_queue.GetFrame(nvdec_id, luma_offset); frame) {
|
||||
auto frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
if (!frame) {
|
||||
// We might've failed to find the frame, or the nvdec id is stale/wrong.
|
||||
// Try to find the nvdec id again.
|
||||
const s32 new_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
if (new_id != -1) {
|
||||
nvdec_id = new_id;
|
||||
frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
if (frame.get()) {
|
||||
switch (frame->GetPixelFormat()) {
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
@@ -191,20 +215,44 @@ void Vic::ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const P
|
||||
|
||||
const auto alpha{u16(slot.config.planar_alpha.Value())};
|
||||
for (s32 y = 0; y < in_luma_height; y++) {
|
||||
const auto src_luma{y * in_luma_stride};
|
||||
const auto src_chroma{(y / 2) * in_chroma_stride};
|
||||
const auto dst{y * out_luma_stride};
|
||||
for (s32 x = 0; x < in_luma_width; x++) {
|
||||
slot_surface[dst + x].r = u16(luma_buffer[src_luma + x] << 2);
|
||||
// Chroma samples are duplicated horizontally and vertically.
|
||||
if(planar) {
|
||||
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + x / 2] << 2);
|
||||
slot_surface[dst + x].b = u16(chroma_v_buffer[src_chroma + x / 2] << 2);
|
||||
const u8* luma_ptr = luma_buffer + y * in_luma_stride;
|
||||
const u8* chroma_u_ptr = chroma_u_buffer + (y / 2) * in_chroma_stride;
|
||||
// For planar, V buffer is separate. For NV12, it is not used directly in the same way.
|
||||
const u8* chroma_v_ptr = planar ? (chroma_v_buffer + (y / 2) * in_chroma_stride) : nullptr;
|
||||
|
||||
Pixel* dst_ptr = &slot_surface[y * out_luma_stride];
|
||||
|
||||
for (s32 x = 0; x < in_luma_width; x += 2) {
|
||||
u16 u_val, v_val;
|
||||
|
||||
if (planar) {
|
||||
// YUV420P: U and V are in separate planes.
|
||||
// 1 UV pair for 2 horizontal pixels.
|
||||
u_val = u16(chroma_u_ptr[x / 2] << 2);
|
||||
v_val = u16(chroma_v_ptr[x / 2] << 2);
|
||||
} else {
|
||||
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + (x & ~1) + 0] << 2);
|
||||
slot_surface[dst + x].b = u16(chroma_u_buffer[src_chroma + (x & ~1) + 1] << 2);
|
||||
// NV12: UV are interleaved in the second plane.
|
||||
// U is at even byte, V is at odd byte.
|
||||
// x is even (0, 2, 4...), so x corresponds to the byte offset in the interleaved buffer.
|
||||
u_val = u16(chroma_u_ptr[x] << 2);
|
||||
v_val = u16(chroma_u_ptr[x + 1] << 2);
|
||||
}
|
||||
slot_surface[dst + x].a = alpha;
|
||||
|
||||
// Pixel 1 (Even x)
|
||||
dst_ptr[0].r = u16(luma_ptr[x] << 2);
|
||||
dst_ptr[0].g = u_val;
|
||||
dst_ptr[0].b = v_val;
|
||||
dst_ptr[0].a = alpha;
|
||||
|
||||
// Pixel 2 (Odd x), check boundary
|
||||
if (x + 1 < in_luma_width) {
|
||||
dst_ptr[1].r = u16(luma_ptr[x + 1] << 2);
|
||||
dst_ptr[1].g = u_val;
|
||||
dst_ptr[1].b = v_val;
|
||||
dst_ptr[1].a = alpha;
|
||||
}
|
||||
|
||||
dst_ptr += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,12 +211,6 @@ void QueryCacheBase<Traits>::CounterClose(QueryType counter_type) {
|
||||
streamer->CloseCounter();
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
bool QueryCacheBase<Traits>::HasStreamer(QueryType counter_type) const {
|
||||
const size_t index = static_cast<size_t>(counter_type);
|
||||
return impl->streamers[index] != nullptr;
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
void QueryCacheBase<Traits>::CounterReset(QueryType counter_type) {
|
||||
size_t index = static_cast<size_t>(counter_type);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -95,8 +92,6 @@ public:
|
||||
|
||||
void CounterReset(QueryType counter_type);
|
||||
|
||||
[[nodiscard]] bool HasStreamer(QueryType counter_type) const;
|
||||
|
||||
void CounterClose(QueryType counter_type);
|
||||
|
||||
void CounterReport(GPUVAddr addr, QueryType counter_type, QueryPropertiesFlags flags,
|
||||
|
||||
@@ -198,10 +198,6 @@ public:
|
||||
return device.CanReportMemoryUsage();
|
||||
}
|
||||
|
||||
u32 GetUniformBufferAlignment() const {
|
||||
return static_cast<u32>(device.GetUniformBufferAlignment());
|
||||
}
|
||||
|
||||
u32 GetStorageBufferAlignment() const {
|
||||
return static_cast<u32>(device.GetShaderStorageBufferAlignment());
|
||||
}
|
||||
|
||||
@@ -7,50 +7,13 @@
|
||||
#include <cstring>
|
||||
#include <bit>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include "common/cityhash.h"
|
||||
#include "common/settings.h" // for enum class Settings::ShaderBackend
|
||||
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/surface.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace {
|
||||
|
||||
std::optional<VideoCore::Surface::PixelFormatNumeric>
|
||||
NumericFromComponentType(Shader::SamplerComponentType component_type) {
|
||||
using VideoCore::Surface::PixelFormatNumeric;
|
||||
switch (component_type) {
|
||||
case Shader::SamplerComponentType::Float:
|
||||
return PixelFormatNumeric::Float;
|
||||
case Shader::SamplerComponentType::Sint:
|
||||
return PixelFormatNumeric::Sint;
|
||||
case Shader::SamplerComponentType::Uint:
|
||||
return PixelFormatNumeric::Uint;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
VideoCore::Surface::PixelFormat ResolveTexelBufferFormat(
|
||||
VideoCore::Surface::PixelFormat format, Shader::SamplerComponentType component_type) {
|
||||
const auto desired_numeric = NumericFromComponentType(component_type);
|
||||
if (!desired_numeric) {
|
||||
return format;
|
||||
}
|
||||
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
|
||||
if (*desired_numeric == current_numeric) {
|
||||
return format;
|
||||
}
|
||||
if (const auto variant =
|
||||
VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
|
||||
return *variant;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
using Shader::ImageBufferDescriptor;
|
||||
using Tegra::Texture::TexturePair;
|
||||
@@ -211,12 +174,8 @@ void ComputePipeline::Configure() {
|
||||
is_written = desc.is_written;
|
||||
}
|
||||
ImageView& image_view{texture_cache.GetImageView(views[texbuf_index].id)};
|
||||
auto buffer_format = image_view.format;
|
||||
if constexpr (!is_image) {
|
||||
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
|
||||
}
|
||||
buffer_cache.BindComputeTextureBuffer(texbuf_index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), buffer_format,
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image);
|
||||
++texbuf_index;
|
||||
}
|
||||
@@ -246,8 +205,7 @@ void ComputePipeline::Configure() {
|
||||
for (const auto& desc : info.texture_descriptors) {
|
||||
for (u32 index = 0; index < desc.count; ++index) {
|
||||
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
||||
textures[texture_binding] =
|
||||
image_view.SampledView(desc.type, desc.component_type);
|
||||
textures[texture_binding] = image_view.Handle(desc.type);
|
||||
if (texture_cache.IsRescaling(image_view)) {
|
||||
texture_scaling_mask |= 1u << texture_binding;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -28,10 +25,6 @@ public:
|
||||
|
||||
void Wait();
|
||||
|
||||
[[nodiscard]] u64 WaitTick() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
OGLSync sync_object;
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <bit>
|
||||
@@ -19,7 +18,6 @@
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/gl_state_tracker.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
|
||||
#if defined(_MSC_VER) && defined(NDEBUG)
|
||||
@@ -41,38 +39,6 @@ using VideoCommon::ImageId;
|
||||
constexpr u32 MAX_TEXTURES = 64;
|
||||
constexpr u32 MAX_IMAGES = 8;
|
||||
|
||||
std::optional<VideoCore::Surface::PixelFormatNumeric>
|
||||
NumericFromComponentType(Shader::SamplerComponentType component_type) {
|
||||
using VideoCore::Surface::PixelFormatNumeric;
|
||||
switch (component_type) {
|
||||
case Shader::SamplerComponentType::Float:
|
||||
return PixelFormatNumeric::Float;
|
||||
case Shader::SamplerComponentType::Sint:
|
||||
return PixelFormatNumeric::Sint;
|
||||
case Shader::SamplerComponentType::Uint:
|
||||
return PixelFormatNumeric::Uint;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
VideoCore::Surface::PixelFormat ResolveTexelBufferFormat(
|
||||
VideoCore::Surface::PixelFormat format, Shader::SamplerComponentType component_type) {
|
||||
const auto desired_numeric = NumericFromComponentType(component_type);
|
||||
if (!desired_numeric) {
|
||||
return format;
|
||||
}
|
||||
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
|
||||
if (*desired_numeric == current_numeric) {
|
||||
return format;
|
||||
}
|
||||
if (const auto variant =
|
||||
VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
|
||||
return *variant;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
GLenum Stage(size_t stage_index) {
|
||||
switch (stage_index) {
|
||||
case 0:
|
||||
@@ -431,12 +397,8 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
is_written = desc.is_written;
|
||||
}
|
||||
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
|
||||
auto buffer_format = image_view.format;
|
||||
if constexpr (!is_image) {
|
||||
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
|
||||
}
|
||||
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), buffer_format,
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image);
|
||||
++index;
|
||||
++texture_buffer_it;
|
||||
@@ -521,8 +483,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
for (const auto& desc : info.texture_descriptors) {
|
||||
for (u32 index = 0; index < desc.count; ++index) {
|
||||
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
|
||||
textures[texture_binding] =
|
||||
image_view.SampledView(desc.type, desc.component_type);
|
||||
textures[texture_binding] = image_view.Handle(desc.type);
|
||||
if (texture_cache.IsRescaling(image_view)) {
|
||||
texture_scaling_mask |= 1u << stage_texture_binding;
|
||||
}
|
||||
|
||||
@@ -220,7 +220,6 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
.support_gl_sparse_textures = device.HasSparseTexture2(),
|
||||
.support_gl_derivative_control = device.HasDerivativeControl(),
|
||||
.support_geometry_streams = true,
|
||||
.warp_stage_support_mask = 0xFFFFFFFFu,
|
||||
|
||||
.warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyLargerThanGuest(),
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ static OGLProgram LinkSeparableProgram(GLuint shader) {
|
||||
glGetProgramInfoLog(program.handle, log_length, nullptr, log.data());
|
||||
if (link_status == GL_FALSE) {
|
||||
LOG_ERROR(Render_OpenGL, "{}", log);
|
||||
glDeleteProgram(program.handle);
|
||||
program.handle = 0;
|
||||
} else {
|
||||
LOG_WARNING(Render_OpenGL, "{}", log);
|
||||
}
|
||||
|
||||
@@ -692,15 +692,6 @@ bool TextureCacheRuntime::HasNativeASTC() const noexcept {
|
||||
return device.HasASTC();
|
||||
}
|
||||
|
||||
bool TextureCacheRuntime::SupportsLinearFilter(VideoCore::Surface::PixelFormat format) const noexcept {
|
||||
using VideoCore::Surface::GetFormatType;
|
||||
using VideoCore::Surface::IsPixelFormatInteger;
|
||||
if (IsPixelFormatInteger(format)) {
|
||||
return false;
|
||||
}
|
||||
return GetFormatType(format) == VideoCore::Surface::SurfaceType::ColorTexture;
|
||||
}
|
||||
|
||||
Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_, GPUVAddr gpu_addr_,
|
||||
VAddr cpu_addr_)
|
||||
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), runtime{&runtime_} {
|
||||
@@ -1238,13 +1229,6 @@ GLuint ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFor
|
||||
return view;
|
||||
}
|
||||
|
||||
GLuint ImageView::SampledView(Shader::TextureType view_type,
|
||||
Shader::SamplerComponentType /*component_type*/) {
|
||||
// OpenGL swizzles already configure depth/stencil selection per TIC entry,
|
||||
// so fall back to the default view handle.
|
||||
return Handle(view_type);
|
||||
}
|
||||
|
||||
void ImageView::SetupView(Shader::TextureType view_type) {
|
||||
views[static_cast<size_t>(view_type)] = MakeView(view_type, internal_format);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -16,7 +13,6 @@
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
|
||||
#include "video_core/renderer_opengl/util_shaders.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/image_view_base.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
|
||||
@@ -133,8 +129,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SupportsLinearFilter(VideoCore::Surface::PixelFormat format) const noexcept;
|
||||
|
||||
bool HasBrokenTextureViewFormats() const noexcept {
|
||||
return has_broken_texture_view_formats;
|
||||
}
|
||||
@@ -143,8 +137,6 @@ public:
|
||||
|
||||
void TickFrame() {}
|
||||
|
||||
void WaitForGpuTick(u64) {}
|
||||
|
||||
StateTracker& GetStateTracker() {
|
||||
return state_tracker;
|
||||
}
|
||||
@@ -272,9 +264,6 @@ public:
|
||||
[[nodiscard]] GLuint StorageView(Shader::TextureType texture_type,
|
||||
Shader::ImageFormat image_format);
|
||||
|
||||
[[nodiscard]] GLuint SampledView(Shader::TextureType view_type,
|
||||
Shader::SamplerComponentType component_type);
|
||||
|
||||
[[nodiscard]] GLuint Handle(Shader::TextureType handle_type) const noexcept {
|
||||
return views[static_cast<size_t>(handle_type)];
|
||||
}
|
||||
|
||||
@@ -116,9 +116,14 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
|
||||
glBindTextureUnit(0, textures[i]);
|
||||
glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
|
||||
matrices[i].data());
|
||||
glProgramUniform2ui(frag.handle, ScreenSizeLocation,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
if (frag.handle != 0) {
|
||||
const GLint screen_size_loc = glGetUniformLocation(frag.handle, "screen_size");
|
||||
if (screen_size_loc != -1) {
|
||||
glProgramUniform2ui(frag.handle, screen_size_loc,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
}
|
||||
}
|
||||
glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i]));
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
@@ -525,24 +525,18 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
||||
nullptr, PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(float) * 4>))),
|
||||
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
|
||||
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)),
|
||||
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
|
||||
? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)
|
||||
: vk::ShaderModule{}),
|
||||
blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)),
|
||||
clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)),
|
||||
clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)),
|
||||
clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)),
|
||||
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
|
||||
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
|
||||
convert_abgr8_to_d24s8_frag(device.IsExtShaderStencilExportSupported()
|
||||
? BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)
|
||||
: vk::ShaderModule{}),
|
||||
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
|
||||
convert_abgr8_to_d32f_frag(BuildShader(device, CONVERT_ABGR8_TO_D32F_FRAG_SPV)),
|
||||
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
|
||||
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
||||
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
||||
convert_abgr8_srgb_to_d24s8_frag(device.IsExtShaderStencilExportSupported()
|
||||
? BuildShader(device, CONVERT_ABGR8_SRGB_TO_D24S8_FRAG_SPV)
|
||||
: vk::ShaderModule{}),
|
||||
convert_abgr8_srgb_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_SRGB_TO_D24S8_FRAG_SPV)),
|
||||
convert_rgba_to_bgra_frag(BuildShader(device, CONVERT_RGBA8_TO_BGRA8_FRAG_SPV)),
|
||||
convert_yuv420_to_rgb_comp(BuildShader(device, CONVERT_YUV420_TO_RGB_COMP_SPV)),
|
||||
convert_rgb_to_yuv420_comp(BuildShader(device, CONVERT_RGB_TO_YUV420_COMP_SPV)),
|
||||
@@ -673,11 +667,6 @@ void BlitImageHelper::ConvertR16ToD16(const Framebuffer* dst_framebuffer,
|
||||
|
||||
void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
if (!device.IsExtShaderStencilExportSupported()) {
|
||||
// Shader requires VK_EXT_shader_stencil_export which is not available
|
||||
LOG_WARNING(Render_Vulkan, "ConvertABGR8ToD24S8 requires shader_stencil_export, skipping");
|
||||
return;
|
||||
}
|
||||
ConvertPipelineDepthTargetEx(convert_abgr8_to_d24s8_pipeline, dst_framebuffer->RenderPass(),
|
||||
convert_abgr8_to_d24s8_frag);
|
||||
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
|
||||
@@ -713,11 +702,6 @@ void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer,
|
||||
|
||||
void BlitImageHelper::ConvertABGR8SRGBToD24S8(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
if (!device.IsExtShaderStencilExportSupported()) {
|
||||
// Shader requires VK_EXT_shader_stencil_export which is not available
|
||||
LOG_WARNING(Render_Vulkan, "ConvertABGR8SRGBToD24S8 requires shader_stencil_export, skipping");
|
||||
return;
|
||||
}
|
||||
ConvertPipelineDepthTargetEx(convert_abgr8_srgb_to_d24s8_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
convert_abgr8_srgb_to_d24s8_frag);
|
||||
|
||||
@@ -59,7 +59,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
raw1 = 0;
|
||||
extended_dynamic_state.Assign(features.has_extended_dynamic_state ? 1 : 0);
|
||||
extended_dynamic_state_2.Assign(features.has_extended_dynamic_state_2 ? 1 : 0);
|
||||
extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0);
|
||||
extended_dynamic_state_2_extra.Assign(features.has_extended_dynamic_state_2_extra ? 1 : 0);
|
||||
extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0);
|
||||
extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0);
|
||||
dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0);
|
||||
@@ -157,7 +157,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
return static_cast<u16>(array.stride.Value());
|
||||
});
|
||||
}
|
||||
if (!extended_dynamic_state_2_logic_op) {
|
||||
if (!extended_dynamic_state_2_extra) {
|
||||
dynamic_state.Refresh2(regs, topology_, extended_dynamic_state_2);
|
||||
}
|
||||
if (!extended_dynamic_state_3_blend) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -23,11 +20,9 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
struct DynamicFeatures {
|
||||
bool has_extended_dynamic_state;
|
||||
bool has_extended_dynamic_state_2;
|
||||
bool has_extended_dynamic_state_2_logic_op;
|
||||
bool has_extended_dynamic_state_2_patch_control_points;
|
||||
bool has_extended_dynamic_state_2_extra;
|
||||
bool has_extended_dynamic_state_3_blend;
|
||||
bool has_extended_dynamic_state_3_enables;
|
||||
bool has_dual_source_blend;
|
||||
bool has_dynamic_vertex_input;
|
||||
};
|
||||
|
||||
@@ -191,7 +186,7 @@ struct FixedPipelineState {
|
||||
u32 raw1;
|
||||
BitField<0, 1, u32> extended_dynamic_state;
|
||||
BitField<1, 1, u32> extended_dynamic_state_2;
|
||||
BitField<2, 1, u32> extended_dynamic_state_2_logic_op;
|
||||
BitField<2, 1, u32> extended_dynamic_state_2_extra;
|
||||
BitField<3, 1, u32> extended_dynamic_state_3_blend;
|
||||
BitField<4, 1, u32> extended_dynamic_state_3_enables;
|
||||
BitField<5, 1, u32> dynamic_vertex_input;
|
||||
|
||||
@@ -165,7 +165,7 @@ struct FormatTuple {
|
||||
{VK_FORMAT_R16G16_SINT, Attachable | Storage}, // R16G16_SINT
|
||||
{VK_FORMAT_R16G16_SNORM, Attachable | Storage}, // R16G16_SNORM
|
||||
{VK_FORMAT_R32G32B32_SFLOAT}, // R32G32B32_FLOAT
|
||||
{VK_FORMAT_A8B8G8R8_SRGB_PACK32, Attachable | Storage}, // A8B8G8R8_SRGB
|
||||
{VK_FORMAT_A8B8G8R8_SRGB_PACK32, Attachable}, // A8B8G8R8_SRGB
|
||||
{VK_FORMAT_R8G8_UNORM, Attachable | Storage}, // R8G8_UNORM
|
||||
{VK_FORMAT_R8G8_SNORM, Attachable | Storage}, // R8G8_SNORM
|
||||
{VK_FORMAT_R8G8_SINT, Attachable | Storage}, // R8G8_SINT
|
||||
@@ -177,7 +177,7 @@ struct FormatTuple {
|
||||
{VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8_UNORM
|
||||
{VK_FORMAT_ASTC_8x5_UNORM_BLOCK}, // ASTC_2D_8X5_UNORM
|
||||
{VK_FORMAT_ASTC_5x4_UNORM_BLOCK}, // ASTC_2D_5X4_UNORM
|
||||
{VK_FORMAT_B8G8R8A8_SRGB, Attachable | Storage}, // B8G8R8A8_SRGB
|
||||
{VK_FORMAT_B8G8R8A8_SRGB, Attachable}, // B8G8R8A8_SRGB
|
||||
{VK_FORMAT_BC1_RGBA_SRGB_BLOCK}, // BC1_RGBA_SRGB
|
||||
{VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB
|
||||
{VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
|
||||
|
||||
@@ -189,16 +189,12 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
|
||||
const VideoCommon::ImageViewId image_view_id{(views++)->id};
|
||||
const VideoCommon::SamplerId sampler_id{*(samplers++)};
|
||||
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
|
||||
const VkImageView vk_image_view{
|
||||
image_view.SampledView(desc.type, desc.component_type)};
|
||||
const VkImageView vk_image_view{image_view.Handle(desc.type)};
|
||||
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
|
||||
const bool supports_linear_filter{
|
||||
texture_cache.SupportsLinearFilter(image_view.format)};
|
||||
const bool supports_depth_compare_sampling{
|
||||
image_view.SupportsDepthCompareSampling()};
|
||||
const VkSampler vk_sampler{
|
||||
sampler.SelectHandle(supports_linear_filter, image_view.SupportsAnisotropy(),
|
||||
supports_depth_compare_sampling)};
|
||||
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
|
||||
!image_view.SupportsAnisotropy()};
|
||||
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
|
||||
: sampler.Handle()};
|
||||
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
|
||||
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
|
||||
}
|
||||
|
||||
@@ -280,6 +280,7 @@ void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t i
|
||||
Tegra::Texture::UnswizzleTexture(
|
||||
mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
|
||||
bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
|
||||
buffer.Flush(); // Ensure host writes are visible before the GPU copy.
|
||||
}
|
||||
|
||||
const VkBufferImageCopy copy{
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
@@ -334,13 +333,6 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
||||
staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
|
||||
quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
|
||||
compute_pass_descriptor_queue) {
|
||||
const VkDriverIdKHR driver_id = device.GetDriverID();
|
||||
limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_MESA_TURNIP ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
if (limit_dynamic_storage_buffers) {
|
||||
max_dynamic_storage_buffers = device.GetMaxDescriptorSetStorageBuffersDynamic();
|
||||
}
|
||||
if (device.GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
|
||||
// TODO: FixMe: Uint8Pass compute shader does not build on some Qualcomm drivers.
|
||||
uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
|
||||
@@ -416,10 +408,6 @@ bool BufferCacheRuntime::CanReportMemoryUsage() const {
|
||||
return device.CanReportMemoryUsage();
|
||||
}
|
||||
|
||||
u32 BufferCacheRuntime::GetUniformBufferAlignment() const {
|
||||
return static_cast<u32>(device.GetUniformBufferAlignment());
|
||||
}
|
||||
|
||||
u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
|
||||
return static_cast<u32>(device.GetStorageBufferAlignment());
|
||||
}
|
||||
@@ -595,15 +583,7 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
|
||||
if (index >= device.GetMaxVertexInputBindings()) {
|
||||
return;
|
||||
}
|
||||
if (!device.HasNullDescriptor() && buffer == VK_NULL_HANDLE) {
|
||||
ReserveNullBuffer();
|
||||
buffer = *null_buffer;
|
||||
offset = 0;
|
||||
size = std::numeric_limits<u32>::max();
|
||||
}
|
||||
// Use BindVertexBuffers2EXT only if EDS1 is supported AND VIDS is not active
|
||||
// When VIDS is active, the pipeline doesn't declare VERTEX_INPUT_BINDING_STRIDE as dynamic
|
||||
if (device.IsExtExtendedDynamicStateSupported() && !device.IsExtVertexInputDynamicStateSupported()) {
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
|
||||
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
|
||||
const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE;
|
||||
@@ -643,8 +623,7 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
|
||||
if (binding_count == 0) {
|
||||
return;
|
||||
}
|
||||
// Use BindVertexBuffers2EXT only if EDS1 is supported AND VIDS is not active
|
||||
if (device.IsExtExtendedDynamicStateSupported() && !device.IsExtVertexInputDynamicStateSupported()) {
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles),
|
||||
binding_count](vk::CommandBuffer cmdbuf) {
|
||||
@@ -701,50 +680,27 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::ReserveNullBuffer() {
|
||||
const VkBufferUsageFlags expected_usage = NullBufferUsageFlags();
|
||||
if (null_buffer && null_buffer_usage_flags != expected_usage) {
|
||||
RefreshNullBuffer();
|
||||
}
|
||||
if (!null_buffer) {
|
||||
null_buffer = CreateNullBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
VkBufferUsageFlags BufferCacheRuntime::NullBufferUsageFlags() const {
|
||||
VkBufferUsageFlags usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
|
||||
if (device.IsExtTransformFeedbackSupported()) {
|
||||
usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::RefreshNullBuffer() {
|
||||
if (!null_buffer) {
|
||||
return;
|
||||
}
|
||||
scheduler.Finish();
|
||||
null_buffer.reset();
|
||||
null_buffer = CreateNullBuffer();
|
||||
}
|
||||
|
||||
vk::Buffer BufferCacheRuntime::CreateNullBuffer() {
|
||||
VkBufferCreateInfo create_info{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = 4,
|
||||
.usage = NullBufferUsageFlags(),
|
||||
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
};
|
||||
if (device.IsExtTransformFeedbackSupported()) {
|
||||
create_info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||
}
|
||||
vk::Buffer ret = memory_allocator.CreateBuffer(create_info, MemoryUsage::DeviceLocal);
|
||||
null_buffer_usage_flags = create_info.usage;
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
ret.SetObjectNameEXT("Null buffer");
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "video_core/buffer_cache/buffer_cache_base.h"
|
||||
#include "video_core/buffer_cache/memory_tracker_base.h"
|
||||
#include "video_core/buffer_cache/usage_tracker.h"
|
||||
@@ -96,8 +94,6 @@ public:
|
||||
|
||||
bool CanReportMemoryUsage() const;
|
||||
|
||||
u32 GetUniformBufferAlignment() const;
|
||||
|
||||
u32 GetStorageBufferAlignment() const;
|
||||
|
||||
[[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
|
||||
@@ -131,9 +127,6 @@ public:
|
||||
|
||||
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
|
||||
|
||||
/// Forces destruction and recreation of the shared null buffer so new usage flags take effect.
|
||||
void RefreshNullBuffer();
|
||||
|
||||
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t /*stage*/,
|
||||
[[maybe_unused]] u32 /*binding_index*/,
|
||||
u32 size) {
|
||||
@@ -162,14 +155,6 @@ public:
|
||||
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
|
||||
}
|
||||
|
||||
bool ShouldLimitDynamicStorageBuffers() const {
|
||||
return limit_dynamic_storage_buffers;
|
||||
}
|
||||
|
||||
u32 GetMaxDynamicStorageBuffers() const {
|
||||
return max_dynamic_storage_buffers;
|
||||
}
|
||||
|
||||
private:
|
||||
void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
|
||||
guest_descriptor_queue.AddBuffer(buffer, offset, size);
|
||||
@@ -177,7 +162,6 @@ private:
|
||||
|
||||
void ReserveNullBuffer();
|
||||
vk::Buffer CreateNullBuffer();
|
||||
VkBufferUsageFlags NullBufferUsageFlags() const;
|
||||
|
||||
struct UniformRing {
|
||||
static constexpr size_t NUM_FRAMES = 3;
|
||||
@@ -207,13 +191,9 @@ private:
|
||||
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
|
||||
|
||||
vk::Buffer null_buffer;
|
||||
VkBufferUsageFlags null_buffer_usage_flags = 0;
|
||||
|
||||
std::unique_ptr<Uint8Pass> uint8_pass;
|
||||
QuadIndexedPass quad_index_pass;
|
||||
|
||||
bool limit_dynamic_storage_buffers = false;
|
||||
u32 max_dynamic_storage_buffers = std::numeric_limits<u32>::max();
|
||||
};
|
||||
|
||||
struct BufferCacheParams {
|
||||
|
||||
@@ -418,9 +418,6 @@ ConditionalRenderingResolvePass::ConditionalRenderingResolvePass(
|
||||
|
||||
void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_buffer,
|
||||
u32 src_offset, bool compare_to_zero) {
|
||||
if (!device.IsExtConditionalRendering()) {
|
||||
return;
|
||||
}
|
||||
const size_t compare_size = compare_to_zero ? 8 : 24;
|
||||
|
||||
compute_pass_descriptor_queue.Acquire();
|
||||
@@ -451,7 +448,7 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||
cmdbuf.Dispatch(1, 1, 1);
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier);
|
||||
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, write_barrier);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -462,14 +459,10 @@ QueriesPrefixScanPass::QueriesPrefixScanPass(
|
||||
device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS,
|
||||
QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO,
|
||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(QueriesPrefixScanPushConstants)>,
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT) &&
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT) &&
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT) &&
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT)
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) &&
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) &&
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
|
||||
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT)
|
||||
? std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_COMP_SPV)
|
||||
: std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_NOSUBGROUPS_COMP_SPV)),
|
||||
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
||||
@@ -477,14 +470,6 @@ QueriesPrefixScanPass::QueriesPrefixScanPass(
|
||||
void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer,
|
||||
VkBuffer src_buffer, size_t number_of_sums,
|
||||
size_t min_accumulation_limit, size_t max_accumulation_limit) {
|
||||
constexpr VkAccessFlags BASE_DST_ACCESS = VK_ACCESS_SHADER_READ_BIT |
|
||||
VK_ACCESS_TRANSFER_READ_BIT |
|
||||
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
|
||||
VK_ACCESS_INDIRECT_COMMAND_READ_BIT |
|
||||
VK_ACCESS_INDEX_READ_BIT |
|
||||
VK_ACCESS_UNIFORM_READ_BIT;
|
||||
const VkAccessFlags conditional_access =
|
||||
device.IsExtConditionalRendering() ? VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT : 0;
|
||||
size_t current_runs = number_of_sums;
|
||||
size_t offset = 0;
|
||||
while (current_runs != 0) {
|
||||
@@ -501,18 +486,22 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, descriptor_data, min_accumulation_limit, max_accumulation_limit,
|
||||
runs_to_do, used_offset, conditional_access](vk::CommandBuffer cmdbuf) {
|
||||
runs_to_do, used_offset](vk::CommandBuffer cmdbuf) {
|
||||
static constexpr VkMemoryBarrier read_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
};
|
||||
const VkMemoryBarrier write_barrier{
|
||||
static constexpr VkMemoryBarrier write_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = BASE_DST_ACCESS | conditional_access,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
|
||||
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
|
||||
VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
|
||||
VK_ACCESS_UNIFORM_READ_BIT |
|
||||
VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
|
||||
};
|
||||
const QueriesPrefixScanPushConstants uniforms{
|
||||
.min_accumulation_base = static_cast<u32>(min_accumulation_limit),
|
||||
@@ -530,7 +519,8 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe
|
||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
|
||||
cmdbuf.Dispatch(1, 1, 1);
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier);
|
||||
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0,
|
||||
write_barrier);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,49 +18,14 @@
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include <optional>
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
using Shader::ImageBufferDescriptor;
|
||||
using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
|
||||
using Tegra::Texture::TexturePair;
|
||||
using VideoCore::Surface::PixelFormat;
|
||||
using VideoCore::Surface::PixelFormatNumeric;
|
||||
|
||||
static std::optional<PixelFormatNumeric> NumericFromComponentType(
|
||||
Shader::SamplerComponentType component_type) {
|
||||
switch (component_type) {
|
||||
case Shader::SamplerComponentType::Float:
|
||||
return PixelFormatNumeric::Float;
|
||||
case Shader::SamplerComponentType::Sint:
|
||||
return PixelFormatNumeric::Sint;
|
||||
case Shader::SamplerComponentType::Uint:
|
||||
return PixelFormatNumeric::Uint;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static PixelFormat ResolveTexelBufferFormat(PixelFormat format,
|
||||
Shader::SamplerComponentType component_type) {
|
||||
const auto desired_numeric = NumericFromComponentType(component_type);
|
||||
if (!desired_numeric) {
|
||||
return format;
|
||||
}
|
||||
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
|
||||
if (*desired_numeric == current_numeric) {
|
||||
return format;
|
||||
}
|
||||
if (const auto variant = VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
|
||||
return *variant;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipeline_cache_,
|
||||
DescriptorPool& descriptor_pool,
|
||||
@@ -217,12 +182,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
is_written = desc.is_written;
|
||||
}
|
||||
ImageView& image_view = texture_cache.GetImageView(views[index].id);
|
||||
VideoCore::Surface::PixelFormat buffer_format = image_view.format;
|
||||
if constexpr (!is_image) {
|
||||
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
|
||||
}
|
||||
buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), buffer_format,
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image);
|
||||
++index;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,6 @@ public:
|
||||
|
||||
void Wait();
|
||||
|
||||
[[nodiscard]] u64 WaitTick() const noexcept {
|
||||
return wait_tick;
|
||||
}
|
||||
|
||||
private:
|
||||
Scheduler& scheduler;
|
||||
u64 wait_tick = 0;
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
|
||||
@@ -25,9 +23,7 @@
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/texture_cache/samples_helper.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
#if defined(_MSC_VER) && defined(NDEBUG)
|
||||
@@ -48,83 +44,10 @@ using Tegra::Texture::TexturePair;
|
||||
using VideoCore::Surface::PixelFormat;
|
||||
using VideoCore::Surface::PixelFormatFromDepthFormat;
|
||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
using VideoCore::Surface::PixelFormatNumeric;
|
||||
|
||||
constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage;
|
||||
constexpr size_t MAX_IMAGE_ELEMENTS = 64;
|
||||
|
||||
std::optional<PixelFormatNumeric> NumericFromComponentType(
|
||||
Shader::SamplerComponentType component_type) {
|
||||
switch (component_type) {
|
||||
case Shader::SamplerComponentType::Float:
|
||||
return PixelFormatNumeric::Float;
|
||||
case Shader::SamplerComponentType::Sint:
|
||||
return PixelFormatNumeric::Sint;
|
||||
case Shader::SamplerComponentType::Uint:
|
||||
return PixelFormatNumeric::Uint;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
PixelFormat ResolveTexelBufferFormat(PixelFormat format,
|
||||
Shader::SamplerComponentType component_type) {
|
||||
const auto desired_numeric = NumericFromComponentType(component_type);
|
||||
if (!desired_numeric) {
|
||||
return format;
|
||||
}
|
||||
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
|
||||
if (*desired_numeric == current_numeric) {
|
||||
return format;
|
||||
}
|
||||
if (const auto variant = VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
|
||||
return *variant;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
bool UsesDualSourceFactor(Maxwell::Blend::Factor factor) {
|
||||
switch (factor) {
|
||||
case Maxwell::Blend::Factor::Source1Color_D3D:
|
||||
case Maxwell::Blend::Factor::Source1Color_GL:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Color_D3D:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Color_GL:
|
||||
case Maxwell::Blend::Factor::Source1Alpha_D3D:
|
||||
case Maxwell::Blend::Factor::Source1Alpha_GL:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Alpha_D3D:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Alpha_GL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Maxwell::Blend::Factor FallbackDualSourceFactor(Maxwell::Blend::Factor factor) {
|
||||
switch (factor) {
|
||||
case Maxwell::Blend::Factor::Source1Color_D3D:
|
||||
case Maxwell::Blend::Factor::Source1Color_GL:
|
||||
return Maxwell::Blend::Factor::SourceColor_D3D;
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Color_D3D:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Color_GL:
|
||||
return Maxwell::Blend::Factor::OneMinusSourceColor_D3D;
|
||||
case Maxwell::Blend::Factor::Source1Alpha_D3D:
|
||||
case Maxwell::Blend::Factor::Source1Alpha_GL:
|
||||
return Maxwell::Blend::Factor::SourceAlpha_D3D;
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Alpha_D3D:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Alpha_GL:
|
||||
return Maxwell::Blend::Factor::OneMinusSourceAlpha_D3D;
|
||||
default:
|
||||
return factor;
|
||||
}
|
||||
}
|
||||
|
||||
bool AttachmentUsesDualSource(const FixedPipelineState::BlendingAttachment& blend) {
|
||||
return UsesDualSourceFactor(blend.SourceRGBFactor()) ||
|
||||
UsesDualSourceFactor(blend.DestRGBFactor()) ||
|
||||
UsesDualSourceFactor(blend.SourceAlphaFactor()) ||
|
||||
UsesDualSourceFactor(blend.DestAlphaFactor());
|
||||
}
|
||||
|
||||
DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span<const Shader::Info> infos) {
|
||||
DescriptorLayoutBuilder builder{device};
|
||||
for (size_t index = 0; index < infos.size(); ++index) {
|
||||
@@ -340,7 +263,6 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
std::ranges::copy(info->constant_buffer_used_sizes, uniform_buffer_sizes[stage].begin());
|
||||
num_textures += Shader::NumDescriptors(info->texture_descriptors);
|
||||
}
|
||||
fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0];
|
||||
auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] {
|
||||
DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)};
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
@@ -494,12 +416,8 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
is_written = desc.is_written;
|
||||
}
|
||||
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
|
||||
VideoCore::Surface::PixelFormat buffer_format = image_view.format;
|
||||
if constexpr (!is_image) {
|
||||
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
|
||||
}
|
||||
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), buffer_format,
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image);
|
||||
++index;
|
||||
++texture_buffer_it;
|
||||
@@ -784,18 +702,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.lineWidth = 1.0f,
|
||||
// TODO(alekpop): Transfer from regs
|
||||
};
|
||||
const bool smooth_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsSmoothLines();
|
||||
const bool stippled_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsStippledRectangularLines();
|
||||
VkPipelineRasterizationLineStateCreateInfoEXT line_state{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.lineRasterizationMode = key.state.smooth_lines != 0 && smooth_lines_supported
|
||||
.lineRasterizationMode = key.state.smooth_lines != 0
|
||||
? VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT
|
||||
: VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT,
|
||||
.stippledLineEnable =
|
||||
(dynamic.line_stipple_enable && stippled_lines_supported) ? VK_TRUE : VK_FALSE,
|
||||
.stippledLineEnable = dynamic.line_stipple_enable ? VK_TRUE : VK_FALSE,
|
||||
.lineStippleFactor = key.state.line_stipple_factor,
|
||||
.lineStipplePattern = static_cast<uint16_t>(key.state.line_stipple_pattern),
|
||||
};
|
||||
@@ -826,25 +739,17 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex);
|
||||
}
|
||||
|
||||
const bool supports_alpha_output = fragment_has_color0_output;
|
||||
const bool alpha_to_one_supported = device.SupportsAlphaToOne();
|
||||
const auto msaa_mode = key.state.msaa_mode.Value();
|
||||
const VkSampleCountFlagBits vk_samples = MaxwellToVK::MsaaMode(msaa_mode);
|
||||
VkPipelineMultisampleStateCreateInfo multisample_ci{
|
||||
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.rasterizationSamples = vk_samples,
|
||||
.rasterizationSamples = MaxwellToVK::MsaaMode(key.state.msaa_mode),
|
||||
.sampleShadingEnable = Settings::values.sample_shading.GetValue() ? VK_TRUE : VK_FALSE,
|
||||
.minSampleShading = static_cast<float>(Settings::values.sample_shading_fraction.GetValue()) / 100.0f,
|
||||
.pSampleMask = nullptr,
|
||||
.alphaToCoverageEnable =
|
||||
supports_alpha_output && key.state.alpha_to_coverage_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
.alphaToOneEnable = supports_alpha_output && alpha_to_one_supported &&
|
||||
key.state.alpha_to_one_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
.alphaToCoverageEnable = key.state.alpha_to_coverage_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
.alphaToOneEnable = key.state.alpha_to_one_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
};
|
||||
|
||||
|
||||
const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -866,12 +771,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
}
|
||||
static_vector<VkPipelineColorBlendAttachmentState, Maxwell::NumRenderTargets> cb_attachments;
|
||||
const size_t num_attachments{NumAttachments(key.state)};
|
||||
const bool supports_dual_source_blend = device.SupportsDualSourceBlend();
|
||||
const u32 max_dual_source_attachments = supports_dual_source_blend
|
||||
? device.MaxFragmentDualSrcAttachments()
|
||||
: 0;
|
||||
u32 granted_dual_source_attachments = 0;
|
||||
bool logged_dual_source_warning = false;
|
||||
for (size_t index = 0; index < num_attachments; ++index) {
|
||||
static constexpr std::array mask_table{
|
||||
VK_COLOR_COMPONENT_R_BIT,
|
||||
@@ -885,30 +784,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
for (size_t i = 0; i < mask_table.size(); ++i) {
|
||||
write_mask |= mask[i] ? mask_table[i] : 0;
|
||||
}
|
||||
const bool attachment_uses_dual_source = AttachmentUsesDualSource(blend);
|
||||
const bool allow_dual_source = attachment_uses_dual_source && supports_dual_source_blend &&
|
||||
granted_dual_source_attachments < max_dual_source_attachments;
|
||||
if (allow_dual_source) {
|
||||
++granted_dual_source_attachments;
|
||||
} else if (attachment_uses_dual_source && !logged_dual_source_warning) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Dual-source blend factors exceed device limit (maxFragmentDualSrcAttachments={}), falling back to single-source factors",
|
||||
max_dual_source_attachments);
|
||||
logged_dual_source_warning = true;
|
||||
}
|
||||
const auto sanitize_factor = [&](Maxwell::Blend::Factor factor) {
|
||||
if (allow_dual_source || !UsesDualSourceFactor(factor)) {
|
||||
return factor;
|
||||
}
|
||||
return FallbackDualSourceFactor(factor);
|
||||
};
|
||||
cb_attachments.push_back({
|
||||
.blendEnable = blend.enable != 0,
|
||||
.srcColorBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.SourceRGBFactor())),
|
||||
.dstColorBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.DestRGBFactor())),
|
||||
.srcColorBlendFactor = MaxwellToVK::BlendFactor(blend.SourceRGBFactor()),
|
||||
.dstColorBlendFactor = MaxwellToVK::BlendFactor(blend.DestRGBFactor()),
|
||||
.colorBlendOp = MaxwellToVK::BlendEquation(blend.EquationRGB()),
|
||||
.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.SourceAlphaFactor())),
|
||||
.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.DestAlphaFactor())),
|
||||
.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.SourceAlphaFactor()),
|
||||
.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.DestAlphaFactor()),
|
||||
.alphaBlendOp = MaxwellToVK::BlendEquation(blend.EquationAlpha()),
|
||||
.colorWriteMask = write_mask,
|
||||
});
|
||||
@@ -924,25 +806,14 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.blendConstants = {}
|
||||
};
|
||||
static_vector<VkDynamicState, 34> dynamic_states{
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS,
|
||||
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
VK_DYNAMIC_STATE_LINE_WIDTH,
|
||||
};
|
||||
|
||||
if (device.UsesAdvancedCoreDynamicState()) {
|
||||
static constexpr std::array core_dynamic_states{
|
||||
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||
VK_DYNAMIC_STATE_DEPTH_BOUNDS,
|
||||
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), core_dynamic_states.begin(),
|
||||
core_dynamic_states.end());
|
||||
}
|
||||
if (key.state.extended_dynamic_state) {
|
||||
static constexpr std::array extended{
|
||||
std::vector<VkDynamicState> extended{
|
||||
VK_DYNAMIC_STATE_CULL_MODE_EXT,
|
||||
VK_DYNAMIC_STATE_FRONT_FACE_EXT,
|
||||
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
|
||||
@@ -952,68 +823,51 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
|
||||
};
|
||||
if (!device.IsExtVertexInputDynamicStateSupported()) {
|
||||
extended.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT);
|
||||
}
|
||||
if (key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
|
||||
}
|
||||
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
|
||||
|
||||
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1
|
||||
// Only use it if VIDS is not active (VIDS replaces it with full vertex input control)
|
||||
if (!key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT);
|
||||
if (key.state.extended_dynamic_state_2) {
|
||||
static constexpr std::array extended2{
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended2.begin(), extended2.end());
|
||||
}
|
||||
}
|
||||
|
||||
// VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS
|
||||
// Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE
|
||||
if (key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
|
||||
}
|
||||
|
||||
// EDS2 - Core (3 states)
|
||||
if (key.state.extended_dynamic_state_2) {
|
||||
static constexpr std::array extended2{
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended2.begin(), extended2.end());
|
||||
}
|
||||
|
||||
// EDS2 - LogicOp (granular)
|
||||
if (key.state.extended_dynamic_state_2_logic_op) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_EXT);
|
||||
}
|
||||
|
||||
// EDS3 - Blending (composite: 3 states)
|
||||
if (key.state.extended_dynamic_state_3_blend) {
|
||||
static constexpr std::array extended3{
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
}
|
||||
|
||||
// EDS3 - Enables (composite: per-feature)
|
||||
if (key.state.extended_dynamic_state_3_enables) {
|
||||
if (device.SupportsDynamicState3DepthClampEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT);
|
||||
if (key.state.extended_dynamic_state_2_extra) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3LogicOpEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT);
|
||||
if (key.state.extended_dynamic_state_3_blend) {
|
||||
static constexpr std::array extended3{
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT,
|
||||
|
||||
// VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
}
|
||||
if (device.SupportsDynamicState3LineRasterizationMode()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3ConservativeRasterizationMode()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3LineStippleEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3AlphaToCoverageEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3AlphaToOneEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT);
|
||||
if (key.state.extended_dynamic_state_3_enables) {
|
||||
static constexpr std::array extended3{
|
||||
VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT,
|
||||
|
||||
// additional state3 extensions
|
||||
VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT,
|
||||
|
||||
VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT,
|
||||
|
||||
VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,17 +82,6 @@ public:
|
||||
const std::array<const Shader::Info*, NUM_STAGES>& infos);
|
||||
|
||||
bool HasDynamicVertexInput() const noexcept { return key.state.dynamic_vertex_input; }
|
||||
bool SupportsAlphaToCoverage() const noexcept {
|
||||
return fragment_has_color0_output;
|
||||
}
|
||||
|
||||
bool SupportsAlphaToOne() const noexcept {
|
||||
return fragment_has_color0_output;
|
||||
}
|
||||
|
||||
bool UsesExtendedDynamicState() const noexcept {
|
||||
return key.state.extended_dynamic_state != 0;
|
||||
}
|
||||
GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete;
|
||||
GraphicsPipeline(GraphicsPipeline&&) noexcept = delete;
|
||||
|
||||
@@ -160,7 +149,6 @@ private:
|
||||
std::array<u32, 5> enabled_uniform_buffer_masks{};
|
||||
VideoCommon::UniformBufferSizes uniform_buffer_sizes{};
|
||||
u32 num_textures{};
|
||||
bool fragment_has_color0_output{};
|
||||
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
DescriptorAllocator descriptor_allocator;
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/shader_cache.h"
|
||||
#include "video_core/shader_environment.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
@@ -106,41 +105,6 @@ Shader::CompareFunction MaxwellToCompareFunction(Maxwell::ComparisonOp compariso
|
||||
return {};
|
||||
}
|
||||
|
||||
Shader::AttributeType RenderTargetAttributeType(Tegra::RenderTargetFormat format) {
|
||||
if (format == Tegra::RenderTargetFormat::NONE) {
|
||||
return Shader::AttributeType::Float;
|
||||
}
|
||||
const auto pixel_format{
|
||||
VideoCore::Surface::PixelFormatFromRenderTargetFormat(format)};
|
||||
if (!VideoCore::Surface::IsPixelFormatInteger(pixel_format)) {
|
||||
return Shader::AttributeType::Float;
|
||||
}
|
||||
if (VideoCore::Surface::IsPixelFormatSignedInteger(pixel_format)) {
|
||||
return Shader::AttributeType::SignedInt;
|
||||
}
|
||||
return Shader::AttributeType::UnsignedInt;
|
||||
}
|
||||
|
||||
VkShaderStageFlagBits StageToVkStage(Shader::Stage stage) {
|
||||
switch (stage) {
|
||||
case Shader::Stage::VertexA:
|
||||
case Shader::Stage::VertexB:
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
case Shader::Stage::TessellationControl:
|
||||
return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
|
||||
case Shader::Stage::TessellationEval:
|
||||
return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
|
||||
case Shader::Stage::Geometry:
|
||||
return VK_SHADER_STAGE_GEOMETRY_BIT;
|
||||
case Shader::Stage::Fragment:
|
||||
return VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
case Shader::Stage::Compute:
|
||||
return VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
default:
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
Shader::AttributeType CastAttributeType(const FixedPipelineState::VertexAttribute& attr) {
|
||||
if (attr.enabled == 0) {
|
||||
return Shader::AttributeType::Disabled;
|
||||
@@ -265,10 +229,6 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
||||
info.alpha_test_func = MaxwellToCompareFunction(
|
||||
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
|
||||
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
|
||||
for (size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[index]);
|
||||
info.color_output_types[index] = RenderTargetAttributeType(format);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -309,8 +269,8 @@ size_t GetTotalPipelineWorkers() {
|
||||
const size_t max_core_threads =
|
||||
std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
|
||||
#ifdef ANDROID
|
||||
// Leave at least one core free on Android to reduce thermal pressure.
|
||||
constexpr size_t free_cores = 1ULL;
|
||||
// Leave at least a few cores free in android
|
||||
constexpr size_t free_cores = 3ULL;
|
||||
if (max_core_threads <= free_cores) {
|
||||
return 1ULL;
|
||||
}
|
||||
@@ -357,7 +317,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
"VkPipelineBuilder"),
|
||||
serialization_thread(1, "VkPipelineSerialization") {
|
||||
const auto& float_control{device.FloatControlProperties()};
|
||||
const bool float_controls_supported{device.IsKhrShaderFloatControlsSupported()};
|
||||
const VkDriverId driver_id{device.GetDriverID()};
|
||||
profile = Shader::Profile{
|
||||
.supported_spirv = device.SupportedSpirvVersion(),
|
||||
@@ -367,24 +326,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
.support_int16 = device.IsShaderInt16Supported(),
|
||||
.support_int64 = device.IsShaderInt64Supported(),
|
||||
.support_vertex_instance_id = false,
|
||||
.support_float_controls = float_controls_supported,
|
||||
.support_separate_denorm_behavior = float_controls_supported &&
|
||||
.support_float_controls = device.IsKhrShaderFloatControlsSupported(),
|
||||
.support_separate_denorm_behavior =
|
||||
float_control.denormBehaviorIndependence == VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL,
|
||||
.support_separate_rounding_mode = float_controls_supported &&
|
||||
.support_separate_rounding_mode =
|
||||
float_control.roundingModeIndependence == VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL,
|
||||
.support_fp16_denorm_preserve = float_controls_supported &&
|
||||
float_control.shaderDenormPreserveFloat16 != VK_FALSE,
|
||||
.support_fp32_denorm_preserve = float_controls_supported &&
|
||||
float_control.shaderDenormPreserveFloat32 != VK_FALSE,
|
||||
.support_fp16_denorm_flush = float_controls_supported &&
|
||||
float_control.shaderDenormFlushToZeroFloat16 != VK_FALSE,
|
||||
.support_fp32_denorm_flush = float_controls_supported &&
|
||||
float_control.shaderDenormFlushToZeroFloat32 != VK_FALSE,
|
||||
.support_fp16_signed_zero_nan_preserve = float_controls_supported &&
|
||||
.support_fp16_denorm_preserve = float_control.shaderDenormPreserveFloat16 != VK_FALSE,
|
||||
.support_fp32_denorm_preserve = float_control.shaderDenormPreserveFloat32 != VK_FALSE,
|
||||
.support_fp16_denorm_flush = float_control.shaderDenormFlushToZeroFloat16 != VK_FALSE,
|
||||
.support_fp32_denorm_flush = float_control.shaderDenormFlushToZeroFloat32 != VK_FALSE,
|
||||
.support_fp16_signed_zero_nan_preserve =
|
||||
float_control.shaderSignedZeroInfNanPreserveFloat16 != VK_FALSE,
|
||||
.support_fp32_signed_zero_nan_preserve = float_controls_supported &&
|
||||
.support_fp32_signed_zero_nan_preserve =
|
||||
float_control.shaderSignedZeroInfNanPreserveFloat32 != VK_FALSE,
|
||||
.support_fp64_signed_zero_nan_preserve = float_controls_supported &&
|
||||
.support_fp64_signed_zero_nan_preserve =
|
||||
float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE,
|
||||
.support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(),
|
||||
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
|
||||
@@ -440,27 +395,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
.support_conditional_barrier = device.SupportsConditionalBarriers(),
|
||||
};
|
||||
|
||||
profile.warp_stage_support_mask = 0;
|
||||
static constexpr std::array kAllStages{
|
||||
Shader::Stage::VertexA, Shader::Stage::VertexB,
|
||||
Shader::Stage::TessellationControl, Shader::Stage::TessellationEval,
|
||||
Shader::Stage::Geometry, Shader::Stage::Fragment,
|
||||
Shader::Stage::Compute,
|
||||
};
|
||||
for (const auto stage : kAllStages) {
|
||||
const auto vk_stage = StageToVkStage(stage);
|
||||
if (device.SupportsWarpIntrinsics(vk_stage)) {
|
||||
profile.warp_stage_support_mask |= 1u << static_cast<u32>(stage);
|
||||
}
|
||||
}
|
||||
profile.support_vote = profile.warp_stage_support_mask != 0;
|
||||
|
||||
if (!profile.SupportsWarpIntrinsics(Shader::Stage::Fragment)) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Fragment shaders lack subgroup support on this driver; warp intrinsics will be "
|
||||
"approximated and visual artifacts may remain");
|
||||
}
|
||||
|
||||
if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) {
|
||||
LOG_WARNING(Render_Vulkan, "maxVertexInputAttributes is too low: {} < {}",
|
||||
device.GetMaxVertexInputAttributes(), Maxwell::NumVertexAttributes);
|
||||
@@ -470,40 +404,14 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
device.GetMaxVertexInputBindings(), Maxwell::NumVertexArrays);
|
||||
}
|
||||
|
||||
LOG_INFO(Render_Vulkan, "DynamicState setting value: {}", Settings::values.dyna_state.GetValue());
|
||||
|
||||
dynamic_features = {};
|
||||
|
||||
// User granularity enforced in vulkan_device.cpp switch statement:
|
||||
// Level 0: Core Dynamic States only
|
||||
// Level 1: Core + EDS1
|
||||
// Level 2: Core + EDS1 + EDS2 (accumulative)
|
||||
// Level 3: Core + EDS1 + EDS2 + EDS3 (accumulative)
|
||||
// Here we only verify if extensions were successfully loaded by the device
|
||||
|
||||
dynamic_features.has_extended_dynamic_state =
|
||||
device.IsExtExtendedDynamicStateSupported();
|
||||
|
||||
dynamic_features.has_extended_dynamic_state_2 =
|
||||
device.IsExtExtendedDynamicState2Supported();
|
||||
dynamic_features.has_extended_dynamic_state_2_logic_op =
|
||||
device.IsExtExtendedDynamicState2ExtrasSupported();
|
||||
dynamic_features.has_extended_dynamic_state_2_patch_control_points = false;
|
||||
|
||||
dynamic_features.has_extended_dynamic_state_3_blend =
|
||||
device.IsExtExtendedDynamicState3BlendingSupported();
|
||||
dynamic_features.has_dual_source_blend = device.SupportsDualSourceBlend();
|
||||
if (!dynamic_features.has_dual_source_blend) {
|
||||
LOG_WARNING(Render_Vulkan, "Dual-source blending unsupported, disabling dynamic blend");
|
||||
dynamic_features.has_extended_dynamic_state_3_blend = false;
|
||||
}
|
||||
dynamic_features.has_extended_dynamic_state_3_enables =
|
||||
device.IsExtExtendedDynamicState3EnablesSupported();
|
||||
|
||||
// VIDS: Independent toggle (not affected by dyna_state levels)
|
||||
dynamic_features.has_dynamic_vertex_input =
|
||||
device.IsExtVertexInputDynamicStateSupported() &&
|
||||
Settings::values.vertex_input_dynamic_state.GetValue();
|
||||
dynamic_features = DynamicFeatures{
|
||||
.has_extended_dynamic_state = device.IsExtExtendedDynamicStateSupported(),
|
||||
.has_extended_dynamic_state_2 = device.IsExtExtendedDynamicState2Supported(),
|
||||
.has_extended_dynamic_state_2_extra = device.IsExtExtendedDynamicState2ExtrasSupported(),
|
||||
.has_extended_dynamic_state_3_blend = device.IsExtExtendedDynamicState3BlendingSupported(),
|
||||
.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported(),
|
||||
.has_dynamic_vertex_input = device.IsExtVertexInputDynamicStateSupported(),
|
||||
};
|
||||
}
|
||||
|
||||
PipelineCache::~PipelineCache() {
|
||||
@@ -513,13 +421,6 @@ PipelineCache::~PipelineCache() {
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::DrainPendingBuilds() {
|
||||
if (!device.HasBrokenParallelShaderCompiling()) {
|
||||
return;
|
||||
}
|
||||
workers.WaitForRequests();
|
||||
}
|
||||
|
||||
GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() {
|
||||
|
||||
if (!RefreshStages(graphics_key.unique_hashes)) {
|
||||
@@ -550,17 +451,12 @@ ComputePipeline* PipelineCache::CurrentComputePipeline() {
|
||||
.shared_memory_size = qmd.shared_alloc,
|
||||
.workgroup_size{qmd.block_dim_x, qmd.block_dim_y, qmd.block_dim_z},
|
||||
};
|
||||
const auto [pair, inserted]{compute_cache.try_emplace(key)};
|
||||
const auto [pair, is_new]{compute_cache.try_emplace(key)};
|
||||
auto& pipeline{pair->second};
|
||||
if (!pipeline) {
|
||||
auto [slot, should_build] = AcquireComputeBuildSlot(key);
|
||||
if (!should_build) {
|
||||
WaitForBuildCompletion(slot);
|
||||
} else {
|
||||
pipeline = CreateComputePipeline(key, shader);
|
||||
ReleaseComputeBuildSlot(key, slot);
|
||||
}
|
||||
if (!is_new) {
|
||||
return pipeline.get();
|
||||
}
|
||||
pipeline = CreateComputePipeline(key, shader);
|
||||
return pipeline.get();
|
||||
}
|
||||
|
||||
@@ -620,8 +516,8 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
dynamic_features.has_extended_dynamic_state ||
|
||||
(key.state.extended_dynamic_state_2 != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2 ||
|
||||
(key.state.extended_dynamic_state_2_logic_op != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2_logic_op ||
|
||||
(key.state.extended_dynamic_state_2_extra != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2_extra ||
|
||||
(key.state.extended_dynamic_state_3_blend != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_3_blend ||
|
||||
(key.state.extended_dynamic_state_3_enables != 0) !=
|
||||
@@ -676,20 +572,13 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
}
|
||||
|
||||
GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() {
|
||||
const auto [pair, inserted]{graphics_cache.try_emplace(graphics_key)};
|
||||
const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
|
||||
auto& pipeline{pair->second};
|
||||
if (is_new) {
|
||||
pipeline = CreateGraphicsPipeline();
|
||||
}
|
||||
if (!pipeline) {
|
||||
const auto key = pair->first;
|
||||
auto [slot, should_build] = AcquireGraphicsBuildSlot(key);
|
||||
if (!should_build) {
|
||||
WaitForBuildCompletion(slot);
|
||||
} else {
|
||||
pipeline = CreateGraphicsPipeline();
|
||||
ReleaseGraphicsBuildSlot(key, slot);
|
||||
}
|
||||
if (!pipeline) {
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
if (current_pipeline) {
|
||||
current_pipeline->AddTransition(pipeline.get());
|
||||
@@ -712,7 +601,6 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const
|
||||
if (draw_state.index_buffer.count <= 6 || draw_state.vertex_buffer.count <= 6) {
|
||||
return pipeline;
|
||||
}
|
||||
scheduler.KeepAliveTick();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -816,10 +704,6 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
|
||||
}
|
||||
LOG_ERROR(Render_Vulkan, "{}", exception.what());
|
||||
return nullptr;
|
||||
} catch (const vk::Exception& exception) {
|
||||
LOG_ERROR(Render_Vulkan, "Failed to create graphics pipeline 0x{:016x}: {}", key.Hash(),
|
||||
exception.what());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
|
||||
@@ -883,19 +767,6 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
}
|
||||
|
||||
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
|
||||
const VkDriverIdKHR driver_id = device.GetDriverID();
|
||||
const bool needs_shared_mem_clamp =
|
||||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
|
||||
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Compute shader 0x{:016x} requests {}KB shared memory but device max is {}KB - clamping",
|
||||
key.unique_hash,
|
||||
program.shared_memory_size / 1024,
|
||||
max_shared_memory / 1024);
|
||||
program.shared_memory_size = max_shared_memory;
|
||||
}
|
||||
const std::vector<u32> code{EmitSPIRV(profile, program, this->optimize_spirv_output)};
|
||||
device.SaveShader(code);
|
||||
vk::ShaderModule spv_module{BuildShader(device, code)};
|
||||
@@ -911,10 +782,6 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
} catch (const Shader::Exception& exception) {
|
||||
LOG_ERROR(Render_Vulkan, "{}", exception.what());
|
||||
return nullptr;
|
||||
} catch (const vk::Exception& exception) {
|
||||
LOG_ERROR(Render_Vulkan, "Failed to create compute pipeline 0x{:016x}: {}", key.Hash(),
|
||||
exception.what());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PipelineCache::SerializeVulkanPipelineCache(const std::filesystem::path& filename,
|
||||
@@ -1012,68 +879,4 @@ vk::PipelineCache PipelineCache::LoadVulkanPipelineCache(const std::filesystem::
|
||||
}
|
||||
}
|
||||
|
||||
auto PipelineCache::AcquireGraphicsBuildSlot(const GraphicsPipelineCacheKey& key)
|
||||
-> std::pair<InFlightPipelinePtr, bool> {
|
||||
std::scoped_lock lock(graphics_inflight_mutex);
|
||||
auto [it, inserted] = graphics_inflight_builds.try_emplace(key);
|
||||
if (inserted || !it->second) {
|
||||
it->second = std::make_shared<InFlightPipelineBuild>();
|
||||
return {it->second, true};
|
||||
}
|
||||
return {it->second, false};
|
||||
}
|
||||
|
||||
auto PipelineCache::AcquireComputeBuildSlot(const ComputePipelineCacheKey& key)
|
||||
-> std::pair<InFlightPipelinePtr, bool> {
|
||||
std::scoped_lock lock(compute_inflight_mutex);
|
||||
auto [it, inserted] = compute_inflight_builds.try_emplace(key);
|
||||
if (inserted || !it->second) {
|
||||
it->second = std::make_shared<InFlightPipelineBuild>();
|
||||
return {it->second, true};
|
||||
}
|
||||
return {it->second, false};
|
||||
}
|
||||
|
||||
void PipelineCache::ReleaseGraphicsBuildSlot(const GraphicsPipelineCacheKey& key,
|
||||
const InFlightPipelinePtr& slot) {
|
||||
if (!slot) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::scoped_lock slot_lock(slot->mutex);
|
||||
slot->building = false;
|
||||
}
|
||||
slot->cv.notify_all();
|
||||
std::scoped_lock map_lock(graphics_inflight_mutex);
|
||||
auto it = graphics_inflight_builds.find(key);
|
||||
if (it != graphics_inflight_builds.end() && it->second == slot) {
|
||||
graphics_inflight_builds.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::ReleaseComputeBuildSlot(const ComputePipelineCacheKey& key,
|
||||
const InFlightPipelinePtr& slot) {
|
||||
if (!slot) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::scoped_lock slot_lock(slot->mutex);
|
||||
slot->building = false;
|
||||
}
|
||||
slot->cv.notify_all();
|
||||
std::scoped_lock map_lock(compute_inflight_mutex);
|
||||
auto it = compute_inflight_builds.find(key);
|
||||
if (it != compute_inflight_builds.end() && it->second == slot) {
|
||||
compute_inflight_builds.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::WaitForBuildCompletion(const InFlightPipelinePtr& slot) const {
|
||||
if (!slot) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock(slot->mutex);
|
||||
slot->cv.wait(lock, [&] { return !slot->building; });
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -115,17 +113,7 @@ public:
|
||||
void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback);
|
||||
|
||||
void DrainPendingBuilds();
|
||||
|
||||
private:
|
||||
struct InFlightPipelineBuild {
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool building{true};
|
||||
};
|
||||
|
||||
using InFlightPipelinePtr = std::shared_ptr<InFlightPipelineBuild>;
|
||||
|
||||
[[nodiscard]] GraphicsPipeline* CurrentGraphicsPipelineSlowPath();
|
||||
|
||||
[[nodiscard]] GraphicsPipeline* BuiltPipeline(GraphicsPipeline* pipeline) const noexcept;
|
||||
@@ -152,14 +140,6 @@ private:
|
||||
vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename,
|
||||
u32 expected_cache_version);
|
||||
|
||||
std::pair<InFlightPipelinePtr, bool> AcquireGraphicsBuildSlot(
|
||||
const GraphicsPipelineCacheKey& key);
|
||||
std::pair<InFlightPipelinePtr, bool> AcquireComputeBuildSlot(
|
||||
const ComputePipelineCacheKey& key);
|
||||
void ReleaseGraphicsBuildSlot(const GraphicsPipelineCacheKey& key, const InFlightPipelinePtr& slot);
|
||||
void ReleaseComputeBuildSlot(const ComputePipelineCacheKey& key, const InFlightPipelinePtr& slot);
|
||||
void WaitForBuildCompletion(const InFlightPipelinePtr& slot) const;
|
||||
|
||||
const Device& device;
|
||||
Scheduler& scheduler;
|
||||
DescriptorPool& descriptor_pool;
|
||||
@@ -178,11 +158,6 @@ private:
|
||||
std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<ComputePipeline>> compute_cache;
|
||||
std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
|
||||
|
||||
std::mutex graphics_inflight_mutex;
|
||||
std::unordered_map<GraphicsPipelineCacheKey, InFlightPipelinePtr> graphics_inflight_builds;
|
||||
std::mutex compute_inflight_mutex;
|
||||
std::unordered_map<ComputePipelineCacheKey, InFlightPipelinePtr> compute_inflight_builds;
|
||||
|
||||
ShaderPools main_pools;
|
||||
|
||||
Shader::Profile profile;
|
||||
|
||||
@@ -42,8 +42,7 @@ public:
|
||||
static constexpr size_t BANK_SIZE = 256;
|
||||
static constexpr size_t QUERY_SIZE = 8;
|
||||
explicit SamplesQueryBank(const Device& device_, size_t index_)
|
||||
: BankBase(BANK_SIZE), device{device_}, index{index_},
|
||||
supports_host_query_reset{device_.SupportsHostQueryReset()} {
|
||||
: BankBase(BANK_SIZE), device{device_}, index{index_} {
|
||||
const auto& dev = device.GetLogical();
|
||||
query_pool = dev.CreateQueryPool({
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
||||
@@ -61,10 +60,8 @@ public:
|
||||
void Reset() override {
|
||||
ASSERT(references == 0);
|
||||
VideoCommon::BankBase::Reset();
|
||||
if (supports_host_query_reset) {
|
||||
const auto& dev = device.GetLogical();
|
||||
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
|
||||
}
|
||||
const auto& dev = device.GetLogical();
|
||||
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
|
||||
host_results.fill(0ULL);
|
||||
next_bank = 0;
|
||||
}
|
||||
@@ -102,7 +99,6 @@ public:
|
||||
private:
|
||||
const Device& device;
|
||||
const size_t index;
|
||||
const bool supports_host_query_reset;
|
||||
vk::QueryPool query_pool;
|
||||
std::array<u64, BANK_SIZE> host_results;
|
||||
};
|
||||
@@ -1204,24 +1200,21 @@ struct QueryCacheRuntimeImpl {
|
||||
hcr_setup.pNext = nullptr;
|
||||
hcr_setup.flags = 0;
|
||||
|
||||
if (device.IsExtConditionalRendering()) {
|
||||
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
|
||||
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
|
||||
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
|
||||
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
|
||||
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = sizeof(u32),
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
};
|
||||
hcr_resolve_buffer =
|
||||
memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
|
||||
}
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = sizeof(u32),
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
};
|
||||
hcr_resolve_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface* rasterizer;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user