Merge pull request #111 from raphaelthegreat/main

core: Rewrite videoout library and bringup new vulkan backend
This commit is contained in:
georgemoralis 2024-04-29 19:33:56 +03:00 committed by GitHub
commit d496fab492
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
96 changed files with 5731 additions and 2172 deletions

15
.gitmodules vendored
View file

@ -46,3 +46,18 @@
[submodule "externals/fmt"]
path = externals/fmt
url = https://github.com/shadps4-emu/ext-fmt.git
[submodule "externals/vulkan-headers"]
path = externals/vulkan-headers
url = https://github.com/KhronosGroup/Vulkan-Headers
[submodule "externals/vma"]
path = externals/vma
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
[submodule "externals/glslang"]
path = externals/glslang
url = https://github.com/KhronosGroup/glslang
[submodule "externals/robin-map"]
path = externals/robin-map
url = https://github.com/Tessil/robin-map
[submodule "externals/boost"]
path = externals/boost
url = https://github.com/raphaelthegreat/ext-boost

View file

@ -65,15 +65,9 @@ if (CLANG_FORMAT)
set(SRCS ${PROJECT_SOURCE_DIR}/src)
set(CCOMMENT "Running clang format against all the .h and .cpp files in src/")
if (WIN32)
if(MINGW)
add_custom_target(clang-format
COMMAND find `cygpath -u ${SRCS}` -iname *.h -o -iname *.cpp -o -iname *.mm | xargs `cygpath -u ${CLANG_FORMAT}` -i
COMMENT ${CCOMMENT})
else()
add_custom_target(clang-format
COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h,*.mm -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}"
COMMENT ${CCOMMENT})
endif()
add_custom_target(clang-format
COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h,*.mm -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}"
COMMENT ${CCOMMENT})
else()
add_custom_target(clang-format
COMMAND find ${SRCS} -iname *.h -o -iname *.cpp -o -iname *.mm | xargs ${CLANG_FORMAT} -i
@ -155,6 +149,13 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/system/userservice.h
)
set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h
src/core/libraries/videoout/driver.cpp
src/core/libraries/videoout/driver.h
src/core/libraries/videoout/video_out.cpp
src/core/libraries/videoout/video_out.h
)
set(LIBC_SOURCES src/core/libraries/libc/libc.cpp
src/core/libraries/libc/libc.h
src/core/libraries/libc/printf.h
@ -185,6 +186,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/logging/text_formatter.cpp
src/common/logging/text_formatter.h
src/common/logging/types.h
src/common/alignment.h
src/common/assert.cpp
src/common/assert.h
src/common/bounded_threadsafe_queue.h
@ -197,6 +199,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/discord.cpp
src/common/discord.h
src/common/endian.h
src/common/enum.h
src/common/io_file.cpp
src/common/io_file.h
src/common/error.cpp
@ -205,6 +208,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/native_clock.h
src/common/path_util.cpp
src/common/path_util.h
src/common/polyfill_thread.h
src/common/rdtsc.cpp
src/common/rdtsc.h
src/common/singleton.h
@ -249,6 +253,7 @@ set(CORE src/core/aerolib/stubs.cpp
${SYSTEM_LIBS}
${LIBC_SOURCES}
${PAD_LIB}
${VIDEOOUT_LIB}
src/core/linker.cpp
src/core/linker.h
src/core/tls.cpp
@ -257,21 +262,39 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/virtual_memory.h
)
set(VIDEO_CORE src/core/PS4/HLE/Graphics/video_out.cpp
src/core/PS4/HLE/Graphics/video_out.h
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h
src/core/PS4/HLE/Graphics/graphics_ctx.h
src/core/PS4/GPU/gpu_memory.cpp
src/core/PS4/GPU/gpu_memory.h
src/core/PS4/GPU/video_out_buffer.cpp
src/core/PS4/GPU/video_out_buffer.h
src/core/PS4/HLE/Graphics/graphics_render.cpp
src/core/PS4/HLE/Graphics/graphics_render.h
src/core/PS4/GPU/tile_manager.cpp
src/core/PS4/GPU/tile_manager.h
src/vulkan_util.cpp
src/vulkan_util.h
set(VIDEO_CORE src/video_core/pixel_format.h
src/video_core/renderer_vulkan/renderer_vulkan.cpp
src/video_core/renderer_vulkan/renderer_vulkan.h
src/video_core/renderer_vulkan/vk_common.cpp
src/video_core/renderer_vulkan/vk_common.h
src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp
src/video_core/renderer_vulkan/vk_descriptor_update_queue.h
src/video_core/renderer_vulkan/vk_instance.cpp
src/video_core/renderer_vulkan/vk_instance.h
src/video_core/renderer_vulkan/vk_master_semaphore.cpp
src/video_core/renderer_vulkan/vk_master_semaphore.h
src/video_core/renderer_vulkan/vk_platform.cpp
src/video_core/renderer_vulkan/vk_platform.h
src/video_core/renderer_vulkan/vk_resource_pool.cpp
src/video_core/renderer_vulkan/vk_resource_pool.h
src/video_core/renderer_vulkan/vk_scheduler.cpp
src/video_core/renderer_vulkan/vk_scheduler.h
src/video_core/renderer_vulkan/vk_shader_util.cpp
src/video_core/renderer_vulkan/vk_shader_util.h
src/video_core/renderer_vulkan/vk_stream_buffer.cpp
src/video_core/renderer_vulkan/vk_stream_buffer.h
src/video_core/renderer_vulkan/vk_swapchain.cpp
src/video_core/renderer_vulkan/vk_swapchain.h
src/video_core/texture_cache/image.cpp
src/video_core/texture_cache/image.h
src/video_core/texture_cache/image_view.cpp
src/video_core/texture_cache/image_view.h
src/video_core/texture_cache/slot_vector.h
src/video_core/texture_cache/texture_cache.cpp
src/video_core/texture_cache/texture_cache.h
src/video_core/texture_cache/tile_manager.cpp
src/video_core/texture_cache/tile_manager.h
src/video_core/texture_cache/types.h
)
set(INPUT src/input/controller.cpp
@ -282,7 +305,8 @@ set(INPUT src/input/controller.cpp
if(ENABLE_QT_GUI)
qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
set(QT_GUI
set(QT_GUI
src/qt_gui/main_window_ui.h
src/qt_gui/main_window.cpp
src/qt_gui/main_window.h
@ -318,8 +342,8 @@ qt_add_executable(shadps4
${COMMON}
${CORE}
${VIDEO_CORE}
src/emulator.cpp
src/emulator.h
src/sdl_window.h
src/sdl_window.cpp
)
else()
add_executable(shadps4
@ -329,15 +353,15 @@ add_executable(shadps4
${CORE}
${VIDEO_CORE}
src/main.cpp
src/emulator.cpp
src/emulator.h
src/sdl_window.h
src/sdl_window.cpp
)
endif()
create_target_directory_groups(shadps4)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 SDL3-shared)
target_link_libraries(shadps4 PRIVATE discord-rpc vulkan-1 xxhash Zydis)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map)
target_link_libraries(shadps4 PRIVATE discord-rpc boost vma vulkan-headers xxhash Zydis SPIRV glslang SDL3-shared)
if(NOT ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE SDL3-shared)
@ -358,6 +382,8 @@ if (WIN32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
add_definitions(-D_TIMESPEC_DEFINED) #needed for conflicts with time.h of windows.h
# Target Windows 10 RS5
add_definitions(-DNTDDI_VERSION=0x0A000006 -D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00)
endif()
if(WIN32)

121
LICENSES/CC0-1.0.txt Normal file
View file

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View file

@ -6,6 +6,13 @@ if (MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
endif()
# Boost
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "")
set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "")
add_library(boost INTERFACE)
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
# fmtlib
add_subdirectory(fmt EXCLUDE_FROM_ALL)
@ -35,3 +42,24 @@ add_subdirectory(zlib-ng)
# SDL3
add_subdirectory(sdl3 EXCLUDE_FROM_ALL)
# vulkan-headers
add_library(vulkan-headers INTERFACE)
target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include)
# VMA
add_library(vma INTERFACE)
target_include_directories(vma SYSTEM INTERFACE ./vma/include)
# glslang
set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "")
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "")
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "")
set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "")
set(ENABLE_OPT OFF CACHE BOOL "")
add_subdirectory(glslang)
# Robin-map
add_subdirectory(robin-map)

1
externals/boost vendored Submodule

@ -0,0 +1 @@
Subproject commit dfb313f8357b8f6601fa7420be1a39a51ba86f77

1
externals/glslang vendored Submodule

@ -0,0 +1 @@
Subproject commit 2db79056b418587e61122a8a820cd832b2abdf83

1
externals/robin-map vendored Submodule

@ -0,0 +1 @@
Subproject commit 048eb1442a76ab81ecb3c4ab0495f15a68e54a6d

1
externals/vma vendored Submodule

@ -0,0 +1 @@
Subproject commit 5677097bafb8477097c6e3354ce68b7a44fd01a4

1
externals/vulkan-headers vendored Submodule

@ -0,0 +1 @@
Subproject commit cfebfc96b2b0bce93da7d12f2c14cc01793ae25c

31
src/common/alignment.h Normal file
View file

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
// SPDX-License-Identifier: CC0-1.0
#pragma once
#include <cstddef>
#include <type_traits>
namespace Common {
template <typename T>
[[nodiscard]] constexpr T alignUp(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
auto mod{static_cast<T>(value % size)};
value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size);
}
template <typename T>
[[nodiscard]] constexpr T alignDown(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool is16KBAligned(T value) {
return (value & 0x3FFF) == 0;
}
} // namespace Common

View file

@ -8,7 +8,7 @@
#include <cstddef>
#include <memory>
#include <mutex>
#include <new>
#include "common/polyfill_thread.h"
namespace Common {
@ -122,7 +122,7 @@ private:
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer_cv_mutex};
consumer_cv.wait(lock, stop_token, [this, read_index] {
Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] {
return read_index != m_write_index.load(std::memory_order::acquire);
});
if (stop_token.stop_requested()) {

View file

@ -12,6 +12,7 @@ namespace Config {
bool isNeo = false;
u32 screenWidth = 1280;
u32 screenHeight = 720;
u32 gpuId = 0; // Vulkan physical device no
std::string logFilter;
std::string logType = "sync";
bool isDebugDump = false;
@ -32,6 +33,10 @@ u32 getScreenHeight() {
return screenHeight;
}
u32 getGpuId() {
return gpuId;
}
std::string getLogFilter() {
return logFilter;
}
@ -72,12 +77,13 @@ void load(const std::filesystem::path& path) {
}
}
if (data.contains("GPU")) {
auto generalResult = toml::expect<toml::value>(data.at("GPU"));
if (generalResult.is_ok()) {
auto general = generalResult.unwrap();
auto gpuResult = toml::expect<toml::value>(data.at("GPU"));
if (gpuResult.is_ok()) {
auto gpu = gpuResult.unwrap();
screenWidth = toml::find_or<toml::integer>(general, "screenWidth", false);
screenHeight = toml::find_or<toml::integer>(general, "screenHeight", false);
screenWidth = toml::find_or<toml::integer>(gpu, "screenWidth", screenWidth);
screenHeight = toml::find_or<toml::integer>(gpu, "screenHeight", screenHeight);
gpuId = toml::find_or<toml::integer>(gpu, "gpuId", 0);
}
}
if (data.contains("Debug")) {
@ -119,6 +125,7 @@ void save(const std::filesystem::path& path) {
data["General"]["isPS4Pro"] = isNeo;
data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType;
data["GPU"]["gpuId"] = gpuId;
data["GPU"]["screenWidth"] = screenWidth;
data["GPU"]["screenHeight"] = screenHeight;
data["Debug"]["DebugDump"] = isDebugDump;

View file

@ -16,6 +16,7 @@ std::string getLogType();
u32 getScreenWidth();
u32 getScreenHeight();
u32 getGpuId();
bool debugDump();
bool isLleLibc();

60
src/common/enum.h Normal file
View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
} \
constexpr type& operator|=(type& a, type b) noexcept { \
a = a | b; \
return a; \
} \
constexpr type& operator&=(type& a, type b) noexcept { \
a = a & b; \
return a; \
} \
constexpr type& operator^=(type& a, type b) noexcept { \
a = a ^ b; \
return a; \
} \
constexpr type& operator<<=(type& a, type b) noexcept { \
a = a << b; \
return a; \
} \
constexpr type& operator>>=(type& a, type b) noexcept { \
a = a >> b; \
return a; \
} \
[[nodiscard]] constexpr type operator~(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}

View file

@ -0,0 +1,375 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
//
// TODO: remove this file when jthread is supported by all compilation targets
//
#pragma once
#include <version>
#ifdef __cpp_lib_jthread
#include <chrono>
#include <condition_variable>
#include <stop_token>
#include <thread>
#include <utility>
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
cv.wait(lk, token, std::forward<Pred>(pred));
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
std::condition_variable_any cv;
std::mutex m;
// Perform the timed wait.
std::unique_lock lk{m};
return !cv.wait_for(lk, token, rel_time, [&] { return token.stop_requested(); });
}
} // namespace Common
#else
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <type_traits>
#include <utility>
namespace std {
namespace polyfill {
using stop_state_callback = size_t;
class stop_state {
public:
stop_state() = default;
~stop_state() = default;
bool request_stop() {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Already set, nothing to do.
return false;
}
// Mark stop requested.
m_stop_requested = true;
while (!m_callbacks.empty()) {
// Get an iterator to the first element.
const auto it = m_callbacks.begin();
// Move the callback function out of the map.
function<void()> f;
swap(it->second, f);
// Erase the now-empty map element.
m_callbacks.erase(it);
// Run the callback.
if (f) {
f();
}
}
return true;
}
bool stop_requested() const {
unique_lock lk{m_lock};
return m_stop_requested;
}
stop_state_callback insert_callback(function<void()> f) {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Stop already requested. Don't insert anything,
// just run the callback synchronously.
if (f) {
f();
}
return 0;
}
// Insert the callback.
stop_state_callback ret = ++m_next_callback;
m_callbacks.emplace(ret, std::move(f));
return ret;
}
void remove_callback(stop_state_callback cb) {
unique_lock lk{m_lock};
m_callbacks.erase(cb);
}
private:
mutable recursive_mutex m_lock;
map<stop_state_callback, function<void()>> m_callbacks;
stop_state_callback m_next_callback{0};
bool m_stop_requested{false};
};
} // namespace polyfill
class stop_token;
class stop_source;
struct nostopstate_t {
explicit nostopstate_t() = default;
};
inline constexpr nostopstate_t nostopstate{};
template <class Callback>
class stop_callback;
class stop_token {
public:
stop_token() noexcept = default;
stop_token(const stop_token&) noexcept = default;
stop_token(stop_token&&) noexcept = default;
stop_token& operator=(const stop_token&) noexcept = default;
stop_token& operator=(stop_token&&) noexcept = default;
~stop_token() = default;
void swap(stop_token& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
private:
friend class stop_source;
template <typename Callback>
friend class stop_callback;
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
class stop_source {
public:
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
explicit stop_source(nostopstate_t) noexcept {}
stop_source(const stop_source&) noexcept = default;
stop_source(stop_source&&) noexcept = default;
stop_source& operator=(const stop_source&) noexcept = default;
stop_source& operator=(stop_source&&) noexcept = default;
~stop_source() = default;
void swap(stop_source& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] stop_token get_token() const noexcept {
return stop_token(m_stop_state);
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
bool request_stop() noexcept {
return m_stop_state && m_stop_state->request_stop();
}
private:
friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
: m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
template <typename Callback>
class stop_callback {
static_assert(is_nothrow_destructible_v<Callback>);
static_assert(is_invocable_v<Callback>);
public:
using callback_type = Callback;
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(const stop_token& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(std::move(st.m_stop_state)) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
~stop_callback() {
if (m_stop_state && m_callback) {
m_stop_state->remove_callback(m_callback);
}
}
stop_callback(const stop_callback&) = delete;
stop_callback(stop_callback&&) = delete;
stop_callback& operator=(const stop_callback&) = delete;
stop_callback& operator=(stop_callback&&) = delete;
private:
shared_ptr<polyfill::stop_state> m_stop_state;
polyfill::stop_state_callback m_callback;
};
template <typename Callback>
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
class jthread {
public:
using id = thread::id;
using native_handle_type = thread::native_handle_type;
jthread() noexcept = default;
template <typename F, typename... Args,
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
~jthread() {
if (joinable()) {
request_stop();
join();
}
}
jthread(const jthread&) = delete;
jthread(jthread&&) noexcept = default;
jthread& operator=(const jthread&) = delete;
jthread& operator=(jthread&& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
return *this;
}
void swap(jthread& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool joinable() const noexcept {
return m_thread.joinable();
}
void join() {
m_thread.join();
}
void detach() {
m_thread.detach();
m_stop_state.reset();
}
[[nodiscard]] id get_id() const noexcept {
return m_thread.get_id();
}
[[nodiscard]] native_handle_type native_handle() {
return m_thread.native_handle();
}
[[nodiscard]] stop_source get_stop_source() noexcept {
return stop_source(m_stop_state);
}
[[nodiscard]] stop_token get_stop_token() const noexcept {
return stop_source(m_stop_state).get_token();
}
bool request_stop() noexcept {
return get_stop_source().request_stop();
}
[[nodiscard]] static unsigned int hardware_concurrency() noexcept {
return thread::hardware_concurrency();
}
private:
template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
} else {
return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
shared_ptr<polyfill::stop_state> m_stop_state;
thread m_thread;
};
} // namespace std
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
if (token.stop_requested()) {
return;
}
std::stop_callback callback(token, [&] {
{ std::scoped_lock lk2{*lk.mutex()}; }
cv.notify_all();
});
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
if (token.stop_requested()) {
return false;
}
bool stop_requested = false;
std::condition_variable cv;
std::mutex m;
std::stop_callback cb(token, [&] {
// Wake up the waiting thread.
{
std::scoped_lock lk{m};
stop_requested = true;
}
cv.notify_one();
});
// Perform the timed wait.
std::unique_lock lk{m};
return !cv.wait_for(lk, rel_time, [&] { return stop_requested; });
}
} // namespace Common
#endif

View file

@ -22,6 +22,9 @@ using f64 = double;
using u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
using VAddr = uintptr_t;
using PAddr = uintptr_t;
#define PS4_SYSV_ABI __attribute__((sysv_abi))
// UDLs for memory size values

View file

@ -1,203 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <xxh3.h>
#include "common/singleton.h"
#include "core/PS4/GPU/gpu_memory.h"
void* GPU::memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx,
void* todo /*CommandBuffer?*/, u64 virtual_addr, u64 size,
const GPUObject& info) {
auto* gpumemory = Common::Singleton<GPUMemory>::Instance();
return gpumemory->memoryCreateObj(submit_id, ctx, nullptr, &virtual_addr, &size, 1, info);
}
void GPU::memorySetAllocArea(u64 virtual_addr, u64 size) {
auto* gpumemory = Common::Singleton<GPUMemory>::Instance();
std::scoped_lock lock{gpumemory->m_mutex};
MemoryHeap h;
h.allocated_virtual_addr = virtual_addr;
h.allocated_size = size;
gpumemory->m_heaps.push_back(h);
}
u64 GPU::calculate_hash(const u8* buf, u64 size) {
return (size > 0 && buf != nullptr ? XXH3_64bits(buf, size) : 0);
}
bool GPU::vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx,
HLE::Libs::Graphics::VulkanMemory* mem) {
static std::atomic_uint64_t unique_id = 0;
VkPhysicalDeviceMemoryProperties memory_properties{};
vkGetPhysicalDeviceMemoryProperties(ctx->m_physical_device, &memory_properties);
u32 index = 0;
for (; index < memory_properties.memoryTypeCount; index++) {
if ((mem->requirements.memoryTypeBits & (static_cast<uint32_t>(1) << index)) != 0 &&
(memory_properties.memoryTypes[index].propertyFlags & mem->property) == mem->property) {
break;
}
}
mem->type = index;
mem->offset = 0;
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = nullptr;
alloc_info.allocationSize = mem->requirements.size;
alloc_info.memoryTypeIndex = index;
mem->unique_id = ++unique_id;
auto result = vkAllocateMemory(ctx->m_device, &alloc_info, nullptr, &mem->memory);
if (result == VK_SUCCESS) {
return true;
}
return false;
}
void GPU::flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx) {
auto* gpumemory = Common::Singleton<GPUMemory>::Instance();
gpumemory->flushAllHeaps(ctx);
}
int GPU::GPUMemory::getHeapId(u64 virtual_addr, u64 size) {
int index = 0;
for (const auto& heap : m_heaps) {
if ((virtual_addr >= heap.allocated_virtual_addr &&
virtual_addr < heap.allocated_virtual_addr + heap.allocated_size) ||
((virtual_addr + size - 1) >= heap.allocated_virtual_addr &&
(virtual_addr + size - 1) < heap.allocated_virtual_addr + heap.allocated_size)) {
return index;
}
index++;
}
return -1;
}
void* GPU::GPUMemory::memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx,
void* todo, const u64* virtual_addr, const u64* size,
int virtual_addr_num, const GPUObject& info) {
auto* gpumemory = Common::Singleton<GPUMemory>::Instance();
std::scoped_lock lock{gpumemory->m_mutex};
int heap_id = gpumemory->getHeapId(virtual_addr[0], size[0]);
if (heap_id < 0) {
return nullptr;
}
auto& heap = m_heaps[heap_id];
ObjInfo objInfo = {};
// copy parameters from info to obj
for (int i = 0; i < 8; i++) {
objInfo.obj_params[i] = info.obj_params[i];
}
objInfo.gpu_object.objectType = info.objectType;
objInfo.gpu_object.obj = nullptr;
for (int h = 0; h < virtual_addr_num; h++) {
if (info.check_hash) {
objInfo.hash[h] =
GPU::calculate_hash(reinterpret_cast<const u8*>(virtual_addr[h]), size[h]);
} else {
objInfo.hash[h] = 0;
}
}
objInfo.submit_id = submit_id;
objInfo.check_hash = info.check_hash;
objInfo.gpu_object.obj = info.getCreateFunc()(ctx, objInfo.obj_params, virtual_addr, size,
virtual_addr_num, &objInfo.mem);
objInfo.update_func = info.getUpdateFunc();
int index = static_cast<int>(heap.objects.size());
HeapObject hobj{};
hobj.block = createHeapBlock(virtual_addr, size, virtual_addr_num, heap_id, index);
hobj.info = objInfo;
hobj.free = false;
heap.objects.push_back(hobj);
return objInfo.gpu_object.obj;
}
GPU::HeapBlock GPU::GPUMemory::createHeapBlock(const u64* virtual_addr, const u64* size,
int virtual_addr_num, int heap_id, int obj_id) {
auto& heap = m_heaps[heap_id];
GPU::HeapBlock heapBlock{};
heapBlock.virtual_addr_num = virtual_addr_num;
for (int vi = 0; vi < virtual_addr_num; vi++) {
heapBlock.virtual_addr[vi] = virtual_addr[vi];
heapBlock.size[vi] = size[vi];
}
return heapBlock;
}
void GPU::GPUMemory::update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id,
int obj_id) {
auto& heap = m_heaps[heap_id];
auto& heapObj = heap.objects[obj_id];
auto& objInfo = heapObj.info;
bool need_update = false;
if (submit_id > objInfo.submit_id) {
uint64_t hash[3] = {};
for (int i = 0; i < heapObj.block.virtual_addr_num; i++) {
if (objInfo.check_hash) {
hash[i] = GPU::calculate_hash(
reinterpret_cast<const uint8_t*>(heapObj.block.virtual_addr[i]),
heapObj.block.size[i]);
} else {
hash[i] = 0;
}
}
for (int i = 0; i < heapObj.block.virtual_addr_num; i++) {
if (objInfo.hash[i] != hash[i]) {
need_update = true;
objInfo.hash[i] = hash[i];
}
}
if (submit_id != UINT64_MAX) {
objInfo.submit_id = submit_id;
}
}
if (need_update) {
objInfo.update_func(ctx, objInfo.obj_params, objInfo.gpu_object.obj,
heapObj.block.virtual_addr, heapObj.block.size,
heapObj.block.virtual_addr_num);
}
}
void GPU::GPUMemory::flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx) {
std::scoped_lock lock{m_mutex};
int heap_id = 0;
for (auto& heap : m_heaps) {
int index = 0;
for (auto& heapObj : heap.objects) {
if (!heapObj.free) {
update(UINT64_MAX, ctx, heap_id, index);
}
index++;
}
heap_id++;
}
}

View file

@ -1,94 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <vector>
#include "common/types.h"
#include "core/PS4/HLE/Graphics/graphics_ctx.h"
namespace GPU {
class GPUObject;
enum class MemoryMode : u32 { NoAccess = 0, Read = 1, Write = 2, ReadWrite = 3 };
enum class MemoryObjectType : u64 { InvalidObj, VideoOutBufferObj };
struct GpuMemoryObject {
MemoryObjectType objectType = MemoryObjectType::InvalidObj;
void* obj = nullptr;
};
struct HeapBlock {
u64 virtual_addr[3] = {};
u64 size[3] = {};
int virtual_addr_num = 0;
};
class GPUObject {
public:
GPUObject() = default;
virtual ~GPUObject() = default;
u64 obj_params[8] = {};
bool check_hash = false;
bool isReadOnly = false;
MemoryObjectType objectType = MemoryObjectType::InvalidObj;
using create_func_t = void* (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params,
const u64* virtual_addr, const u64* size, int virtual_addr_num,
HLE::Libs::Graphics::VulkanMemory* mem);
using update_func_t = void (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params,
void* obj, const u64* virtual_addr, const u64* size,
int virtual_addr_num);
virtual create_func_t getCreateFunc() const = 0;
virtual update_func_t getUpdateFunc() const = 0;
};
struct ObjInfo {
u64 obj_params[8] = {};
GpuMemoryObject gpu_object;
u64 hash[3] = {};
u64 submit_id = 0;
bool check_hash = false;
HLE::Libs::Graphics::VulkanMemory mem;
GPU::GPUObject::update_func_t update_func = nullptr;
};
struct HeapObject {
HeapBlock block;
ObjInfo info;
bool free = true;
};
struct MemoryHeap {
u64 allocated_virtual_addr = 0;
u64 allocated_size = 0;
std::vector<HeapObject> objects;
};
class GPUMemory {
public:
GPUMemory() {}
virtual ~GPUMemory() {}
int getHeapId(u64 vaddr, u64 size);
std::mutex m_mutex;
std::vector<MemoryHeap> m_heaps;
void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx,
/*CommandBuffer* buffer*/ void* todo, const u64* virtual_addr,
const u64* size, int virtual_addr_num, const GPUObject& info);
HeapBlock createHeapBlock(const u64* virtual_addr, const u64* size, int virtual_addr_num,
int heap_id, int obj_id);
void update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id, int obj_id);
void flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx);
};
void memorySetAllocArea(u64 virtual_addr, u64 size);
void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx,
/*CommandBuffer* buffer*/ void* todo, u64 virtual_addr, u64 size,
const GPUObject& info);
u64 calculate_hash(const u8* buf, u64 size);
bool vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx,
HLE::Libs::Graphics::VulkanMemory* mem);
void flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx);
} // namespace GPU

View file

@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace GPU {
void convertTileToLinear(void* dst, const void* src, u32 width, u32 height, bool neo);
} // namespace GPU

View file

@ -1,146 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/PS4/GPU/tile_manager.h"
#include "core/PS4/GPU/video_out_buffer.h"
#include "vulkan_util.h"
static void update_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, void* obj,
const u64* virtual_addr, const u64* size, int virtual_addr_num) {
auto pitch = params[GPU::VideoOutBufferObj::PITCH_PARAM];
bool tiled = (params[GPU::VideoOutBufferObj::IS_TILE_PARAM] != 0);
bool neo = (params[GPU::VideoOutBufferObj::IS_NEO_PARAM] != 0);
auto width = params[GPU::VideoOutBufferObj::WIDTH_PARAM];
auto height = params[GPU::VideoOutBufferObj::HEIGHT_PARAM];
auto* vk_obj = static_cast<HLE::Libs::Graphics::VideoOutVulkanImage*>(obj);
vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED;
if (tiled) {
std::vector<u8> tempbuff(*size);
GPU::convertTileToLinear(tempbuff.data(), reinterpret_cast<void*>(*virtual_addr), width,
height, neo);
Graphics::Vulkan::vulkanFillImage(
ctx, vk_obj, tempbuff.data(), *size, pitch,
static_cast<u64>(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL));
} else {
Graphics::Vulkan::vulkanFillImage(
ctx, vk_obj, reinterpret_cast<void*>(*virtual_addr), *size, pitch,
static_cast<u64>(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL));
}
}
static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params,
const u64* virtual_addr, const u64* size, int virtual_addr_num,
HLE::Libs::Graphics::VulkanMemory* mem) {
auto pixel_format = params[GPU::VideoOutBufferObj::PIXEL_FORMAT_PARAM];
auto width = params[GPU::VideoOutBufferObj::WIDTH_PARAM];
auto height = params[GPU::VideoOutBufferObj::HEIGHT_PARAM];
auto* vk_obj = new HLE::Libs::Graphics::VideoOutVulkanImage;
VkFormat vk_format = VK_FORMAT_UNDEFINED;
switch (pixel_format) {
case static_cast<uint64_t>(GPU::VideoOutBufferFormat::R8G8B8A8Srgb):
vk_format = VK_FORMAT_R8G8B8A8_SRGB;
break;
case static_cast<uint64_t>(GPU::VideoOutBufferFormat::B8G8R8A8Srgb):
vk_format = VK_FORMAT_B8G8R8A8_SRGB;
break;
default:
UNREACHABLE_MSG("Unknown pixelFormat = {}", pixel_format);
}
vk_obj->extent.width = width;
vk_obj->extent.height = height;
vk_obj->format = vk_format;
vk_obj->image = nullptr;
vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED;
for (auto& view : vk_obj->image_view) {
view = nullptr;
}
VkImageCreateInfo image_info{};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = nullptr;
image_info.flags = 0;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent.width = vk_obj->extent.width;
image_info.extent.height = vk_obj->extent.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.format = vk_obj->format;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = vk_obj->layout;
image_info.usage = static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT) |
VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
vkCreateImage(ctx->m_device, &image_info, nullptr, &vk_obj->image);
if (vk_obj->image == nullptr) {
UNREACHABLE_MSG("vk_obj->image is null");
}
vkGetImageMemoryRequirements(ctx->m_device, vk_obj->image, &mem->requirements);
mem->property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
bool allocated = GPU::vulkanAllocateMemory(ctx, mem);
if (!allocated) {
UNREACHABLE_MSG("Can't allocate vulkan memory");
}
vkBindImageMemory(ctx->m_device, vk_obj->image, mem->memory, mem->offset);
vk_obj->memory = *mem;
LOG_INFO(Lib_VideoOut, "videoOutBuffer create object width = {}, height = {}, size = {}", width,
height, *size);
update_func(ctx, params, vk_obj, virtual_addr, size, virtual_addr_num);
VkImageViewCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.image = vk_obj->image;
create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
create_info.format = vk_obj->format;
create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
create_info.subresourceRange.baseArrayLayer = 0;
create_info.subresourceRange.baseMipLevel = 0;
create_info.subresourceRange.layerCount = 1;
create_info.subresourceRange.levelCount = 1;
vkCreateImageView(ctx->m_device, &create_info, nullptr,
&vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT]);
if (vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT] == nullptr) {
UNREACHABLE_MSG("vk_obj->image_view is null");
}
return vk_obj;
}
GPU::GPUObject::create_func_t GPU::VideoOutBufferObj::getCreateFunc() const {
return create_func;
}
GPU::GPUObject::update_func_t GPU::VideoOutBufferObj::getUpdateFunc() const {
return update_func;
}

View file

@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/PS4/GPU/gpu_memory.h"
namespace GPU {
enum class VideoOutBufferFormat : u64 {
Unknown,
R8G8B8A8Srgb,
B8G8R8A8Srgb,
};
class VideoOutBufferObj : public GPUObject {
public:
static constexpr int PIXEL_FORMAT_PARAM = 0;
static constexpr int WIDTH_PARAM = 1;
static constexpr int HEIGHT_PARAM = 2;
static constexpr int IS_TILE_PARAM = 3;
static constexpr int IS_NEO_PARAM = 4;
static constexpr int PITCH_PARAM = 5;
explicit VideoOutBufferObj(VideoOutBufferFormat pixel_format, u32 width, u32 height,
bool is_tiled, bool is_neo, u32 pitch) {
obj_params[PIXEL_FORMAT_PARAM] = static_cast<uint64_t>(pixel_format);
obj_params[WIDTH_PARAM] = width;
obj_params[HEIGHT_PARAM] = height;
obj_params[IS_TILE_PARAM] = is_tiled ? 1 : 0;
obj_params[IS_NEO_PARAM] = is_neo ? 1 : 0;
obj_params[PITCH_PARAM] = pitch;
check_hash = true;
objectType = GPU::MemoryObjectType::VideoOutBufferObj;
}
create_func_t getCreateFunc() const override;
update_func_t getUpdateFunc() const override;
};
} // namespace GPU

View file

@ -1,149 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "core/PS4/HLE/Graphics/Objects/video_out_ctx.h"
#include "core/libraries/kernel/time_management.h"
namespace HLE::Graphics::Objects {
void VideoOutCtx::Init(u32 width, u32 height) {
m_video_out_ctx.m_resolution.fullWidth = width;
m_video_out_ctx.m_resolution.fullHeight = height;
m_video_out_ctx.m_resolution.paneWidth = width;
m_video_out_ctx.m_resolution.paneHeight = height;
}
int VideoOutCtx::Open() {
std::scoped_lock lock{m_mutex};
int handle = -1;
if (!m_video_out_ctx.isOpened) {
handle = 1; // positive return , should be more than 1 ?
}
m_video_out_ctx.isOpened = true;
m_video_out_ctx.m_flip_status = SceVideoOutFlipStatus();
m_video_out_ctx.m_flip_status.flipArg = -1;
m_video_out_ctx.m_flip_status.currentBuffer = -1;
m_video_out_ctx.m_flip_status.count = 0;
m_video_out_ctx.m_vblank_status = SceVideoOutVblankStatus();
return handle;
}
void VideoOutCtx::Close(s32 handle) {
std::scoped_lock lock{m_mutex};
m_video_out_ctx.isOpened = false;
if (m_video_out_ctx.m_flip_evtEq.size() > 0) {
BREAKPOINT(); // we need to clear all events if they have been created
}
m_video_out_ctx.m_flip_rate = 0;
// clear buffers
for (auto& buffer : m_video_out_ctx.buffers) {
buffer.buffer = nullptr;
buffer.buffer_render = nullptr;
buffer.buffer_size = 0;
buffer.set_id = 0;
}
m_video_out_ctx.buffers_sets.clear();
m_video_out_ctx.buffers_registration_index = 0;
}
void VideoOutCtx::Vblank() {
std::scoped_lock lock{m_mutex};
if (m_video_out_ctx.isOpened) {
m_video_out_ctx.m_mutex.lock();
m_video_out_ctx.m_vblank_status.count++;
m_video_out_ctx.m_vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
m_video_out_ctx.m_vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc();
m_video_out_ctx.m_mutex.unlock();
}
}
VideoConfigInternal* VideoOutCtx::getCtx(int handle) {
if (handle != 1) [[unlikely]] {
return nullptr;
}
return &m_video_out_ctx; // assuming that it's the only ctx TODO check if we need more
}
void FlipQueue::getFlipStatus(VideoConfigInternal* cfg, SceVideoOutFlipStatus* out) {
std::scoped_lock lock(m_mutex);
*out = cfg->m_flip_status;
}
bool FlipQueue::submitFlip(VideoConfigInternal* cfg, s32 index, s64 flip_arg) {
std::scoped_lock lock{m_mutex};
if (m_requests.size() >= 2) {
return false;
}
Request r{};
r.cfg = cfg;
r.index = index;
r.flip_arg = flip_arg;
r.submit_tsc = Libraries::Kernel::sceKernelReadTsc();
m_requests.push_back(r);
cfg->m_flip_status.flipPendingNum = static_cast<int>(m_requests.size());
cfg->m_flip_status.gcQueueNum = 0;
m_submit_cond.notify_one();
return true;
}
bool FlipQueue::flip(u32 micros) {
const auto request = [&]() -> Request* {
std::unique_lock lock{m_mutex};
m_submit_cond.wait_for(lock, std::chrono::microseconds(micros),
[&] { return !m_requests.empty(); });
if (m_requests.empty()) {
return nullptr;
}
return &m_requests.at(0); // Process first request
}();
if (!request) {
return false;
}
const auto buffer = request->cfg->buffers[request->index].buffer_render;
Emu::DrawBuffer(buffer);
std::scoped_lock lock{m_mutex};
{
std::scoped_lock cfg_lock{request->cfg->m_mutex};
for (auto& flip_eq : request->cfg->m_flip_evtEq) {
if (flip_eq != nullptr) {
flip_eq->triggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Libraries::Kernel::EVFILT_VIDEO_OUT,
reinterpret_cast<void*>(request->flip_arg));
}
}
}
m_requests.erase(m_requests.begin());
m_done_cond.notify_one();
request->cfg->m_flip_status.count++;
request->cfg->m_flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
request->cfg->m_flip_status.tsc = Libraries::Kernel::sceKernelReadTsc();
request->cfg->m_flip_status.submitTsc = request->submit_tsc;
request->cfg->m_flip_status.flipArg = request->flip_arg;
request->cfg->m_flip_status.currentBuffer = request->index;
request->cfg->m_flip_status.flipPendingNum = static_cast<int>(m_requests.size());
return true;
}
} // namespace HLE::Graphics::Objects

View file

@ -1,90 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#include "core/PS4/HLE/Graphics/graphics_ctx.h"
#include "core/PS4/HLE/Graphics/video_out.h"
#include "emulator.h"
using namespace HLE::Libs::Graphics::VideoOut;
namespace HLE::Graphics::Objects {
struct VideoOutBufferInfo {
const void* buffer = nullptr;
HLE::Libs::Graphics::VideoOutVulkanImage* buffer_render = nullptr;
u64 buffer_size = 0;
u64 buffer_pitch = 0;
int set_id = 0;
};
struct VideoConfigInternal {
std::mutex m_mutex;
SceVideoOutResolutionStatus m_resolution;
bool isOpened = false;
SceVideoOutFlipStatus m_flip_status;
SceVideoOutVblankStatus m_vblank_status;
std::vector<Libraries::Kernel::SceKernelEqueue> m_flip_evtEq;
int m_flip_rate = 0;
VideoOutBufferInfo buffers[16];
std::vector<VideoOutBufferSetInternal> buffers_sets;
int buffers_registration_index = 0;
};
class FlipQueue {
public:
FlipQueue() {}
virtual ~FlipQueue() {}
void getFlipStatus(VideoConfigInternal* cfg, SceVideoOutFlipStatus* out);
bool submitFlip(VideoConfigInternal* cfg, s32 index, s64 flip_arg);
bool flip(u32 micros);
private:
struct Request {
VideoConfigInternal* cfg;
int index;
int64_t flip_arg;
uint64_t submit_tsc;
};
std::mutex m_mutex;
std::condition_variable m_submit_cond;
std::condition_variable m_done_cond;
std::vector<Request> m_requests;
};
class VideoOutCtx {
public:
VideoOutCtx() {}
virtual ~VideoOutCtx() {}
void Init(u32 width, u32 height);
int Open();
void Close(s32 handle);
VideoConfigInternal* getCtx(int handle);
FlipQueue& getFlipQueue() {
return m_flip_queue;
}
HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() {
std::scoped_lock lock{m_mutex};
if (!m_graphic_ctx) {
m_graphic_ctx = Emu::getGraphicCtx();
}
return m_graphic_ctx;
}
void Vblank();
private:
std::mutex m_mutex;
VideoConfigInternal m_video_out_ctx;
FlipQueue m_flip_queue;
HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr;
};
} // namespace HLE::Graphics::Objects

View file

@ -1,77 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <mutex>
#include <vector>
#include <vulkan/vulkan.h>
#include "common/types.h"
namespace HLE::Libs::Graphics {
struct VulkanCommandPool {
std::mutex mutex;
VkCommandPool pool = nullptr;
std::vector<VkCommandBuffer> buffers;
std::vector<VkFence> fences;
std::vector<VkSemaphore> semaphores;
std::vector<bool> busy;
u32 buffers_count = 0;
};
struct VulkanQueueInfo {
std::unique_ptr<std::mutex> mutex{};
u32 family = static_cast<u32>(-1);
u32 index = static_cast<u32>(-1);
VkQueue vk_queue = nullptr;
};
struct GraphicCtx {
u32 screen_width = 0;
u32 screen_height = 0;
VkInstance m_instance = nullptr;
VkPhysicalDevice m_physical_device = nullptr;
VkDevice m_device = nullptr;
VulkanQueueInfo queues[11]; // VULKAN_QUEUES_NUM
};
enum class VulkanImageType { Unknown, VideoOut };
struct VulkanMemory {
VkMemoryRequirements requirements = {};
VkMemoryPropertyFlags property = 0;
VkDeviceMemory memory = nullptr;
VkDeviceSize offset = 0;
u32 type = 0;
u64 unique_id = 0;
};
struct VulkanBuffer {
VkBuffer buffer = nullptr;
VulkanMemory memory;
VkBufferUsageFlags usage = 0;
};
struct VulkanImage {
static constexpr int VIEW_MAX = 4;
static constexpr int VIEW_DEFAULT = 0;
static constexpr int VIEW_BGRA = 1;
static constexpr int VIEW_DEPTH_TEXTURE = 2;
explicit VulkanImage(VulkanImageType type) : type(type) {}
VulkanImageType type = VulkanImageType::Unknown;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {};
VkImage image = nullptr;
VkImageView image_view[VIEW_MAX] = {};
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VulkanMemory memory;
};
struct VideoOutVulkanImage : public VulkanImage {
VideoOutVulkanImage() : VulkanImage(VulkanImageType::VideoOut) {}
};
} // namespace HLE::Libs::Graphics

View file

@ -1,207 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/core.h>
#include "common/singleton.h"
#include "core/PS4/HLE/Graphics/graphics_render.h"
#include "emulator.h"
static thread_local GPU::CommandPool g_command_pool;
void GPU::renderCreateCtx() {
auto* render_ctx = Common::Singleton<RenderCtx>::Instance();
render_ctx->setGraphicCtx(Emu::getGraphicCtx());
}
void GPU::CommandBuffer::allocateBuffer() {
m_pool = g_command_pool.getPool(m_queue);
std::scoped_lock lock{m_pool->mutex};
for (uint32_t i = 0; i < m_pool->buffers_count; i++) {
if (!m_pool->busy[i]) {
m_pool->busy[i] = true;
vkResetCommandBuffer(m_pool->buffers[i], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
m_index = i;
break;
}
}
}
void GPU::CommandBuffer::freeBuffer() {
std::scoped_lock lock{m_pool->mutex};
waitForFence();
m_pool->busy[m_index] = false;
vkResetCommandBuffer(m_pool->buffers[m_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
m_index = static_cast<uint32_t>(-1);
}
void GPU::CommandBuffer::waitForFence() {
auto* render_ctx = Common::Singleton<RenderCtx>::Instance();
if (m_execute) {
auto* device = render_ctx->getGraphicCtx()->m_device;
vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX);
vkResetFences(device, 1, &m_pool->fences[m_index]);
m_execute = false;
}
}
void GPU::CommandBuffer::begin() const {
auto* buffer = m_pool->buffers[m_index];
VkCommandBufferBeginInfo begin_info{};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.pNext = nullptr;
begin_info.flags = 0;
begin_info.pInheritanceInfo = nullptr;
auto result = vkBeginCommandBuffer(buffer, &begin_info);
if (result != VK_SUCCESS) {
fmt::print("vkBeginCommandBuffer failed\n");
std::exit(0);
}
}
void GPU::CommandBuffer::end() const {
auto* buffer = m_pool->buffers[m_index];
auto result = vkEndCommandBuffer(buffer);
if (result != VK_SUCCESS) {
fmt::print("vkEndCommandBuffer failed\n");
std::exit(0);
}
}
void GPU::CommandBuffer::executeWithSemaphore() {
auto* buffer = m_pool->buffers[m_index];
auto* fence = m_pool->fences[m_index];
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.pNext = nullptr;
submit_info.waitSemaphoreCount = 0;
submit_info.pWaitSemaphores = nullptr;
submit_info.pWaitDstStageMask = nullptr;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &m_pool->semaphores[m_index];
auto* render_ctx = Common::Singleton<RenderCtx>::Instance();
const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue];
auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
m_execute = true;
if (result != VK_SUCCESS) {
fmt::print("vkQueueSubmit failed\n");
std::exit(0);
}
}
void GPU::CommandBuffer::execute() {
auto* buffer = m_pool->buffers[m_index];
auto* fence = m_pool->fences[m_index];
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.pNext = nullptr;
submit_info.waitSemaphoreCount = 0;
submit_info.pWaitSemaphores = nullptr;
submit_info.pWaitDstStageMask = nullptr;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = 0;
submit_info.pSignalSemaphores = nullptr;
auto* render_ctx = Common::Singleton<RenderCtx>::Instance();
const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue];
auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
m_execute = true;
if (result != VK_SUCCESS) {
fmt::print("vkQueueSubmit failed\n");
std::exit(0);
}
}
void GPU::CommandPool::createPool(int id) {
auto* render_ctx = Common::Singleton<RenderCtx>::Instance();
auto* ctx = render_ctx->getGraphicCtx();
VkCommandPoolCreateInfo pool_info{};
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_info.pNext = nullptr;
pool_info.queueFamilyIndex = ctx->queues[id].family;
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
vkCreateCommandPool(ctx->m_device, &pool_info, nullptr, &m_pool[id].pool);
if (!m_pool[id].pool) {
fmt::print("pool is nullptr");
std::exit(0);
}
m_pool[id].buffers_count = 4;
m_pool[id].buffers.resize(m_pool[id].buffers_count);
m_pool[id].fences.resize(m_pool[id].buffers_count);
m_pool[id].semaphores.resize(m_pool[id].buffers_count);
m_pool[id].busy.resize(m_pool[id].buffers_count);
VkCommandBufferAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.commandPool = m_pool[id].pool;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandBufferCount = m_pool[id].buffers_count;
if (vkAllocateCommandBuffers(ctx->m_device, &alloc_info, m_pool[id].buffers.data()) !=
VK_SUCCESS) {
fmt::print("Can't allocate command buffers\n");
std::exit(0);
}
for (u32 i = 0; i < m_pool[id].buffers_count; i++) {
m_pool[id].busy[i] = false;
VkFenceCreateInfo fence_info{};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.pNext = nullptr;
fence_info.flags = 0;
if (vkCreateFence(ctx->m_device, &fence_info, nullptr, &m_pool[id].fences[i]) !=
VK_SUCCESS) {
fmt::print("Can't create fence\n");
std::exit(0);
}
VkSemaphoreCreateInfo semaphore_info{};
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphore_info.pNext = nullptr;
semaphore_info.flags = 0;
if (vkCreateSemaphore(ctx->m_device, &semaphore_info, nullptr, &m_pool[id].semaphores[i]) !=
VK_SUCCESS) {
fmt::print("Can't create semas\n");
std::exit(0);
}
}
}
void GPU::CommandPool::deleteAllPool() {
auto* render_ctx = Common::Singleton<RenderCtx>::Instance();
auto* ctx = render_ctx->getGraphicCtx();
for (auto& pool : m_pool) {
if (pool.pool) {
for (u32 i = 0; i < pool.buffers_count; i++) {
vkDestroySemaphore(ctx->m_device, pool.semaphores[i], nullptr);
vkDestroyFence(ctx->m_device, pool.fences[i], nullptr);
}
vkDestroyCommandPool(ctx->m_device, pool.pool, nullptr);
}
}
}

View file

@ -1,81 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "core/PS4/HLE/Graphics/graphics_ctx.h"
namespace GPU {
class CommandPool {
public:
CommandPool() = default;
~CommandPool() {}
HLE::Libs::Graphics::VulkanCommandPool* getPool(int id) {
if (!m_pool[id].pool) {
createPool(id);
}
return &m_pool[id];
}
private:
void createPool(int id);
void deleteAllPool();
std::array<HLE::Libs::Graphics::VulkanCommandPool, 11> m_pool{};
};
class CommandBuffer {
public:
explicit CommandBuffer(int queue) : m_queue(queue) {
allocateBuffer();
}
virtual ~CommandBuffer() {
freeBuffer();
}
void allocateBuffer();
void freeBuffer();
void waitForFence();
void begin() const;
void end() const;
void executeWithSemaphore();
void execute();
u32 getIndex() const {
return m_index;
}
HLE::Libs::Graphics::VulkanCommandPool* getPool() {
return m_pool;
}
private:
int m_queue = -1;
u32 m_index = static_cast<u32>(-1);
HLE::Libs::Graphics::VulkanCommandPool* m_pool = nullptr;
bool m_execute = false;
};
class Framebuffer {
public:
Framebuffer() {}
virtual ~Framebuffer() {}
};
class RenderCtx {
public:
RenderCtx() = default;
virtual ~RenderCtx() {}
void setGraphicCtx(HLE::Libs::Graphics::GraphicCtx* ctx) {
m_graphic_ctx = ctx;
}
HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() {
return m_graphic_ctx;
}
private:
Framebuffer m_framebuffer{};
HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr;
};
void renderCreateCtx();
}; // namespace GPU

View file

@ -1,382 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include "Objects/video_out_ctx.h"
#include "common/config.h"
#include "common/debug.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/PS4/GPU/gpu_memory.h"
#include "core/PS4/GPU/video_out_buffer.h"
#include "core/PS4/HLE/Graphics/graphics_render.h"
#include "core/PS4/HLE/Graphics/video_out.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/gnmdriver/gnmdriver.h"
#include "core/libraries/libs.h"
#include "core/loader/symbols_resolver.h"
#include "emulator.h"
#include "src/core/libraries/system/userservice.h"
namespace HLE::Libs::Graphics::VideoOut {
constexpr bool log_file_videoout = true; // disable it to disable logging
void videoOutInit(u32 width, u32 height) {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
videoOut->Init(width, height);
}
bool videoOutFlip(u32 micros) {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
return videoOut->getFlipQueue().flip(micros);
}
void VideoOutVblank() {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
return videoOut->Vblank();
}
std::string getPixelFormatString(s32 format) {
switch (format) {
case SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB:
return "PIXEL_FORMAT_A8R8G8B8_SRGB";
case SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB:
return "PIXEL_FORMAT_A8B8G8R8_SRGB";
case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10:
return "PIXEL_FORMAT_A2R10G10B10";
case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB:
return "PIXEL_FORMAT_A2R10G10B10_SRGB";
case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ:
return "PIXEL_FORMAT_A2R10G10B10_BT2020_PQ";
case SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT:
return "PIXEL_FORMAT_A16R16G16B16_FLOAT";
case SCE_VIDEO_OUT_PIXEL_FORMAT_YCBCR420_BT709:
return "PIXEL_FORMAT_YCBCR420_BT709";
default:
return "Unknown pixel format";
}
}
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(SceVideoOutBufferAttribute* attribute,
u32 pixelFormat, u32 tilingMode, u32 aspectRatio,
u32 width, u32 height, u32 pitchInPixel) {
LOG_INFO(Lib_VideoOut,
"pixelFormat = {}, tilingMode = {}, aspectRatio = {}, width = {}, height = {}, "
"pitchInPixel = {}",
getPixelFormatString(pixelFormat), tilingMode, aspectRatio, width, height,
pitchInPixel);
std::memset(attribute, 0, sizeof(SceVideoOutBufferAttribute));
attribute->pixelFormat = pixelFormat;
attribute->tilingMode = tilingMode;
attribute->aspectRatio = aspectRatio;
attribute->width = width;
attribute->height = height;
attribute->pitchInPixel = pitchInPixel;
attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE;
}
static void flip_reset_event_func(Libraries::Kernel::EqueueEvent* event) {
event->isTriggered = false;
event->event.fflags = 0;
event->event.data = 0;
}
static void flip_trigger_event_func(Libraries::Kernel::EqueueEvent* event, void* trigger_data) {
event->isTriggered = true;
event->event.fflags++;
event->event.data = reinterpret_cast<intptr_t>(trigger_data);
}
static void flip_delete_event_func(Libraries::Kernel::SceKernelEqueue eq,
Libraries::Kernel::EqueueEvent* event) {
BREAKPOINT(); // TODO
}
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Libraries::Kernel::SceKernelEqueue eq, s32 handle,
void* udata) {
LOG_INFO(Lib_VideoOut, "handle = {}", handle);
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
if (ctx == nullptr) {
return SCE_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
std::scoped_lock lock(ctx->m_mutex);
if (eq == nullptr) {
return SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE;
}
Libraries::Kernel::EqueueEvent event{};
event.isTriggered = false;
event.event.ident = SCE_VIDEO_OUT_EVENT_FLIP;
event.event.filter = Libraries::Kernel::EVFILT_VIDEO_OUT;
event.event.udata = udata;
event.event.fflags = 0;
event.event.data = 0;
event.filter.delete_event_func = flip_delete_event_func;
event.filter.reset_event_func = flip_reset_event_func;
event.filter.trigger_event_func = flip_trigger_event_func;
event.filter.data = ctx;
int result = eq->addEvent(event);
ctx->m_flip_evtEq.push_back(eq);
return result;
}
s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus* status) {
if (status == nullptr) {
return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
ctx->m_mutex.lock();
*status = ctx->m_vblank_status;
ctx->m_mutex.unlock();
return SCE_OK;
}
s32 sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum,
const SceVideoOutBufferAttribute* attribute) {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
if (handle == 1) { // main port
if (startIndex < 0 || startIndex > 15) {
LOG_ERROR(Lib_VideoOut, "Invalid startIndex = {}", startIndex);
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
}
if (bufferNum < 1 || bufferNum > 16) {
LOG_ERROR(Lib_VideoOut, "Invalid bufferNum = {}", bufferNum);
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
}
}
if (addresses == nullptr) {
LOG_ERROR(Lib_VideoOut, "Addresses are null");
return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
if (attribute == nullptr) {
LOG_ERROR(Lib_VideoOut, "Attribute is null");
return SCE_VIDEO_OUT_ERROR_INVALID_OPTION;
}
if (attribute->aspectRatio != 0) {
LOG_ERROR(Lib_VideoOut, "Invalid aspect ratio = {}", attribute->aspectRatio);
return SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO;
}
if (attribute->tilingMode < 0 || attribute->tilingMode > 1) {
LOG_ERROR(Lib_VideoOut, "Invalid tilingMode = {}", attribute->tilingMode);
return SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE;
}
LOG_INFO(Lib_VideoOut,
"handle = {}, startIndex = {}, bufferNum = {}, pixelFormat = {:#x}, aspectRatio = {}, "
"tilingMode = {}, width = {}, height = {}, pitchInPixel = {}, option = {:#x}",
handle, startIndex, bufferNum, attribute->pixelFormat, attribute->aspectRatio,
attribute->tilingMode, attribute->width, attribute->height, attribute->pitchInPixel,
attribute->option);
int registration_index = ctx->buffers_registration_index++;
Emu::checkAndWaitForGraphicsInit();
GPU::renderCreateCtx();
// try to calculate buffer size
u64 buffer_size = 0; // still calculation is probably partial or wrong :D
if (attribute->tilingMode == 0) {
buffer_size = 1920 * 1088 * 4;
} else {
buffer_size = 1920 * 1080 * 4;
}
u64 buffer_pitch = attribute->pitchInPixel;
VideoOutBufferSetInternal buf{};
buf.start_index = startIndex;
buf.num = bufferNum;
buf.set_id = registration_index;
buf.attr = *attribute;
ctx->buffers_sets.push_back(buf);
GPU::VideoOutBufferFormat format = GPU::VideoOutBufferFormat::Unknown;
if (attribute->pixelFormat == 0x80000000) {
format = GPU::VideoOutBufferFormat::B8G8R8A8Srgb;
} else if (attribute->pixelFormat == 0x80002200) {
format = GPU::VideoOutBufferFormat::R8G8B8A8Srgb;
}
GPU::VideoOutBufferObj buffer_info(format, attribute->width, attribute->height,
attribute->tilingMode == 0, Config::isNeoMode(),
buffer_pitch);
for (int i = 0; i < bufferNum; i++) {
if (ctx->buffers[i + startIndex].buffer != nullptr) {
LOG_ERROR(Lib_VideoOut, "Buffer slot {} is occupied!", i + startIndex);
return SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED;
}
ctx->buffers[i + startIndex].set_id = registration_index;
ctx->buffers[i + startIndex].buffer = addresses[i];
ctx->buffers[i + startIndex].buffer_size = buffer_size;
ctx->buffers[i + startIndex].buffer_pitch = buffer_pitch;
ctx->buffers[i + startIndex].buffer_render =
static_cast<Graphics::VideoOutVulkanImage*>(GPU::memoryCreateObj(
0, videoOut->getGraphicCtx(), nullptr, reinterpret_cast<uint64_t>(addresses[i]),
buffer_size, buffer_info));
LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex,
reinterpret_cast<u64>(addresses[i]));
}
return registration_index;
}
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) {
LOG_INFO(Lib_VideoOut, "called");
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
videoOut->getCtx(handle)->m_flip_rate = rate;
return SCE_OK;
}
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) {
LOG_INFO(Lib_VideoOut, "called");
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
s32 pending = videoOut->getCtx(handle)->m_flip_status.flipPendingNum;
return pending;
}
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg) {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
if (flipMode != 1) {
// BREAKPOINT(); // only flipmode==1 is supported
LOG_WARNING(Lib_VideoOut, "flipmode = {}",
flipMode); // openBOR needs 2 but seems to work
}
if (bufferIndex == -1) {
BREAKPOINT(); // blank output not supported
}
if (bufferIndex < -1 || bufferIndex > 15) {
LOG_ERROR(Lib_VideoOut, "Invalid bufferIndex = {}", bufferIndex);
return SCE_VIDEO_OUT_ERROR_INVALID_INDEX;
}
LOG_INFO(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode,
flipArg);
if (!videoOut->getFlipQueue().submitFlip(ctx, bufferIndex, flipArg)) {
LOG_ERROR(Lib_VideoOut, "Flip queue is full");
return SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL;
}
Libraries::GnmDriver::sceGnmFlushGarlic(); // hackish should be done that neccesary
// for niko's homebrew
return SCE_OK;
}
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, SceVideoOutFlipStatus* status) {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
videoOut->getFlipQueue().getFlipStatus(ctx, status);
LOG_INFO(Lib_VideoOut,
"count = {}, processTime = {}, tsc = {}, submitTsc = {}, flipArg = {}, gcQueueNum = "
"{}, flipPendingNum = {}, currentBuffer = {}",
status->count, status->processTime, status->tsc, status->submitTsc, status->flipArg,
status->gcQueueNum, status->flipPendingNum, status->currentBuffer);
return 0;
}
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
LOG_INFO(Lib_VideoOut, "called");
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
*status = videoOut->getCtx(handle)->m_resolution;
return SCE_OK;
}
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param) {
LOG_INFO(Lib_VideoOut, "called");
if (userId != Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM && userId != 0) {
BREAKPOINT();
}
if (busType != SCE_VIDEO_OUT_BUS_TYPE_MAIN) {
BREAKPOINT();
}
if (index != 0) {
LOG_ERROR(Lib_VideoOut, "Index != 0");
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
}
if (param != nullptr) {
BREAKPOINT();
}
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
int handle = videoOut->Open();
if (handle < 0) {
LOG_ERROR(Lib_VideoOut, "All available handles are open");
return SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY; // it is alreadyOpened
}
return handle;
}
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle) {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
videoOut->Close(handle);
return SCE_OK;
}
s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) {
BREAKPOINT();
return 0;
}
void videoOutRegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetFlipStatus);
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSubmitFlip);
LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutRegisterBuffers);
LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutAddFlipEvent);
LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutSetFlipRate);
LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutSetBufferAttribute);
LIB_FUNCTION("6kPnj51T62Y", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetResolutionStatus);
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutOpen);
LIB_FUNCTION("zgXifHT9ErY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutIsFlipPending);
LIB_FUNCTION("N5KDtkIjjJ4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutUnregisterBuffers);
LIB_FUNCTION("uquVH4-Du78", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutClose);
LIB_FUNCTION("1FZBKy8HeNU", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetVblankStatus);
// openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen);
LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutSetFlipRate);
LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutAddFlipEvent);
LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutSetBufferAttribute);
LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutRegisterBuffers);
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutSubmitFlip);
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutGetFlipStatus);
}
} // namespace HLE::Libs::Graphics::VideoOut

View file

@ -2,11 +2,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/PS4/GPU/gpu_memory.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/gnmdriver/gnmdriver.h"
#include "core/libraries/libs.h"
#include "emulator.h"
namespace Libraries::GnmDriver {
@ -293,7 +291,6 @@ int PS4_SYSV_ABI sceGnmFindResourcesPublic() {
void PS4_SYSV_ABI sceGnmFlushGarlic() {
LOG_WARNING(Lib_GnmDriver, "(STUBBED) called");
GPU::flushGarlic(Emu::getGraphicCtx());
}
int PS4_SYSV_ABI sceGnmGetCoredumpAddress() {

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/debug.h"
#include "core/libraries/kernel/event_queue.h"
@ -11,16 +12,11 @@ EqueueInternal::~EqueueInternal() = default;
int EqueueInternal::addEvent(const EqueueEvent& event) {
std::scoped_lock lock{m_mutex};
if (m_events.size() > 0) {
BREAKPOINT();
}
ASSERT(m_events.empty());
ASSERT(!event.isTriggered);
// TODO check if event is already exists and return it. Currently we just add in m_events array
m_events.push_back(event);
if (event.isTriggered) {
BREAKPOINT(); // we don't support that either yet
}
return 0;
}
@ -32,29 +28,27 @@ int EqueueInternal::waitForEvents(SceKernelEvent* ev, int num, u32 micros) {
ret = getTriggeredEvents(ev, num);
return ret > 0;
};
#ifndef _WIN64
char buf[128];
pthread_getname_np(pthread_self(), buf, 128);
fmt::print("Thread {} waiting for events (micros = {})\n", buf, micros);
#endif // !_WIN64
if (micros == 0) {
m_cond.wait(lock, predicate);
} else {
m_cond.wait_for(lock, std::chrono::microseconds(micros), predicate);
}
fmt::print("Wait done\n");
return ret;
}
bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) {
std::scoped_lock lock{m_mutex};
if (m_events.size() > 1) {
BREAKPOINT(); // we currently support one event
}
ASSERT(m_events.size() <= 1);
auto& event = m_events[0];
if (event.filter.trigger_event_func != nullptr) {
event.filter.trigger_event_func(&event, trigger_data);
} else {
event.isTriggered = true;
}
event.trigger(trigger_data);
m_cond.notify_one();
return true;
@ -63,17 +57,12 @@ bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) {
int EqueueInternal::getTriggeredEvents(SceKernelEvent* ev, int num) {
int ret = 0;
if (m_events.size() > 1) {
BREAKPOINT(); // we currently support one event
}
ASSERT(m_events.size() <= 1);
auto& event = m_events[0];
if (event.isTriggered) {
ev[ret++] = event.event;
if (event.filter.reset_event_func != nullptr) {
event.filter.reset_event_func(&event);
}
event.reset();
}
return ret;

View file

@ -52,15 +52,24 @@ struct SceKernelEvent {
struct Filter {
void* data = nullptr;
TriggerFunc trigger_event_func = nullptr;
ResetFunc reset_event_func = nullptr;
DeleteFunc delete_event_func = nullptr;
};
struct EqueueEvent {
bool isTriggered = false;
SceKernelEvent event;
Filter filter;
void reset() {
isTriggered = false;
event.fflags = 0;
event.data = 0;
}
void trigger(void* data) {
isTriggered = true;
event.fflags++;
event.data = reinterpret_cast<uintptr_t>(data);
}
};
class EqueueInternal {

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"

View file

@ -2,10 +2,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/PS4/GPU/gpu_memory.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/memory_management.h"
#include "core/libraries/kernel/physical_memory.h"
@ -13,10 +13,6 @@
namespace Libraries::Kernel {
bool is16KBAligned(u64 n) {
return ((n % (16ull * 1024) == 0));
}
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
LOG_WARNING(Kernel_Vmm, "called");
return SCE_KERNEL_MAIN_DMEM_SIZE;
@ -34,11 +30,11 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u
return SCE_KERNEL_ERROR_EINVAL;
}
const bool is_in_range = (searchStart < len && searchEnd > len);
if (len <= 0 || !is16KBAligned(len) || !is_in_range) {
if (len <= 0 || !Common::is16KBAligned(len) || !is_in_range) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if ((alignment != 0 || is16KBAligned(alignment)) && !std::has_single_bit(alignment)) {
if ((alignment != 0 || Common::is16KBAligned(alignment)) && !std::has_single_bit(alignment)) {
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
@ -66,23 +62,22 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
"len = {:#x}, prot = {:#x}, flags = {:#x}, directMemoryStart = {:#x}, alignment = {:#x}",
len, prot, flags, directMemoryStart, alignment);
if (len == 0 || !is16KBAligned(len)) {
if (len == 0 || !Common::is16KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (!is16KBAligned(directMemoryStart)) {
if (!Common::is16KBAligned(directMemoryStart)) {
LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (alignment != 0) {
if ((!std::has_single_bit(alignment) && !is16KBAligned(alignment))) {
if ((!std::has_single_bit(alignment) && !Common::is16KBAligned(alignment))) {
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
}
VirtualMemory::MemoryMode cpu_mode = VirtualMemory::MemoryMode::NoAccess;
GPU::MemoryMode gpu_mode = GPU::MemoryMode::NoAccess;
switch (prot) {
case 0x03:
@ -91,7 +86,6 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
case 0x32:
case 0x33: // SCE_KERNEL_PROT_CPU_READ|SCE_KERNEL_PROT_CPU_WRITE|SCE_KERNEL_PROT_GPU_READ|SCE_KERNEL_PROT_GPU_ALL
cpu_mode = VirtualMemory::MemoryMode::ReadWrite;
gpu_mode = GPU::MemoryMode::ReadWrite;
break;
default:
UNREACHABLE();
@ -112,13 +106,10 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
}
auto* physical_memory = Common::Singleton<PhysicalMemory>::Instance();
if (!physical_memory->Map(out_addr, directMemoryStart, len, prot, cpu_mode, gpu_mode)) {
if (!physical_memory->Map(out_addr, directMemoryStart, len, prot, cpu_mode)) {
UNREACHABLE();
}
if (gpu_mode != GPU::MemoryMode::NoAccess) {
GPU::memorySetAllocArea(out_addr, len);
}
return SCE_OK;
}

View file

@ -1,20 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "core/libraries/kernel/physical_memory.h"
namespace Libraries::Kernel {
static u64 AlignUp(u64 pos, u64 align) {
return (align != 0 ? (pos + (align - 1)) & ~(align - 1) : pos);
}
bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut,
int memoryType) {
std::scoped_lock lock{m_mutex};
u64 find_free_pos = 0;
// iterate through allocated blocked and find the next free position
// Iterate through allocated blocked and find the next free position
for (const auto& block : m_allocatedBlocks) {
u64 n = block.start_addr + block.size;
if (n > find_free_pos) {
@ -22,16 +19,15 @@ bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignmen
}
}
// align free position
find_free_pos = AlignUp(find_free_pos, alignment);
// Align free position
find_free_pos = Common::alignUp(find_free_pos, alignment);
// if the new position is between searchStart - searchEnd , allocate a new block
// If the new position is between searchStart - searchEnd , allocate a new block
if (find_free_pos >= searchStart && find_free_pos + len <= searchEnd) {
AllocatedBlock block{};
block.size = len;
block.start_addr = find_free_pos;
block.memoryType = memoryType;
block.gpu_mode = GPU::MemoryMode::NoAccess;
block.map_size = 0;
block.map_virtual_addr = 0;
block.prot = 0;
@ -47,7 +43,7 @@ bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignmen
}
bool PhysicalMemory::Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot,
VirtualMemory::MemoryMode cpu_mode, GPU::MemoryMode gpu_mode) {
VirtualMemory::MemoryMode cpu_mode) {
std::scoped_lock lock{m_mutex};
for (auto& b : m_allocatedBlocks) {
if (phys_addr >= b.start_addr && phys_addr < b.start_addr + b.size) {
@ -59,7 +55,6 @@ bool PhysicalMemory::Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot,
b.map_size = len;
b.prot = prot;
b.cpu_mode = cpu_mode;
b.gpu_mode = gpu_mode;
return true;
}

View file

@ -6,7 +6,6 @@
#include <mutex>
#include <vector>
#include "common/types.h"
#include "core/PS4/GPU/gpu_memory.h"
#include "core/virtual_memory.h"
namespace Libraries::Kernel {
@ -21,7 +20,6 @@ public:
u64 map_size;
int prot;
VirtualMemory::MemoryMode cpu_mode;
GPU::MemoryMode gpu_mode;
};
PhysicalMemory() {}
virtual ~PhysicalMemory() {}
@ -29,8 +27,8 @@ public:
public:
bool Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut,
int memoryType);
bool Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, VirtualMemory::MemoryMode cpu_mode,
GPU::MemoryMode gpu_mode);
bool Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot,
VirtualMemory::MemoryMode cpu_mode);
private:
std::vector<AllocatedBlock> m_allocatedBlocks;

View file

@ -13,7 +13,7 @@
#include "core/libraries/libc/libc_string.h"
#include "core/libraries/libs.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
constexpr bool log_file_libc = true; // disable it to disable logging
static u32 g_need_sceLibc = 1;
@ -421,7 +421,7 @@ const PS4_SYSV_ABI u16* ps4__Getpctype() {
return &characterTypeTable[0];
}
void libcSymbolsRegister(Loader::SymbolsResolver* sym) {
void libcSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
// cxa functions
LIB_FUNCTION("3GPpjQdAMTw", "libc", 1, "libc", 1, 1, ps4___cxa_guard_acquire);
LIB_FUNCTION("9rAeANT2tyE", "libc", 1, "libc", 1, 1, ps4___cxa_guard_release);
@ -490,4 +490,4 @@ void libcSymbolsRegister(Loader::SymbolsResolver* sym) {
LIB_FUNCTION("sUP1hBaouOw", "libc", 1, "libc", 1, 1, ps4__Getpctype);
}
}; // namespace Core::Libraries::LibC
}; // namespace Libraries::LibC

View file

@ -7,8 +7,8 @@ namespace Core::Loader {
class SymbolsResolver;
}
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
void libcSymbolsRegister(Loader::SymbolsResolver* sym);
void libcSymbolsRegister(Core::Loader::SymbolsResolver* sym);
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -7,7 +7,7 @@
// adapted from
// https://opensource.apple.com/source/libcppabi/libcppabi-14/src/cxa_guard.cxx.auto.html
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
// This file implements the __cxa_guard_* functions as defined at:
// http://www.codesourcery.com/public/cxx-abi/abi.html
@ -158,4 +158,4 @@ void PS4_SYSV_ABI ps4___cxa_guard_abort(u64* guard_object) {
setNotInUse(guard_object);
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -6,10 +6,10 @@
#include <pthread.h>
#include "common/types.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
int PS4_SYSV_ABI ps4___cxa_guard_acquire(u64* guard_object);
void PS4_SYSV_ABI ps4___cxa_guard_release(u64* guard_object);
void PS4_SYSV_ABI ps4___cxa_guard_abort(u64* guard_object);
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -4,7 +4,7 @@
#include <cmath>
#include "core/libraries/libc/libc_math.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
float PS4_SYSV_ABI ps4_atan2f(float y, float x) {
return atan2f(y, x);
@ -38,4 +38,4 @@ double PS4_SYSV_ABI ps4_exp2(double arg) {
return exp2(arg);
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -5,7 +5,7 @@
#include "common/types.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
float PS4_SYSV_ABI ps4_atan2f(float y, float x);
float PS4_SYSV_ABI ps4_acosf(float num);
@ -16,4 +16,4 @@ double PS4_SYSV_ABI ps4__Sin(double x);
float PS4_SYSV_ABI ps4__Fsin(float arg);
double PS4_SYSV_ABI ps4_exp2(double arg);
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -6,7 +6,7 @@
#include "core/file_sys/fs.h"
#include "core/libraries/libc/libc_stdio.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
@ -70,4 +70,4 @@ int PS4_SYSV_ABI ps4_puts(const char* s) {
return std::puts(s);
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -6,7 +6,7 @@
#include "common/types.h"
#include "core/libraries/libc/printf.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode);
int PS4_SYSV_ABI ps4_printf(VA_ARGS);
@ -19,4 +19,4 @@ int PS4_SYSV_ABI ps4_fseek(FILE* stream, long offset, int whence);
int PS4_SYSV_ABI ps4_fgetpos(FILE* stream, fpos_t* pos);
std::size_t PS4_SYSV_ABI ps4_fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -5,7 +5,7 @@
#include "common/assert.h"
#include "core/libraries/libc/libc_stdlib.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
void PS4_SYSV_ABI ps4_exit(int code) {
std::exit(code);
@ -42,4 +42,4 @@ int PS4_SYSV_ABI ps4_rand() {
return std::rand();
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -6,7 +6,7 @@
#include <cstddef>
#include "common/types.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
void PS4_SYSV_ABI ps4_exit(int code);
int PS4_SYSV_ABI ps4_atexit(void (*func)());
@ -16,4 +16,4 @@ void PS4_SYSV_ABI ps4_qsort(void* ptr, size_t count, size_t size,
int(PS4_SYSV_ABI* comp)(const void*, const void*));
int PS4_SYSV_ABI ps4_rand();
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -5,7 +5,7 @@
#include <cstring>
#include "core/libraries/libc/libc_string.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
int PS4_SYSV_ABI ps4_memcmp(const void* s1, const void* s2, size_t n) {
return std::memcmp(s1, s2, n);
@ -58,4 +58,4 @@ char* PS4_SYSV_ABI ps4_strdup(const char* str1) {
return dup;
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -6,7 +6,7 @@
#include <cstddef>
#include "common/types.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
int PS4_SYSV_ABI ps4_memcmp(const void* s1, const void* s2, size_t n);
void* PS4_SYSV_ABI ps4_memcpy(void* dest, const void* src, size_t n);
@ -21,4 +21,4 @@ char* PS4_SYSV_ABI ps4_strrchr(const char* s, int c);
int PS4_SYSV_ABI ps4_strncmp(const char* s1, const char* s2, size_t n);
char* PS4_SYSV_ABI ps4_strdup(const char* str1);
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -64,7 +64,7 @@
#include "va_ctx.h"
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
// ntoa conversion buffer size, this must be big enough to hold
// one converted numeric number including padded zeros (dynamically created on stack)
// 32 byte is a good default
@ -750,4 +750,4 @@ static int vsnprintf_ctx(char* s, size_t n, const char* format, VaList* arg) {
std::strcpy(s, buffer);
return result;
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -30,7 +30,7 @@
(ctx).va_list.fp_offset = offsetof(VaRegSave, fp); \
(ctx).va_list.overflow_arg_area = &overflow_arg_area;
namespace Core::Libraries::LibC {
namespace Libraries::LibC {
// https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure
@ -107,4 +107,4 @@ T* vaArgPtr(VaList* l) {
return vaArgOverflowArgArea<T*, 1, 8>(l);
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
#include "core/PS4/HLE/Graphics/video_out.h"
#include "core/libraries/audio/audioin.h"
#include "core/libraries/audio/audioout.h"
#include "core/libraries/gnmdriver/gnmdriver.h"
@ -22,16 +21,17 @@
#include "core/libraries/system/sysmodule.h"
#include "core/libraries/system/systemservice.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/videoout/video_out.h"
namespace Libraries {
void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Kernel::LibKernel_Register(sym);
HLE::Libs::Graphics::VideoOut::videoOutRegisterLib(sym);
Libraries::VideoOut::RegisterLib(sym);
Libraries::GnmDriver::RegisterlibSceGnmDriver(sym);
Libraries::LibPad::padSymbolsRegister(sym);
if (!Config::isLleLibc()) {
Core::Libraries::LibC::libcSymbolsRegister(sym);
Libraries::LibC::libcSymbolsRegister(sym);
}
// New libraries folder from autogen

View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#include "common/assert.h"
#include "common/types.h"
namespace Libraries::VideoOut {
constexpr std::size_t MaxDisplayBuffers = 16;
constexpr std::size_t MaxDisplayBufferGroups = 4;
enum class PixelFormat : u32 {
Unknown,
A8R8G8B8Srgb = 0x80000000,
A8B8G8R8Srgb = 0x80002200,
A2R10G10B10 = 0x88060000,
A2R10G10B10Srgb = 0x88000000,
A2R10G10B10Bt2020Pq = 0x88740000,
A16R16G16B16Float = 0xC1060000,
};
enum class TilingMode : s32 {
Tile = 0,
Linear = 1,
};
constexpr std::string_view GetPixelFormatString(PixelFormat format) {
switch (format) {
case PixelFormat::A8R8G8B8Srgb:
return "A8R8G8B8Srgb";
case PixelFormat::A8B8G8R8Srgb:
return "A8B8G8R8Srgb";
case PixelFormat::A2R10G10B10:
return "A2R10G10B10";
case PixelFormat::A2R10G10B10Srgb:
return "A2R10G10B10Srgb";
case PixelFormat::A2R10G10B10Bt2020Pq:
return "A2R10G10B10Bt2020Pq";
case PixelFormat::A16R16G16B16Float:
return "A16R16G16B16Float";
default:
UNREACHABLE_MSG("Unknown pixel format {}", static_cast<u32>(format));
return "";
}
}
struct BufferAttribute {
PixelFormat pixel_format;
TilingMode tiling_mode;
s32 aspect_ratio;
u32 width;
u32 height;
u32 pitch_in_pixel;
u32 option;
u32 reserved0;
u64 reserved1;
};
struct BufferAttributeGroup {
bool is_occupied;
BufferAttribute attrib;
u32 size_in_bytes;
};
struct VideoOutBuffer {
s32 group_index{-1};
uintptr_t address_left;
uintptr_t address_right;
};
} // namespace Libraries::VideoOut

View file

@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <pthread.h>
#include "common/assert.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/time_management.h"
#include "core/libraries/videoout/driver.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
extern Frontend::WindowSDL* g_window;
namespace Libraries::VideoOut {
constexpr static bool Is32BppPixelFormat(PixelFormat format) {
switch (format) {
case PixelFormat::A8R8G8B8Srgb:
case PixelFormat::A8B8G8R8Srgb:
case PixelFormat::A2R10G10B10:
case PixelFormat::A2R10G10B10Srgb:
case PixelFormat::A2R10G10B10Bt2020Pq:
return true;
default:
return false;
}
}
constexpr u32 PixelFormatBpp(PixelFormat pixel_format) {
switch (pixel_format) {
case PixelFormat::A16R16G16B16Float:
return 8;
default:
return 4;
}
}
VideoOutDriver::VideoOutDriver(u32 width, u32 height) {
main_port.resolution.fullWidth = width;
main_port.resolution.fullHeight = height;
main_port.resolution.paneWidth = width;
main_port.resolution.paneHeight = height;
renderer = std::make_unique<Vulkan::RendererVulkan>(*g_window);
}
VideoOutDriver::~VideoOutDriver() = default;
int VideoOutDriver::Open(const ServiceThreadParams* params) {
std::scoped_lock lock{mutex};
if (main_port.is_open) {
return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
int handle = 1;
main_port.is_open = true;
return handle;
}
void VideoOutDriver::Close(s32 handle) {
std::scoped_lock lock{mutex};
main_port.is_open = false;
main_port.flip_rate = 0;
ASSERT(main_port.flip_events.empty());
}
VideoOutPort* VideoOutDriver::GetPort(int handle) {
if (handle != 1) [[unlikely]] {
return nullptr;
}
return &main_port;
}
int VideoOutDriver::RegisterBuffers(VideoOutPort* port, s32 startIndex, void* const* addresses,
s32 bufferNum, const BufferAttribute* attribute) {
const s32 group_index = port->FindFreeGroup();
if (group_index >= MaxDisplayBufferGroups) {
return ORBIS_VIDEO_OUT_ERROR_NO_EMPTY_SLOT;
}
if (startIndex + bufferNum > MaxDisplayBuffers || startIndex > MaxDisplayBuffers ||
bufferNum > MaxDisplayBuffers) {
LOG_ERROR(Lib_VideoOut,
"Attempted to register too many buffers startIndex = {}, bufferNum = {}",
startIndex, bufferNum);
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
const s32 end_index = startIndex + bufferNum;
if (bufferNum > 0 &&
std::any_of(port->buffer_slots.begin() + startIndex, port->buffer_slots.begin() + end_index,
[](auto& buffer) { return buffer.group_index != -1; })) {
return ORBIS_VIDEO_OUT_ERROR_SLOT_OCCUPIED;
}
if (attribute->reserved0 != 0 || attribute->reserved1 != 0) {
LOG_ERROR(Lib_VideoOut, "Invalid reserved memebers");
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
if (attribute->aspect_ratio != 0) {
LOG_ERROR(Lib_VideoOut, "Invalid aspect ratio = {}", attribute->aspect_ratio);
return ORBIS_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO;
}
if (attribute->width > attribute->pitch_in_pixel) {
LOG_ERROR(Lib_VideoOut, "Buffer width {} is larger than pitch {}", attribute->width,
attribute->pitch_in_pixel);
return ORBIS_VIDEO_OUT_ERROR_INVALID_PITCH;
}
if (attribute->tiling_mode < TilingMode::Tile || attribute->tiling_mode > TilingMode::Linear) {
LOG_ERROR(Lib_VideoOut, "Invalid tilingMode = {}",
static_cast<u32>(attribute->tiling_mode));
return ORBIS_VIDEO_OUT_ERROR_INVALID_TILING_MODE;
}
LOG_INFO(Lib_VideoOut,
"startIndex = {}, bufferNum = {}, pixelFormat = {}, aspectRatio = {}, "
"tilingMode = {}, width = {}, height = {}, pitchInPixel = {}, option = {:#x}",
startIndex, bufferNum, GetPixelFormatString(attribute->pixel_format),
attribute->aspect_ratio, static_cast<u32>(attribute->tiling_mode), attribute->width,
attribute->height, attribute->pitch_in_pixel, attribute->option);
auto& group = port->groups[group_index];
std::memcpy(&group.attrib, attribute, sizeof(BufferAttribute));
group.size_in_bytes =
attribute->height * attribute->pitch_in_pixel * PixelFormatBpp(attribute->pixel_format);
group.is_occupied = true;
for (u32 i = 0; i < bufferNum; i++) {
const uintptr_t address = reinterpret_cast<uintptr_t>(addresses[i]);
port->buffer_slots[startIndex + i] = VideoOutBuffer{
.group_index = group_index,
.address_left = address,
.address_right = 0,
};
LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex, address);
}
return group_index;
}
int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
if (attributeIndex >= MaxDisplayBufferGroups || !port->groups[attributeIndex].is_occupied) {
LOG_ERROR(Lib_VideoOut, "Invalid attribute index {}", attributeIndex);
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
auto& group = port->groups[attributeIndex];
group.is_occupied = false;
for (auto& buffer : port->buffer_slots) {
if (buffer.group_index != attributeIndex) {
continue;
}
buffer.group_index = -1;
}
return ORBIS_OK;
}
void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
Request req;
{
std::unique_lock lock{mutex};
submit_cond.wait_for(lock, timeout, [&] { return !requests.empty(); });
if (requests.empty()) {
return;
}
// Retrieve the request.
req = requests.front();
requests.pop();
}
// Present the frame.
renderer->Present(req.frame);
std::scoped_lock lock{mutex};
// Update flip status.
auto& flip_status = req.port->flip_status;
flip_status.count++;
flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
flip_status.tsc = Libraries::Kernel::sceKernelReadTsc();
flip_status.submitTsc = Libraries::Kernel::sceKernelReadTsc();
flip_status.flipArg = req.flip_arg;
flip_status.currentBuffer = req.index;
flip_status.flipPendingNum = static_cast<int>(requests.size());
// Trigger flip events for the port.
for (auto& event : req.port->flip_events) {
if (event != nullptr) {
event->triggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Kernel::EVFILT_VIDEO_OUT,
reinterpret_cast<void*>(req.flip_arg));
}
}
}
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg) {
const auto& buffer = port->buffer_slots[index];
const auto& group = port->groups[buffer.group_index];
auto* frame = renderer->PrepareFrame(group, buffer.address_left);
std::scoped_lock lock{mutex};
if (requests.size() >= 2) {
return false;
}
requests.push({
.frame = frame,
.port = port,
.index = index,
.flip_arg = flip_arg,
.submit_tsc = Libraries::Kernel::sceKernelReadTsc(),
});
port->flip_status.flipPendingNum = static_cast<int>(requests.size());
port->flip_status.gcQueueNum = 0;
submit_cond.notify_one();
return true;
}
void VideoOutDriver::Vblank() {
std::scoped_lock lock{mutex};
auto& vblank_status = main_port.vblank_status;
vblank_status.count++;
vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc();
}
} // namespace Libraries::VideoOut

View file

@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include "core/libraries/videoout/video_out.h"
namespace Vulkan {
struct Frame;
class RendererVulkan;
} // namespace Vulkan
namespace Libraries::VideoOut {
struct VideoOutPort {
bool is_open = false;
SceVideoOutResolutionStatus resolution;
std::array<VideoOutBuffer, MaxDisplayBuffers> buffer_slots;
std::array<BufferAttributeGroup, MaxDisplayBufferGroups> groups;
FlipStatus flip_status;
SceVideoOutVblankStatus vblank_status;
std::vector<Kernel::SceKernelEqueue> flip_events;
int flip_rate = 0;
s32 FindFreeGroup() const {
s32 index = 0;
while (index < groups.size() && groups[index].is_occupied) {
index++;
}
return index;
}
};
struct ServiceThreadParams {
u32 unknown;
bool set_priority;
u32 priority;
bool set_affinity;
u64 affinity;
};
class VideoOutDriver {
public:
explicit VideoOutDriver(u32 width, u32 height);
~VideoOutDriver();
int Open(const ServiceThreadParams* params);
void Close(s32 handle);
VideoOutPort* GetPort(s32 handle);
int RegisterBuffers(VideoOutPort* port, s32 startIndex, void* const* addresses, s32 bufferNum,
const BufferAttribute* attribute);
int UnregisterBuffers(VideoOutPort* port, s32 attributeIndex);
void Flip(std::chrono::microseconds timeout);
bool SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg);
void Vblank();
private:
struct Request {
Vulkan::Frame* frame;
VideoOutPort* port;
s32 index;
s64 flip_arg;
u64 submit_tsc;
};
std::mutex mutex;
VideoOutPort main_port{};
std::condition_variable_any submit_cond;
std::condition_variable done_cond;
std::queue<Request> requests;
std::unique_ptr<Vulkan::RendererVulkan> renderer;
bool is_neo{};
};
} // namespace Libraries::VideoOut

View file

@ -0,0 +1,253 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/config.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/videoout/driver.h"
#include "core/libraries/videoout/video_out.h"
#include "core/loader/symbols_resolver.h"
namespace Libraries::VideoOut {
static std::unique_ptr<VideoOutDriver> driver;
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat,
u32 tilingMode, u32 aspectRatio, u32 width,
u32 height, u32 pitchInPixel) {
LOG_INFO(Lib_VideoOut,
"pixelFormat = {}, tilingMode = {}, aspectRatio = {}, width = {}, height = {}, "
"pitchInPixel = {}",
GetPixelFormatString(pixelFormat), tilingMode, aspectRatio, width, height,
pitchInPixel);
std::memset(attribute, 0, sizeof(BufferAttribute));
attribute->pixel_format = static_cast<PixelFormat>(pixelFormat);
attribute->tiling_mode = static_cast<TilingMode>(tilingMode);
attribute->aspect_ratio = aspectRatio;
attribute->width = width;
attribute->height = height;
attribute->pitch_in_pixel = pitchInPixel;
attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE;
}
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) {
LOG_INFO(Lib_VideoOut, "handle = {}", handle);
auto* port = driver->GetPort(handle);
if (port == nullptr) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
if (eq == nullptr) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE;
}
Kernel::EqueueEvent event{};
event.isTriggered = false;
event.event.ident = SCE_VIDEO_OUT_EVENT_FLIP;
event.event.filter = Kernel::EVFILT_VIDEO_OUT;
event.event.udata = udata;
event.event.fflags = 0;
event.event.data = 0;
event.filter.data = port;
port->flip_events.push_back(eq);
return eq->addEvent(event);
}
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
s32 bufferNum, const BufferAttribute* attribute) {
if (!addresses || !attribute) {
LOG_ERROR(Lib_VideoOut, "Addresses are null");
return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
auto* port = driver->GetPort(handle);
if (!port || !port->is_open) {
LOG_ERROR(Lib_VideoOut, "Invalid handle = {}", handle);
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
return driver->RegisterBuffers(port, startIndex, addresses, bufferNum, attribute);
}
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) {
LOG_INFO(Lib_VideoOut, "called");
driver->GetPort(handle)->flip_rate = rate;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) {
LOG_INFO(Lib_VideoOut, "called");
s32 pending = driver->GetPort(handle)->flip_status.flipPendingNum;
return pending;
}
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg) {
auto* port = driver->GetPort(handle);
if (!port) {
LOG_ERROR(Lib_VideoOut, "Invalid handle = {}", handle);
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
if (flipMode != 1) {
LOG_WARNING(Lib_VideoOut, "flipmode = {}", flipMode);
}
ASSERT_MSG(bufferIndex != -1, "Blank output not supported");
if (bufferIndex < -1 || bufferIndex > 15) {
LOG_ERROR(Lib_VideoOut, "Invalid bufferIndex = {}", bufferIndex);
return ORBIS_VIDEO_OUT_ERROR_INVALID_INDEX;
}
if (port->buffer_slots[bufferIndex].group_index < 0) {
LOG_ERROR(Lib_VideoOut, "Slot in bufferIndex = {} is not registered", bufferIndex);
return ORBIS_VIDEO_OUT_ERROR_INVALID_INDEX;
}
LOG_INFO(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode,
flipArg);
if (!driver->SubmitFlip(port, bufferIndex, flipArg)) {
LOG_ERROR(Lib_VideoOut, "Flip queue is full");
return ORBIS_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) {
if (!status) {
LOG_ERROR(Lib_VideoOut, "Flip status is null");
return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
auto* port = driver->GetPort(handle);
if (!port) {
LOG_ERROR(Lib_VideoOut, "Invalid port handle = {}", handle);
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
*status = port->flip_status;
LOG_INFO(Lib_VideoOut,
"count = {}, processTime = {}, tsc = {}, submitTsc = {}, flipArg = {}, gcQueueNum = "
"{}, flipPendingNum = {}, currentBuffer = {}",
status->count, status->processTime, status->tsc, status->submitTsc, status->flipArg,
status->gcQueueNum, status->flipPendingNum, status->currentBuffer);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus* status) {
if (status == nullptr) {
return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
auto* port = driver->GetPort(handle);
if (!port) {
LOG_ERROR(Lib_VideoOut, "Invalid port handle = {}", handle);
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
*status = port->vblank_status;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
LOG_INFO(Lib_VideoOut, "called");
*status = driver->GetPort(handle)->resolution;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param) {
LOG_INFO(Lib_VideoOut, "called");
ASSERT(userId == UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM || userId == 0);
ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN);
ASSERT(param == nullptr);
if (index != 0) {
LOG_ERROR(Lib_VideoOut, "Index != 0");
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
auto* params = reinterpret_cast<const ServiceThreadParams*>(param);
int handle = driver->Open(params);
if (handle < 0) {
LOG_ERROR(Lib_VideoOut, "All available handles are open");
return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
return handle;
}
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle) {
driver->Close(handle);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) {
auto* port = driver->GetPort(handle);
if (!port || !port->is_open) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
return driver->UnregisterBuffers(port, attributeIndex);
}
void Flip(std::chrono::microseconds micros) {
return driver->Flip(micros);
}
void Vblank() {
return driver->Vblank();
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
driver = std::make_unique<VideoOutDriver>(Config::getScreenWidth(), Config::getScreenHeight());
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetFlipStatus);
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSubmitFlip);
LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutRegisterBuffers);
LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutAddFlipEvent);
LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutSetFlipRate);
LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutSetBufferAttribute);
LIB_FUNCTION("6kPnj51T62Y", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetResolutionStatus);
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutOpen);
LIB_FUNCTION("zgXifHT9ErY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutIsFlipPending);
LIB_FUNCTION("N5KDtkIjjJ4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutUnregisterBuffers);
LIB_FUNCTION("uquVH4-Du78", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutClose);
LIB_FUNCTION("1FZBKy8HeNU", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetVblankStatus);
// openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen);
LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutSetFlipRate);
LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutAddFlipEvent);
LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutSetBufferAttribute);
LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutRegisterBuffers);
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutSubmitFlip);
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutGetFlipStatus);
}
} // namespace Libraries::VideoOut

View file

@ -3,14 +3,14 @@
#pragma once
#include <string>
#include "core/libraries/kernel/event_queues.h"
#include "core/libraries/videoout/buffer.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace HLE::Libs::Graphics::VideoOut {
namespace Libraries::VideoOut {
using SceUserServiceUserId = s32; // TODO move it to proper place
@ -46,35 +46,20 @@ enum SceVideoOutEventId : s16 {
SCE_VIDEO_OUT_EVENT_PRE_VBLANK_START = 2
};
enum SceVideoOutTilingMode : s32 {
SCE_VIDEO_OUT_TILING_MODE_TILE = 0,
SCE_VIDEO_OUT_TILING_MODE_LINEAR = 1
enum AspectRatioMode : s32 {
SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0,
};
enum AspectRatioMode : s32 { SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0 };
struct SceVideoOutBufferAttribute {
s32 pixelFormat;
s32 tilingMode;
s32 aspectRatio;
u32 width;
u32 height;
u32 pitchInPixel;
u32 option;
u32 reserved0;
u64 reserved1;
};
struct SceVideoOutFlipStatus {
struct FlipStatus {
u64 count = 0;
u64 processTime = 0;
u64 tsc = 0;
s64 flipArg = 0;
s64 flipArg = -1;
u64 submitTsc = 0;
u64 reserved0 = 0;
s32 gcQueueNum = 0;
s32 flipPendingNum = 0;
s32 currentBuffer = 0;
s32 currentBuffer = -1;
u32 reserved1 = 0;
};
@ -99,34 +84,24 @@ struct SceVideoOutVblankStatus {
u8 pad1[7] = {};
};
struct VideoOutBufferSetInternal {
SceVideoOutBufferAttribute attr;
int start_index = 0;
int num = 0;
int set_id = 0;
};
void videoOutInit(u32 width, u32 height);
std::string getPixelFormatString(s32 format);
void videoOutRegisterLib(Core::Loader::SymbolsResolver* sym);
bool videoOutFlip(u32 micros);
void VideoOutVblank();
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(SceVideoOutBufferAttribute* attribute,
u32 pixelFormat, u32 tilingMode, u32 aspectRatio,
u32 width, u32 height, u32 pitchInPixel);
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Libraries::Kernel::SceKernelEqueue eq, s32 handle,
void* udata);
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat,
u32 tilingMode, u32 aspectRatio, u32 width,
u32 height, u32 pitchInPixel);
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata);
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
s32 bufferNum,
const SceVideoOutBufferAttribute* attribute);
s32 bufferNum, const BufferAttribute* attribute);
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate);
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle);
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg);
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, SceVideoOutFlipStatus* status);
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status);
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status);
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param);
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
} // namespace HLE::Libs::Graphics::VideoOut
void Flip(std::chrono::microseconds micros);
void Vblank();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::VideoOut

View file

@ -1,353 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/core.h>
#include "common/singleton.h"
#include "common/version.h"
#include "core/PS4/HLE/Graphics/graphics_render.h"
#include "core/PS4/HLE/Graphics/video_out.h"
#include "core/libraries/pad/pad.h"
#include "emulator.h"
#include "input/controller.h"
#include "vulkan_util.h"
namespace Emu {
bool m_emu_needs_exit = false;
bool m_game_is_paused = {false};
double m_current_time_seconds = {0.0};
double m_previous_time_seconds = {0.0};
int m_update_num = {0};
int m_frame_num = {0};
double m_update_time_seconds = {0.0};
double m_current_fps = {0.0};
int m_max_updates_per_frame = {4};
double m_update_fixed_time = 1.0 / 60.0;
int m_fps_frames_num = {0};
double m_fps_start_time = {0};
void emuInit(u32 width, u32 height) {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
window_ctx->m_graphic_ctx.screen_width = width;
window_ctx->m_graphic_ctx.screen_height = height;
}
void checkAndWaitForGraphicsInit() {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
std::unique_lock lock{window_ctx->m_mutex};
while (!window_ctx->m_is_graphic_initialized) {
window_ctx->m_graphic_initialized_cond.wait(lock);
}
}
static void CreateSdlWindow(WindowCtx* ctx) {
int width = static_cast<int>(ctx->m_graphic_ctx.screen_width);
int height = static_cast<int>(ctx->m_graphic_ctx.screen_height);
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fmt::print("{}\n", SDL_GetError());
std::exit(0);
}
std::string title = "shadps4 v" + std::string(Common::VERSION);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str());
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
SDL_SetNumberProperty(
props, "flags",
(static_cast<uint32_t>(SDL_WINDOW_HIDDEN) | static_cast<uint32_t>(SDL_WINDOW_VULKAN)));
ctx->m_window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
ctx->is_window_hidden =
true; // hide window until we need to show something (should draw something in buffers)
if (ctx->m_window == nullptr) {
fmt::print("{}\n", SDL_GetError());
std::exit(0);
}
SDL_SetWindowResizable(ctx->m_window, SDL_FALSE); // we don't support resizable atm
}
static void update() {
static double lag = 0.0;
lag += m_current_time_seconds - m_previous_time_seconds;
int num = 0;
while (lag >= m_update_fixed_time) {
if (num < m_max_updates_per_frame) {
m_update_num++;
num++;
m_update_time_seconds = m_update_num * m_update_fixed_time;
}
lag -= m_update_fixed_time;
}
}
static void calculateFps(double game_time_s) {
m_previous_time_seconds = m_current_time_seconds;
m_current_time_seconds = game_time_s;
m_frame_num++;
m_fps_frames_num++;
if (m_current_time_seconds - m_fps_start_time > 0.25f) {
m_current_fps =
static_cast<double>(m_fps_frames_num) / (m_current_time_seconds - m_fps_start_time);
m_fps_frames_num = 0;
m_fps_start_time = m_current_time_seconds;
}
}
void emuRun() {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
{
// init window and wait until init finishes
std::scoped_lock lock{window_ctx->m_mutex};
CreateSdlWindow(window_ctx);
Graphics::Vulkan::vulkanCreate(window_ctx);
window_ctx->m_is_graphic_initialized = true;
window_ctx->m_graphic_initialized_cond.notify_one();
calculateFps(0); // TODO: Proper fps
}
bool exit_loop = false;
for (;;) {
if (exit_loop) {
break;
}
SDL_Event event;
if (SDL_PollEvent(&event) != 0) {
switch (event.type) {
case SDL_EVENT_QUIT:
m_emu_needs_exit = true;
break;
case SDL_EVENT_TERMINATING:
m_emu_needs_exit = true;
break;
case SDL_EVENT_WILL_ENTER_BACKGROUND:
break;
case SDL_EVENT_DID_ENTER_BACKGROUND:
break;
case SDL_EVENT_WILL_ENTER_FOREGROUND:
break;
case SDL_EVENT_DID_ENTER_FOREGROUND:
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (event.type == SDL_EVENT_KEY_DOWN) {
if (event.key.keysym.sym == SDLK_p) {
m_game_is_paused = !m_game_is_paused;
}
}
keyboardEvent(&event);
break;
}
continue;
}
if (m_game_is_paused) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_EVENT_QUIT:
m_emu_needs_exit = true;
break;
case SDL_EVENT_TERMINATING:
m_emu_needs_exit = true;
break;
case SDL_EVENT_WILL_ENTER_BACKGROUND:
break;
case SDL_EVENT_DID_ENTER_BACKGROUND:
break;
case SDL_EVENT_WILL_ENTER_FOREGROUND:
break;
case SDL_EVENT_DID_ENTER_FOREGROUND:
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (event.type == SDL_EVENT_KEY_DOWN) {
if (event.key.keysym.sym == SDLK_p) {
m_game_is_paused = !m_game_is_paused;
}
}
keyboardEvent(&event);
break;
}
exit_loop = m_emu_needs_exit;
continue;
}
exit_loop = m_emu_needs_exit;
if (!m_game_is_paused) {
if (!exit_loop) {
update();
}
if (!exit_loop) {
if (HLE::Libs::Graphics::VideoOut::videoOutFlip(100000)) { // flip every 0.1 sec
calculateFps(0); // TODO: Proper fps
}
}
HLE::Libs::Graphics::VideoOut::VideoOutVblank();
}
}
}
HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
std::scoped_lock lock{window_ctx->m_mutex};
return &window_ctx->m_graphic_ctx;
}
void updateSDLTitle() {
const auto title = fmt::format("shadps4 v {} FPS: {}", Common::VERSION, m_current_fps);
auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
SDL_SetWindowTitle(window_ctx->m_window, title.c_str());
}
void DrawBuffer(HLE::Libs::Graphics::VideoOutVulkanImage* image) {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
if (window_ctx->is_window_hidden) {
SDL_ShowWindow(window_ctx->m_window);
window_ctx->is_window_hidden = false;
}
window_ctx->swapchain.current_index = static_cast<u32>(-1);
auto result = vkAcquireNextImageKHR(window_ctx->m_graphic_ctx.m_device,
window_ctx->swapchain.swapchain, UINT64_MAX, nullptr,
VK_NULL_HANDLE, &window_ctx->swapchain.current_index);
if (result != VK_SUCCESS) {
fmt::print("Can't aquireNextImage\n");
std::exit(0);
}
if (window_ctx->swapchain.current_index == static_cast<u32>(-1)) {
fmt::print("Unsupported:swapchain current index is -1\n");
std::exit(0);
}
auto blt_src_image = image;
auto blt_dst_image = window_ctx->swapchain;
if (blt_src_image == nullptr) {
fmt::print("blt_src_image is null\n");
std::exit(0);
}
GPU::CommandBuffer buffer(10);
auto* vk_buffer = buffer.getPool()->buffers[buffer.getIndex()];
buffer.begin();
Graphics::Vulkan::vulkanBlitImage(&buffer, blt_src_image, &blt_dst_image);
VkImageMemoryBarrier pre_present_barrier{};
pre_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
pre_present_barrier.pNext = nullptr;
pre_present_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
pre_present_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
pre_present_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
pre_present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
pre_present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
pre_present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
pre_present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
pre_present_barrier.subresourceRange.baseMipLevel = 0;
pre_present_barrier.subresourceRange.levelCount = 1;
pre_present_barrier.subresourceRange.baseArrayLayer = 0;
pre_present_barrier.subresourceRange.layerCount = 1;
pre_present_barrier.image =
window_ctx->swapchain.swapchain_images[window_ctx->swapchain.current_index];
vkCmdPipelineBarrier(vk_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1,
&pre_present_barrier);
buffer.end();
buffer.executeWithSemaphore();
buffer.waitForFence(); // HACK: The whole vulkan backend needs a rewrite
VkPresentInfoKHR present{};
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.pNext = nullptr;
present.swapchainCount = 1;
present.pSwapchains = &window_ctx->swapchain.swapchain;
present.pImageIndices = &window_ctx->swapchain.current_index;
present.pWaitSemaphores = &buffer.getPool()->semaphores[buffer.getIndex()];
present.waitSemaphoreCount = 1;
present.pResults = nullptr;
const auto& queue = window_ctx->m_graphic_ctx.queues[10];
if (queue.mutex != nullptr) {
fmt::print("queue.mutexe is null\n");
std::exit(0);
}
result = vkQueuePresentKHR(queue.vk_queue, &present);
if (result != VK_SUCCESS) {
fmt::print("vkQueuePresentKHR failed\n");
std::exit(0);
}
updateSDLTitle();
}
void keyboardEvent(SDL_Event* event) {
using Libraries::LibPad::ScePadButton;
if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) {
u32 button = 0;
switch (event->key.keysym.sym) {
case SDLK_UP:
button = ScePadButton::UP;
break;
case SDLK_DOWN:
button = ScePadButton::DOWN;
break;
case SDLK_LEFT:
button = ScePadButton::LEFT;
break;
case SDLK_RIGHT:
button = ScePadButton::RIGHT;
break;
case SDLK_KP_8:
button = ScePadButton ::TRIANGLE;
break;
case SDLK_KP_6:
button = ScePadButton ::CIRCLE;
break;
case SDLK_KP_2:
button = ScePadButton ::CROSS;
break;
case SDLK_KP_4:
button = ScePadButton ::SQUARE;
break;
case SDLK_RETURN:
button = ScePadButton ::OPTIONS;
break;
default:
break;
}
if (button != 0) {
auto* controller = Common::Singleton<Input::GameController>::Instance();
controller->checKButton(0, button, event->type == SDL_EVENT_KEY_DOWN);
}
}
}
} // namespace Emu

View file

@ -1,92 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <SDL.h>
#include <core/PS4/HLE/Graphics/graphics_ctx.h>
#include <condition_variable>
#include <mutex>
#include <vector>
namespace Emu {
struct VulkanExt {
bool enable_validation_layers = false;
char const* const* required_extensions;
u32 required_extensions_count;
std::vector<VkExtensionProperties> available_extensions;
std::vector<const char*> required_layers;
std::vector<VkLayerProperties> available_layers;
};
struct VulkanSurfaceCapabilities {
VkSurfaceCapabilitiesKHR capabilities{};
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> present_modes;
bool is_format_srgb_bgra32 = false;
bool is_format_unorm_bgra32 = false;
};
struct VulkanQueueInfo {
u32 family = 0;
u32 index = 0;
bool is_graphics = false;
bool is_compute = false;
bool is_transfer = false;
bool is_present = false;
};
struct VulkanQueues {
u32 family_count = 0;
std::vector<VulkanQueueInfo> available;
std::vector<VulkanQueueInfo> graphics;
std::vector<VulkanQueueInfo> compute;
std::vector<VulkanQueueInfo> transfer;
std::vector<VulkanQueueInfo> present;
std::vector<u32> family_used;
};
struct VulkanSwapchain {
VkSwapchainKHR swapchain = nullptr;
VkFormat swapchain_format = VK_FORMAT_UNDEFINED;
VkExtent2D swapchain_extent = {};
std::vector<VkImage> swapchain_images;
std::vector<VkImageView> swapchain_image_views;
u32 swapchain_images_count = 0;
VkSemaphore present_complete_semaphore = nullptr;
VkFence present_complete_fence = nullptr;
u32 current_index = 0;
};
struct WindowCtx {
HLE::Libs::Graphics::GraphicCtx m_graphic_ctx;
std::mutex m_mutex;
bool m_is_graphic_initialized = false;
std::condition_variable m_graphic_initialized_cond;
SDL_Window* m_window = nullptr;
bool is_window_hidden = true;
VkSurfaceKHR m_surface = nullptr;
VulkanSurfaceCapabilities m_surface_capabilities;
VulkanSwapchain swapchain;
};
struct EmuPrivate {
EmuPrivate() = default;
std::mutex m_mutex;
HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr;
void* data1 = nullptr;
void* data2 = nullptr;
u32 m_screen_width = {0};
u32 m_screen_height = {0};
};
void emuInit(u32 width, u32 height);
void emuRun();
void checkAndWaitForGraphicsInit();
HLE::Libs::Graphics::GraphicCtx* getGraphicCtx();
void DrawBuffer(HLE::Libs::Graphics::VideoOutVulkanImage* image);
void keyboardEvent(SDL_Event* event);
} // namespace Emu

View file

@ -74,7 +74,7 @@ void GameController::addState(const State& state) {
m_states_num++;
}
void GameController::checKButton(int id, u32 button, bool isPressed) {
void GameController::checkButton(int id, u32 button, bool isPressed) {
std::scoped_lock lock{m_mutex};
auto state = getLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();

View file

@ -23,7 +23,7 @@ public:
void readState(State* state, bool* isConnected, int* connectedCount);
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
State getLastState() const;
void checKButton(int id, u32 button, bool isPressed);
void checkButton(int id, u32 button, bool isPressed);
void addState(const State& state);
private:

View file

@ -12,14 +12,17 @@
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "core/PS4/HLE/Graphics/video_out.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/thread_management.h"
#include "core/libraries/libc/libc.h"
#include "core/libraries/libs.h"
#include "core/libraries/videoout/video_out.h"
#include "core/linker.h"
#include "core/tls.h"
#include "emulator.h"
#include "input/controller.h"
#include "sdl_window.h"
Frontend::WindowSDL* g_window;
int main(int argc, char* argv[]) {
if (argc == 1) {
@ -31,10 +34,12 @@ int main(int argc, char* argv[]) {
Common::Log::Initialize();
Common::Log::Start();
Libraries::Kernel::init_pthreads();
auto width = Config::getScreenWidth();
auto height = Config::getScreenHeight();
Emu::emuInit(width, height);
HLE::Libs::Graphics::VideoOut::videoOutInit(width, height);
s32 width = Config::getScreenWidth();
s32 height = Config::getScreenHeight();
auto* controller = Common::Singleton<Input::GameController>::Instance();
Frontend::WindowSDL window{width, height, controller};
g_window = &window;
// Argument 1 is the path of self file to boot
const char* const path = argv[1];
@ -47,7 +52,8 @@ int main(int argc, char* argv[]) {
Libraries::InitHLELibs(&linker->getHLESymbols());
Core::InstallTlsHandler();
linker->LoadModule(path);
// check if there is a libc.prx in sce_module folder
// Check if there is a libc.prx in sce_module folder
bool found = false;
if (Config::isLleLibc()) {
std::filesystem::path sce_module_folder = p.parent_path() / "sce_module";
@ -62,16 +68,21 @@ int main(int argc, char* argv[]) {
}
}
}
if (!found) // load HLE libc
{
Core::Libraries::LibC::libcSymbolsRegister(&linker->getHLESymbols());
if (!found) {
Libraries::LibC::libcSymbolsRegister(&linker->getHLESymbols());
}
std::jthread mainthread([linker](std::stop_token stop_token, void*) { linker->Execute(); },
nullptr);
std::thread mainthread([linker]() { linker->Execute(); });
Discord::RPC discordRPC;
discordRPC.init();
discordRPC.update(Discord::RPCStatus::Idling, "");
Emu::emuRun();
static constexpr std::chrono::microseconds FlipPeriod{100000};
while (window.isOpen()) {
window.waitEvent();
Libraries::VideoOut::Flip(FlipPeriod);
Libraries::VideoOut::Vblank();
}
discordRPC.stop();
return 0;

View file

@ -6,6 +6,9 @@
#include "qt_gui/game_install_dialog.h"
#include "qt_gui/gui_settings.h"
#include "qt_gui/main_window.h"
#include "src/sdl_window.h"
Frontend::WindowSDL* g_window;
int main(int argc, char* argv[]) {
QApplication a(argc, argv);

135
src/sdl_window.cpp Normal file
View file

@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL.h>
#include "common/assert.h"
#include "common/version.h"
#include "core/libraries/pad/pad.h"
#include "input/controller.h"
#include "sdl_window.h"
namespace Frontend {
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_)
: width{width_}, height{height_}, controller{controller_} {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError());
}
const std::string title = "shadps4 v" + std::string(Common::VERSION);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str());
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
SDL_SetNumberProperty(props, "flags", SDL_WINDOW_VULKAN);
window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
if (window == nullptr) {
UNREACHABLE_MSG("Failed to create window handle: {}", SDL_GetError());
}
// We don't support resizable at the moment.
SDL_SetWindowResizable(window, SDL_FALSE);
#if defined(SDL_PLATFORM_WIN32)
window_info.type = WindowSystemType::Windows;
window_info.render_surface =
SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
#elif defined(SDL_PLATFORM_LINUX)
if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) {
window_info.type = WindowSystemType::X11;
window_info.display_connection = SDL_GetProperty(SDL_GetWindowProperties(window),
SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
window_info.render_surface = (void*)SDL_GetNumberProperty(
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
} else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) {
window_info.type = WindowSystemType::Wayland;
window_info.display_connection = SDL_GetProperty(
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
window_info.render_surface = SDL_GetProperty(SDL_GetWindowProperties(window),
SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL);
}
#endif
}
WindowSDL::~WindowSDL() = default;
void WindowSDL::waitEvent() {
// Called on main thread
SDL_Event event;
if (!SDL_PollEvent(&event)) {
return;
}
switch (event.type) {
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
onResize();
break;
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_EXPOSED:
is_shown = event.type == SDL_EVENT_WINDOW_EXPOSED;
onResize();
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
onKeyPress(&event);
break;
case SDL_EVENT_QUIT:
is_open = false;
break;
default:
break;
}
}
void WindowSDL::onResize() {
SDL_GetWindowSizeInPixels(window, &width, &height);
}
void WindowSDL::onKeyPress(const SDL_Event* event) {
using Libraries::LibPad::ScePadButton;
u32 button = 0;
switch (event->key.keysym.sym) {
case SDLK_UP:
button = ScePadButton::UP;
break;
case SDLK_DOWN:
button = ScePadButton::DOWN;
break;
case SDLK_LEFT:
button = ScePadButton::LEFT;
break;
case SDLK_RIGHT:
button = ScePadButton::RIGHT;
break;
case SDLK_KP_8:
button = ScePadButton::TRIANGLE;
break;
case SDLK_KP_6:
button = ScePadButton::CIRCLE;
break;
case SDLK_KP_2:
button = ScePadButton::CROSS;
break;
case SDLK_KP_4:
button = ScePadButton::SQUARE;
break;
case SDLK_RETURN:
button = ScePadButton::OPTIONS;
break;
default:
break;
}
if (button != 0) {
controller->checkButton(0, button, event->type == SDL_EVENT_KEY_DOWN);
}
}
} // namespace Frontend

77
src/sdl_window.h Normal file
View file

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
struct SDL_Window;
union SDL_Event;
namespace Input {
class GameController;
}
namespace Frontend {
enum class WindowSystemType : u8 {
Headless,
Windows,
X11,
Wayland,
};
struct WindowSystemInfo {
// Window system type. Determines which GL context or Vulkan WSI is used.
WindowSystemType type = WindowSystemType::Headless;
// Connection to a display server. This is used on X11 and Wayland platforms.
void* display_connection = nullptr;
// Render surface. This is a pointer to the native window handle, which depends
// on the platform. e.g. HWND for Windows, Window for X11. If the surface is
// set to nullptr, the video backend will run in headless mode.
void* render_surface = nullptr;
// Scale of the render surface. For hidpi systems, this will be >1.
float render_surface_scale = 1.0f;
};
class WindowSDL {
public:
explicit WindowSDL(s32 width, s32 height, Input::GameController* controller);
~WindowSDL();
s32 getWidth() const {
return width;
}
s32 getHeight() const {
return height;
}
bool isOpen() const {
return is_open;
}
WindowSystemInfo getWindowInfo() const {
return window_info;
}
void waitEvent();
private:
void onResize();
void onKeyPress(const SDL_Event* event);
private:
s32 width;
s32 height;
Input::GameController* controller;
WindowSystemInfo window_info{};
SDL_Window* window{};
bool is_shown{};
bool is_open{true};
};
} // namespace Frontend

View file

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <climits>
#include <utility>
#include "common/types.h"
namespace VideoCore {
// Based on Table 8.13 Data and Image Formats in Sea Islands Series Instruction Set Architecture
enum class PixelFormat : u32 {
Invalid,
R32G32B32A32_Float,
B32G32R32A32_Float,
R32G32B32X32_Float,
B32G32R32X32_Float,
R32G32B32A32_Uint,
R32G32B32A32_Sint,
R32G32B32_Float,
R32G32B32_Uint,
R32G32B32_Sint,
R16G16B16A16_Float,
R16G16B16X16_Float,
B16G16R16X16_Float,
R16G16B16A16_Uint,
R16G16B16A16_Sint,
R16G16B16A16_Unorm,
B16G16R16A16_Unorm,
R16G16B16X16_Unorm,
B16G16R16X16_Unorm,
R16G16B16A16_Snorm,
L32A32_Float,
R32G32_Float,
R32G32_Uint,
R32G32_Sint,
R11G11B10_Float,
R8G8B8A8_Unorm,
R8G8B8X8_Unorm,
R8G8B8A8_UnormSrgb,
R8G8B8X8_UnormSrgb,
R8G8B8A8_Uint,
R8G8B8A8_Snorm,
R8G8B8A8_Sint,
L16A16_Float,
R16G16_Float,
L16A16_Unorm,
R16G16_Unorm,
R16G16_Uint,
R16G16_Snorm,
R16G16_Sint,
R32_Float,
L32_Float,
A32_Float,
R32_Uint,
R32_Sint,
R8G8_Unorm,
R8G8_Uint,
R8G8_Snorm,
R8G8_Sint,
L8A8_Unorm,
L8A8_UnormSrgb,
R16_Float,
L16_Float,
A16_Float,
R16_Unorm,
L16_Unorm,
A16_Unorm,
R16_Uint,
R16_Snorm,
R16_Sint,
R8_Unorm,
L8_Unorm,
L8_UnormSrgb,
R8_Uint,
R8_Snorm,
R8_Sint,
A8_Unorm,
};
constexpr bool IsDepthStencilFormat(PixelFormat format) {
return false;
}
} // namespace VideoCore

View file

@ -0,0 +1,352 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include <vk_mem_alloc.h>
namespace Vulkan {
bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format format) {
const vk::FormatProperties props{physical_device.getFormatProperties(format)};
return static_cast<bool>(props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eBlitDst);
}
[[nodiscard]] vk::ImageSubresourceLayers MakeImageSubresourceLayers() {
return vk::ImageSubresourceLayers{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
};
}
[[nodiscard]] vk::ImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width,
s32 swapchain_height) {
return vk::ImageBlit{
.srcSubresource = MakeImageSubresourceLayers(),
.srcOffsets =
std::array{
vk::Offset3D{
.x = 0,
.y = 0,
.z = 0,
},
vk::Offset3D{
.x = frame_width,
.y = frame_height,
.z = 1,
},
},
.dstSubresource = MakeImageSubresourceLayers(),
.dstOffsets =
std::array{
vk::Offset3D{
.x = 0,
.y = 0,
.z = 0,
},
vk::Offset3D{
.x = swapchain_width,
.y = swapchain_height,
.z = 1,
},
},
};
}
RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_)
: window{window_}, instance{window, Config::getGpuId()}, scheduler{instance},
swapchain{instance, window}, texture_cache{instance, scheduler} {
const u32 num_images = swapchain.GetImageCount();
const vk::Device device = instance.GetDevice();
const vk::CommandPoolCreateInfo pool_info = {
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer |
vk::CommandPoolCreateFlagBits::eTransient,
.queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(),
};
command_pool = device.createCommandPoolUnique(pool_info);
const vk::CommandBufferAllocateInfo alloc_info = {
.commandPool = *command_pool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = num_images,
};
const auto cmdbuffers = device.allocateCommandBuffers(alloc_info);
present_frames.resize(num_images);
for (u32 i = 0; i < num_images; i++) {
Frame& frame = present_frames[i];
frame.cmdbuf = cmdbuffers[i];
frame.render_ready = device.createSemaphore({});
frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
free_queue.push(&frame);
}
}
RendererVulkan::~RendererVulkan() {
scheduler.Finish();
const vk::Device device = instance.GetDevice();
for (auto& frame : present_frames) {
vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation);
device.destroyImageView(frame.image_view);
device.destroySemaphore(frame.render_ready);
device.destroyFence(frame.present_done);
}
}
void RendererVulkan::RecreateFrame(Frame* frame, u32 width, u32 height) {
const vk::Device device = instance.GetDevice();
if (frame->image_view) {
device.destroyImageView(frame->image_view);
}
if (frame->image) {
vmaDestroyImage(instance.GetAllocator(), frame->image, frame->allocation);
}
const vk::Format format = swapchain.GetSurfaceFormat().format;
const vk::ImageCreateInfo image_info = {
.imageType = vk::ImageType::e2D,
.format = format,
.extent = {width, height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eTransferSrc,
};
const VmaAllocationCreateInfo alloc_info = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
};
VkImage unsafe_image{};
VkImageCreateInfo unsafe_image_info = static_cast<VkImageCreateInfo>(image_info);
VkResult result = vmaCreateImage(instance.GetAllocator(), &unsafe_image_info, &alloc_info,
&unsafe_image, &frame->allocation, nullptr);
if (result != VK_SUCCESS) [[unlikely]] {
LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}",
vk::to_string(vk::Result{result}));
UNREACHABLE();
}
frame->image = vk::Image{unsafe_image};
const vk::ImageViewCreateInfo view_info = {
.image = frame->image,
.viewType = vk::ImageViewType::e2D,
.format = format,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
frame->image_view = device.createImageView(view_info);
frame->width = width;
frame->height = height;
}
Frame* RendererVulkan::PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute,
VAddr cpu_address) {
// Request presentation image from the texture cache.
auto& image = texture_cache.FindDisplayBuffer(attribute, cpu_address);
// Request a free presentation frame.
Frame* frame = GetRenderFrame();
// Post-processing (Anti-aliasing, FSR etc) goes here. For now just blit to the frame image.
const vk::ImageMemoryBarrier pre_barrier{
.srcAccessMask = vk::AccessFlagBits::eTransferRead,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eGeneral,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = frame->image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
{}, {}, pre_barrier);
cmdbuf.blitImage(
image.image, vk::ImageLayout::eGeneral, frame->image, vk::ImageLayout::eGeneral,
MakeImageBlit(image.info.size.width, image.info.size.height, frame->width, frame->height),
vk::Filter::eLinear);
// Flush pending vulkan operations.
scheduler.Flush(frame->render_ready);
return frame;
}
void RendererVulkan::Present(Frame* frame) {
swapchain.AcquireNextImage();
const vk::Image swapchain_image = swapchain.Image();
const vk::CommandBufferBeginInfo begin_info = {
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
const vk::CommandBuffer cmdbuf = frame->cmdbuf;
cmdbuf.begin(begin_info);
const vk::Extent2D extent = swapchain.GetExtent();
const std::array pre_barriers{
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eNone,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapchain_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eGeneral,
.newLayout = vk::ImageLayout::eGeneral,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = frame->image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
};
const vk::ImageMemoryBarrier post_barrier{
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eMemoryRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::ePresentSrcKHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapchain_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
{}, {}, pre_barriers);
cmdbuf.blitImage(frame->image, vk::ImageLayout::eGeneral, swapchain_image,
vk::ImageLayout::eTransferDstOptimal,
MakeImageBlit(frame->width, frame->height, extent.width, extent.height),
vk::Filter::eLinear);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier);
cmdbuf.end();
static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = {
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eAllGraphics,
};
const vk::Semaphore present_ready = swapchain.GetPresentReadySemaphore();
const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore();
const std::array wait_semaphores = {image_acquired, frame->render_ready};
vk::SubmitInfo submit_info = {
.waitSemaphoreCount = static_cast<u32>(wait_semaphores.size()),
.pWaitSemaphores = wait_semaphores.data(),
.pWaitDstStageMask = wait_stage_masks.data(),
.commandBufferCount = 1u,
.pCommandBuffers = &cmdbuf,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &present_ready,
};
try {
std::scoped_lock submit_lock{scheduler.submit_mutex};
instance.GetGraphicsQueue().submit(submit_info, frame->present_done);
} catch (vk::DeviceLostError& err) {
LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what());
UNREACHABLE();
}
swapchain.Present();
// Free the frame for reuse
std::scoped_lock fl{free_mutex};
free_queue.push(frame);
free_cv.notify_one();
}
Frame* RendererVulkan::GetRenderFrame() {
// Wait for free presentation frames
Frame* frame;
{
std::unique_lock lock{free_mutex};
free_cv.wait(lock, [this] { return !free_queue.empty(); });
// Take the frame from the queue
frame = free_queue.front();
free_queue.pop();
}
const vk::Device device = instance.GetDevice();
vk::Result result{};
const auto wait = [&]() {
result = device.waitForFences(frame->present_done, false, std::numeric_limits<u64>::max());
return result;
};
// Wait for the presentation to be finished so all frame resources are free
while (wait() != vk::Result::eSuccess) {
// Retry if the waiting times out
if (result == vk::Result::eTimeout) {
continue;
}
}
// Reset fence for next queue submission.
device.resetFences(frame->present_done);
// If the window dimentions changed, recreate this frame
if (frame->width != window.getWidth() || frame->height != window.getHeight()) {
RecreateFrame(frame, window.getWidth(), window.getHeight());
}
return frame;
}
} // namespace Vulkan

View file

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/texture_cache/texture_cache.h"
namespace Frontend {
class WindowSDL;
}
namespace Vulkan {
struct Frame {
u32 width;
u32 height;
VmaAllocation allocation;
vk::Image image;
vk::ImageView image_view;
vk::Semaphore render_ready;
vk::Fence present_done;
vk::CommandBuffer cmdbuf;
};
class RendererVulkan {
public:
explicit RendererVulkan(Frontend::WindowSDL& window);
~RendererVulkan();
Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute,
VAddr cpu_address);
void Present(Frame* frame);
void RecreateFrame(Frame* frame, u32 width, u32 height);
private:
Frame* GetRenderFrame();
private:
Frontend::WindowSDL& window;
Instance instance;
Scheduler scheduler;
Swapchain swapchain;
VideoCore::TextureCache texture_cache;
vk::UniqueCommandPool command_pool;
std::vector<Frame> present_frames;
std::queue<Frame*> free_queue;
std::mutex free_mutex;
std::condition_variable free_cv;
std::condition_variable_any frame_cv;
};
} // namespace Vulkan

View file

@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/renderer_vulkan/vk_common.h"
// Implement vma functions
#define VMA_IMPLEMENTATION
#include <vk_mem_alloc.h>
// Store the dispatch loader here
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE

View file

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// Include vulkan-hpp header
#define VK_ENABLE_BETA_EXTENSIONS
#define VK_NO_PROTOTYPES
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
#define VULKAN_HPP_NO_CONSTRUCTORS
#define VULKAN_HPP_NO_STRUCT_SETTERS
#include <vulkan/vulkan.hpp>
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1

View file

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
#include "video_core/renderer_vulkan/vk_instance.h"
namespace Vulkan {
DescriptorUpdateQueue::DescriptorUpdateQueue(const Instance& instance, u32 descriptor_write_max_)
: device{instance.GetDevice()}, descriptor_write_max{descriptor_write_max_} {
descriptor_infos = std::make_unique<DescriptorInfoUnion[]>(descriptor_write_max);
descriptor_writes = std::make_unique<vk::WriteDescriptorSet[]>(descriptor_write_max);
}
void DescriptorUpdateQueue::Flush() {
if (descriptor_write_end == 0) {
return;
}
device.updateDescriptorSets({std::span(descriptor_writes.get(), descriptor_write_end)}, {});
descriptor_write_end = 0;
}
void DescriptorUpdateQueue::AddStorageImage(vk::DescriptorSet target, u8 binding,
vk::ImageView image_view,
vk::ImageLayout image_layout) {
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
Flush();
}
auto& image_info = descriptor_infos[descriptor_write_end].image_info;
image_info.sampler = VK_NULL_HANDLE;
image_info.imageView = image_view;
image_info.imageLayout = image_layout;
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
.dstSet = target,
.dstBinding = binding,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageImage,
.pImageInfo = &image_info,
};
}
void DescriptorUpdateQueue::AddImageSampler(vk::DescriptorSet target, u8 binding, u8 array_index,
vk::ImageView image_view, vk::Sampler sampler,
vk::ImageLayout image_layout) {
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
Flush();
}
auto& image_info = descriptor_infos[descriptor_write_end].image_info;
image_info.sampler = sampler;
image_info.imageView = image_view;
image_info.imageLayout = image_layout;
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
.dstSet = target,
.dstBinding = binding,
.dstArrayElement = array_index,
.descriptorCount = 1,
.descriptorType =
sampler ? vk::DescriptorType::eCombinedImageSampler : vk::DescriptorType::eSampledImage,
.pImageInfo = &image_info,
};
}
void DescriptorUpdateQueue::AddBuffer(vk::DescriptorSet target, u8 binding, vk::Buffer buffer,
vk::DeviceSize offset, vk::DeviceSize size,
vk::DescriptorType type) {
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
Flush();
}
auto& buffer_info = descriptor_infos[descriptor_write_end].buffer_info;
buffer_info.buffer = buffer;
buffer_info.offset = offset;
buffer_info.range = size;
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
.dstSet = target,
.dstBinding = binding,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = type,
.pBufferInfo = &buffer_info,
};
}
void DescriptorUpdateQueue::AddTexelBuffer(vk::DescriptorSet target, u8 binding,
vk::BufferView buffer_view) {
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
Flush();
}
auto& buffer_info = descriptor_infos[descriptor_write_end].buffer_view;
buffer_info = buffer_view;
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
.dstSet = target,
.dstBinding = binding,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eUniformTexelBuffer,
.pTexelBufferView = &buffer_info,
};
}
} // namespace Vulkan

View file

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Vulkan {
class Instance;
struct DescriptorInfoUnion {
DescriptorInfoUnion() {}
union {
vk::DescriptorImageInfo image_info;
vk::DescriptorBufferInfo buffer_info;
vk::BufferView buffer_view;
};
};
class DescriptorUpdateQueue {
public:
explicit DescriptorUpdateQueue(const Instance& instance, u32 descriptor_write_max = 2048);
~DescriptorUpdateQueue() = default;
void Flush();
void AddStorageImage(vk::DescriptorSet target, u8 binding, vk::ImageView image_view,
vk::ImageLayout image_layout = vk::ImageLayout::eGeneral);
void AddImageSampler(vk::DescriptorSet target, u8 binding, u8 array_index,
vk::ImageView image_view, vk::Sampler sampler,
vk::ImageLayout imageLayout = vk::ImageLayout::eGeneral);
void AddBuffer(vk::DescriptorSet target, u8 binding, vk::Buffer buffer, vk::DeviceSize offset,
vk::DeviceSize size = VK_WHOLE_SIZE,
vk::DescriptorType type = vk::DescriptorType::eUniformBufferDynamic);
void AddTexelBuffer(vk::DescriptorSet target, u8 binding, vk::BufferView buffer_view);
private:
const vk::Device device;
const u32 descriptor_write_max;
std::unique_ptr<DescriptorInfoUnion[]> descriptor_infos;
std::unique_ptr<vk::WriteDescriptorSet[]> descriptor_writes;
u32 descriptor_write_end = 0;
};
} // namespace Vulkan

View file

@ -0,0 +1,269 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include <boost/container/static_vector.hpp>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "common/assert.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_platform.h"
#include <vk_mem_alloc.h>
namespace Vulkan {
namespace {
std::vector<std::string> GetSupportedExtensions(vk::PhysicalDevice physical) {
const std::vector extensions = physical.enumerateDeviceExtensionProperties();
std::vector<std::string> supported_extensions;
supported_extensions.reserve(extensions.size());
for (const auto& extension : extensions) {
supported_extensions.emplace_back(extension.extensionName.data());
}
return supported_extensions;
}
std::string GetReadableVersion(u32 version) {
return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
VK_VERSION_PATCH(version));
}
} // Anonymous namespace
Instance::Instance(bool enable_validation, bool dump_command_buffers)
: instance{CreateInstance(dl, Frontend::WindowSystemType::Headless, enable_validation,
dump_command_buffers)},
physical_devices{instance->enumeratePhysicalDevices()} {}
Instance::Instance(Frontend::WindowSDL& window, u32 physical_device_index)
: instance{CreateInstance(dl, window.getWindowInfo().type, true, false)},
debug_callback{CreateDebugCallback(*instance)}, physical_devices{
instance->enumeratePhysicalDevices()} {
const std::size_t num_physical_devices = static_cast<u16>(physical_devices.size());
ASSERT_MSG(physical_device_index < num_physical_devices,
"Invalid physical device index {} provided when only {} devices exist",
physical_device_index, num_physical_devices);
physical_device = physical_devices[physical_device_index];
available_extensions = GetSupportedExtensions(physical_device);
properties = physical_device.getProperties();
if (properties.apiVersion < TargetVulkanApiVersion) {
throw std::runtime_error(fmt::format(
"Vulkan {}.{} is required, but only {}.{} is supported by device!",
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion),
VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion)));
}
CollectDeviceParameters();
CreateDevice();
CollectToolingInfo();
}
Instance::~Instance() {
vmaDestroyAllocator(allocator);
}
std::string Instance::GetDriverVersionName() {
// Extracted from
// https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314
const u32 version = properties.driverVersion;
if (driver_id == vk::DriverId::eNvidiaProprietary) {
const u32 major = (version >> 22) & 0x3ff;
const u32 minor = (version >> 14) & 0x0ff;
const u32 secondary = (version >> 6) & 0x0ff;
const u32 tertiary = version & 0x003f;
return fmt::format("{}.{}.{}.{}", major, minor, secondary, tertiary);
}
if (driver_id == vk::DriverId::eIntelProprietaryWindows) {
const u32 major = version >> 14;
const u32 minor = version & 0x3fff;
return fmt::format("{}.{}", major, minor);
}
return GetReadableVersion(version);
}
bool Instance::CreateDevice() {
const vk::StructureChain feature_chain = physical_device.getFeatures2<
vk::PhysicalDeviceFeatures2, vk::PhysicalDevicePortabilitySubsetFeaturesKHR,
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT,
vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT,
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT,
vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR,
vk::PhysicalDeviceCustomBorderColorFeaturesEXT, vk::PhysicalDeviceIndexTypeUint8FeaturesEXT,
vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT,
vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT,
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR>();
const vk::StructureChain properties_chain =
physical_device.getProperties2<vk::PhysicalDeviceProperties2,
vk::PhysicalDevicePortabilitySubsetPropertiesKHR,
vk::PhysicalDeviceExternalMemoryHostPropertiesEXT>();
features = feature_chain.get().features;
if (available_extensions.empty()) {
LOG_CRITICAL(Render_Vulkan, "No extensions supported by device.");
return false;
}
boost::container::static_vector<const char*, 13> enabled_extensions;
const auto add_extension = [&](std::string_view extension) -> bool {
const auto result =
std::find_if(available_extensions.begin(), available_extensions.end(),
[&](const std::string& name) { return name == extension; });
if (result != available_extensions.end()) {
LOG_INFO(Render_Vulkan, "Enabling extension: {}", extension);
enabled_extensions.push_back(extension.data());
return true;
}
LOG_WARNING(Render_Vulkan, "Extension {} unavailable.", extension);
return false;
};
add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
image_format_list = add_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);
shader_stencil_export = add_extension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
external_memory_host = add_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME);
custom_border_color = add_extension(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
index_type_uint8 = add_extension(VK_KHR_INDEX_TYPE_UINT8_EXTENSION_NAME);
const auto family_properties = physical_device.getQueueFamilyProperties();
if (family_properties.empty()) {
LOG_CRITICAL(Render_Vulkan, "Physical device reported no queues.");
return false;
}
bool graphics_queue_found = false;
for (std::size_t i = 0; i < family_properties.size(); i++) {
const u32 index = static_cast<u32>(i);
if (family_properties[i].queueFlags & vk::QueueFlagBits::eGraphics) {
queue_family_index = index;
graphics_queue_found = true;
}
}
if (!graphics_queue_found) {
LOG_CRITICAL(Render_Vulkan, "Unable to find graphics and/or present queues.");
return false;
}
static constexpr std::array<f32, 1> queue_priorities = {1.0f};
const vk::DeviceQueueCreateInfo queue_info = {
.queueFamilyIndex = queue_family_index,
.queueCount = static_cast<u32>(queue_priorities.size()),
.pQueuePriorities = queue_priorities.data(),
};
vk::StructureChain device_chain = {
vk::DeviceCreateInfo{
.queueCreateInfoCount = 1u,
.pQueueCreateInfos = &queue_info,
.enabledExtensionCount = static_cast<u32>(enabled_extensions.size()),
.ppEnabledExtensionNames = enabled_extensions.data(),
},
vk::PhysicalDeviceFeatures2{
.features{
.robustBufferAccess = features.robustBufferAccess,
.geometryShader = features.geometryShader,
.logicOp = features.logicOp,
.samplerAnisotropy = features.samplerAnisotropy,
.fragmentStoresAndAtomics = features.fragmentStoresAndAtomics,
.shaderClipDistance = features.shaderClipDistance,
},
},
vk::PhysicalDeviceVulkan12Features{
.timelineSemaphore = true,
},
vk::PhysicalDeviceCustomBorderColorFeaturesEXT{
.customBorderColors = true,
.customBorderColorWithoutFormat = true,
},
vk::PhysicalDeviceIndexTypeUint8FeaturesEXT{
.indexTypeUint8 = true,
},
};
if (!index_type_uint8) {
device_chain.unlink<vk::PhysicalDeviceIndexTypeUint8FeaturesEXT>();
}
try {
device = physical_device.createDeviceUnique(device_chain.get());
} catch (vk::ExtensionNotPresentError& err) {
LOG_CRITICAL(Render_Vulkan, "Some required extensions are not available {}", err.what());
return false;
}
VULKAN_HPP_DEFAULT_DISPATCHER.init(*device);
graphics_queue = device->getQueue(queue_family_index, 0);
present_queue = device->getQueue(queue_family_index, 0);
CreateAllocator();
return true;
}
void Instance::CreateAllocator() {
const VmaVulkanFunctions functions = {
.vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr,
.vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr,
};
const VmaAllocatorCreateInfo allocator_info = {
.physicalDevice = physical_device,
.device = *device,
.pVulkanFunctions = &functions,
.instance = *instance,
.vulkanApiVersion = TargetVulkanApiVersion,
};
const VkResult result = vmaCreateAllocator(&allocator_info, &allocator);
if (result != VK_SUCCESS) {
UNREACHABLE_MSG("Failed to initialize VMA with error {}",
vk::to_string(vk::Result{result}));
}
}
void Instance::CollectDeviceParameters() {
const vk::StructureChain property_chain =
physical_device
.getProperties2<vk::PhysicalDeviceProperties2, vk::PhysicalDeviceDriverProperties>();
const vk::PhysicalDeviceDriverProperties driver =
property_chain.get<vk::PhysicalDeviceDriverProperties>();
driver_id = driver.driverID;
vendor_name = driver.driverName.data();
const std::string model_name{GetModelName()};
const std::string driver_version = GetDriverVersionName();
const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
const std::string api_version = GetReadableVersion(properties.apiVersion);
const std::string extensions = fmt::format("{}", fmt::join(available_extensions, ", "));
LOG_INFO(Render_Vulkan, "GPU_Vendor", vendor_name);
LOG_INFO(Render_Vulkan, "GPU_Model", model_name);
LOG_INFO(Render_Vulkan, "GPU_Vulkan_Driver", driver_name);
LOG_INFO(Render_Vulkan, "GPU_Vulkan_Version", api_version);
LOG_INFO(Render_Vulkan, "GPU_Vulkan_Extensions", extensions);
}
void Instance::CollectToolingInfo() {
if (!tooling_info) {
return;
}
const auto tools = physical_device.getToolPropertiesEXT();
for (const vk::PhysicalDeviceToolProperties& tool : tools) {
const std::string_view name = tool.name;
LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name);
has_renderdoc = has_renderdoc || name == "RenderDoc";
has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics";
}
}
} // namespace Vulkan

View file

@ -0,0 +1,228 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "video_core/renderer_vulkan/vk_platform.h"
namespace Frontend {
class WindowSDL;
}
VK_DEFINE_HANDLE(VmaAllocator)
namespace Vulkan {
class Instance {
public:
explicit Instance(bool validation = false, bool dump_command_buffers = false);
explicit Instance(Frontend::WindowSDL& window, u32 physical_device_index);
~Instance();
/// Returns a formatted string for the driver version
std::string GetDriverVersionName();
/// Returns the Vulkan instance
vk::Instance GetInstance() const {
return *instance;
}
/// Returns the current physical device
vk::PhysicalDevice GetPhysicalDevice() const {
return physical_device;
}
/// Returns the Vulkan device
vk::Device GetDevice() const {
return *device;
}
/// Returns the VMA allocator handle
VmaAllocator GetAllocator() const {
return allocator;
}
/// Returns a list of the available physical devices
std::span<const vk::PhysicalDevice> GetPhysicalDevices() const {
return physical_devices;
}
/// Retrieve queue information
u32 GetGraphicsQueueFamilyIndex() const {
return queue_family_index;
}
u32 GetPresentQueueFamilyIndex() const {
return queue_family_index;
}
vk::Queue GetGraphicsQueue() const {
return graphics_queue;
}
vk::Queue GetPresentQueue() const {
return present_queue;
}
/// Returns true when a known debugging tool is attached.
bool HasDebuggingToolAttached() const {
return has_renderdoc || has_nsight_graphics;
}
/// Returns true if anisotropic filtering is supported
bool IsAnisotropicFilteringSupported() const {
return features.samplerAnisotropy;
}
/// Returns true when VK_EXT_custom_border_color is supported
bool IsCustomBorderColorSupported() const {
return custom_border_color;
}
/// Returns true when VK_EXT_index_type_uint8 is supported
bool IsIndexTypeUint8Supported() const {
return index_type_uint8;
}
/// Returns true when VK_EXT_fragment_shader_interlock is supported
bool IsFragmentShaderInterlockSupported() const {
return fragment_shader_interlock;
}
/// Returns true when VK_KHR_image_format_list is supported
bool IsImageFormatListSupported() const {
return image_format_list;
}
/// Returns true when VK_EXT_pipeline_creation_cache_control is supported
bool IsPipelineCreationCacheControlSupported() const {
return pipeline_creation_cache_control;
}
/// Returns true when VK_EXT_shader_stencil_export is supported
bool IsShaderStencilExportSupported() const {
return shader_stencil_export;
}
/// Returns true when VK_EXT_external_memory_host is supported
bool IsExternalMemoryHostSupported() const {
return external_memory_host;
}
/// Returns the vendor ID of the physical device
u32 GetVendorID() const {
return properties.vendorID;
}
/// Returns the device ID of the physical device
u32 GetDeviceID() const {
return properties.deviceID;
}
/// Returns the driver ID.
vk::DriverId GetDriverID() const {
return driver_id;
}
/// Returns the current driver version provided in Vulkan-formatted version numbers.
u32 GetDriverVersion() const {
return properties.driverVersion;
}
/// Returns the current Vulkan API version provided in Vulkan-formatted version numbers.
u32 ApiVersion() const {
return properties.apiVersion;
}
/// Returns the vendor name reported from Vulkan.
std::string_view GetVendorName() const {
return vendor_name;
}
/// Returns the list of available extensions.
std::span<const std::string> GetAvailableExtensions() const {
return available_extensions;
}
/// Returns the device name.
std::string_view GetModelName() const {
return properties.deviceName;
}
/// Returns the pipeline cache unique identifier
const auto GetPipelineCacheUUID() const {
return properties.pipelineCacheUUID;
}
/// Returns the minimum required alignment for uniforms
vk::DeviceSize UniformMinAlignment() const {
return properties.limits.minUniformBufferOffsetAlignment;
}
/// Returns the minimum alignemt required for accessing host-mapped device memory
vk::DeviceSize NonCoherentAtomSize() const {
return properties.limits.nonCoherentAtomSize;
}
/// Returns the maximum supported elements in a texel buffer
u32 MaxTexelBufferElements() const {
return properties.limits.maxTexelBufferElements;
}
/// Returns true if shaders can declare the ClipDistance attribute
bool IsShaderClipDistanceSupported() const {
return features.shaderClipDistance;
}
/// Returns the minimum imported host pointer alignment
u64 GetMinImportedHostPointerAlignment() const {
return min_imported_host_pointer_alignment;
}
private:
/// Creates the logical device opportunistically enabling extensions
bool CreateDevice();
/// Creates the VMA allocator handle
void CreateAllocator();
/// Collects telemetry information from the device.
void CollectDeviceParameters();
void CollectToolingInfo();
private:
vk::DynamicLoader dl;
vk::UniqueInstance instance;
vk::PhysicalDevice physical_device;
vk::UniqueDevice device;
vk::PhysicalDeviceProperties properties;
vk::PhysicalDeviceFeatures features;
vk::DriverIdKHR driver_id;
vk::UniqueDebugUtilsMessengerEXT debug_callback;
std::string vendor_name;
VmaAllocator allocator{};
vk::Queue present_queue;
vk::Queue graphics_queue;
std::vector<vk::PhysicalDevice> physical_devices;
std::vector<std::string> available_extensions;
u32 queue_family_index{0};
bool image_view_reinterpretation{true};
bool timeline_semaphores{};
bool custom_border_color{};
bool index_type_uint8{};
bool fragment_shader_interlock{};
bool image_format_list{};
bool pipeline_creation_cache_control{};
bool fragment_shader_barycentric{};
bool shader_stencil_export{};
bool external_memory_host{};
u64 min_imported_host_pointer_alignment{};
bool tooling_info{};
bool debug_utils_supported{};
bool has_nsight_graphics{};
bool has_renderdoc{};
};
} // namespace Vulkan

View file

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <limits>
#include <mutex>
#include "common/assert.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
namespace Vulkan {
constexpr u64 WAIT_TIMEOUT = std::numeric_limits<u64>::max();
MasterSemaphore::MasterSemaphore(const Instance& instance_) : instance{instance_} {
const vk::StructureChain semaphore_chain = {
vk::SemaphoreCreateInfo{},
vk::SemaphoreTypeCreateInfo{
.semaphoreType = vk::SemaphoreType::eTimeline,
.initialValue = 0,
},
};
semaphore = instance.GetDevice().createSemaphoreUnique(semaphore_chain.get());
}
MasterSemaphore::~MasterSemaphore() = default;
void MasterSemaphore::Refresh() {
u64 this_tick{};
u64 counter{};
do {
this_tick = gpu_tick.load(std::memory_order_acquire);
counter = instance.GetDevice().getSemaphoreCounterValue(*semaphore);
if (counter < this_tick) {
return;
}
} while (!gpu_tick.compare_exchange_weak(this_tick, counter, std::memory_order_release,
std::memory_order_relaxed));
}
void MasterSemaphore::Wait(u64 tick) {
// No need to wait if the GPU is ahead of the tick
if (IsFree(tick)) {
return;
}
// Update the GPU tick and try again
Refresh();
if (IsFree(tick)) {
return;
}
// If none of the above is hit, fallback to a regular wait
const vk::SemaphoreWaitInfo wait_info = {
.semaphoreCount = 1,
.pSemaphores = &semaphore.get(),
.pValues = &tick,
};
while (instance.GetDevice().waitSemaphores(&wait_info, WAIT_TIMEOUT) != vk::Result::eSuccess) {
}
Refresh();
}
void MasterSemaphore::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal,
u64 signal_value) {
cmdbuf.end();
const u32 num_signal_semaphores = signal ? 2U : 1U;
const std::array signal_values{signal_value, u64(0)};
const std::array signal_semaphores{Handle(), signal};
const u32 num_wait_semaphores = wait ? 2U : 1U;
const std::array wait_values{signal_value - 1, u64(1)};
const std::array wait_semaphores{Handle(), wait};
static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = {
vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
};
const vk::TimelineSemaphoreSubmitInfo timeline_si = {
.waitSemaphoreValueCount = num_wait_semaphores,
.pWaitSemaphoreValues = wait_values.data(),
.signalSemaphoreValueCount = num_signal_semaphores,
.pSignalSemaphoreValues = signal_values.data(),
};
const vk::SubmitInfo submit_info = {
.pNext = &timeline_si,
.waitSemaphoreCount = num_wait_semaphores,
.pWaitSemaphores = wait_semaphores.data(),
.pWaitDstStageMask = wait_stage_masks.data(),
.commandBufferCount = 1u,
.pCommandBuffers = &cmdbuf,
.signalSemaphoreCount = num_signal_semaphores,
.pSignalSemaphores = signal_semaphores.data(),
};
try {
instance.GetGraphicsQueue().submit(submit_info);
} catch (vk::DeviceLostError& err) {
UNREACHABLE_MSG("Device lost during submit: {}", err.what());
}
}
} // namespace Vulkan

View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <condition_variable>
#include <thread>
#include <queue>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Vulkan {
class Instance;
class Scheduler;
class MasterSemaphore {
public:
explicit MasterSemaphore(const Instance& instance_);
~MasterSemaphore();
[[nodiscard]] u64 CurrentTick() const noexcept {
return current_tick.load(std::memory_order_acquire);
}
[[nodiscard]] u64 KnownGpuTick() const noexcept {
return gpu_tick.load(std::memory_order_acquire);
}
[[nodiscard]] bool IsFree(u64 tick) const noexcept {
return KnownGpuTick() >= tick;
}
[[nodiscard]] u64 NextTick() noexcept {
return current_tick.fetch_add(1, std::memory_order_release);
}
[[nodiscard]] vk::Semaphore Handle() const noexcept {
return semaphore.get();
}
/// Refresh the known GPU tick
void Refresh();
/// Waits for a tick to be hit on the GPU
void Wait(u64 tick);
/// Submits the provided command buffer for execution
void SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal,
u64 signal_value);
protected:
const Instance& instance;
vk::UniqueSemaphore semaphore; ///< Timeline semaphore.
std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick.
std::atomic<u64> current_tick{1}; ///< Current logical tick.
};
} // namespace Vulkan

View file

@ -0,0 +1,231 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Include the vulkan platform specific header
#if defined(ANDROID)
#define VK_USE_PLATFORM_ANDROID_KHR
#elif defined(_WIN64)
#define VK_USE_PLATFORM_WIN32_KHR
#elif defined(__APPLE__)
#define VK_USE_PLATFORM_METAL_EXT
#else
#define VK_USE_PLATFORM_WAYLAND_KHR
#define VK_USE_PLATFORM_XLIB_KHR
#endif
#include <vector>
#include "common/assert.h"
#include "common/logging/log.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/vk_platform.h"
namespace Vulkan {
static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) {
switch (static_cast<u32>(callback_data->messageIdNumber)) {
case 0x609a13b: // Vertex attribute at location not consumed by shader
case 0xc81ad50e:
return VK_FALSE;
default:
break;
}
Common::Log::Level level{};
switch (severity) {
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
level = Common::Log::Level::Error;
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
level = Common::Log::Level::Info;
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
level = Common::Log::Level::Debug;
break;
default:
level = Common::Log::Level::Info;
}
LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, "{}: {}",
callback_data->pMessageIdName ? callback_data->pMessageIdName : "<null>",
callback_data->pMessage ? callback_data->pMessage : "<null>");
return VK_FALSE;
}
vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window) {
const auto& window_info = emu_window.getWindowInfo();
vk::SurfaceKHR surface{};
#if defined(VK_USE_PLATFORM_WIN32_KHR)
if (window_info.type == Frontend::WindowSystemType::Windows) {
const vk::Win32SurfaceCreateInfoKHR win32_ci = {
.hinstance = nullptr,
.hwnd = static_cast<HWND>(window_info.render_surface),
};
if (instance.createWin32SurfaceKHR(&win32_ci, nullptr, &surface) != vk::Result::eSuccess) {
LOG_CRITICAL(Render_Vulkan, "Failed to initialize Win32 surface");
UNREACHABLE();
}
}
#elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR)
if (window_info.type == Frontend::WindowSystemType::X11) {
const vk::XlibSurfaceCreateInfoKHR xlib_ci = {
.dpy = static_cast<Display*>(window_info.display_connection),
.window = reinterpret_cast<Window>(window_info.render_surface),
};
if (instance.createXlibSurfaceKHR(&xlib_ci, nullptr, &surface) != vk::Result::eSuccess) {
LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface");
UNREACHABLE();
}
} else if (window_info.type == Frontend::WindowSystemType::Wayland) {
const vk::WaylandSurfaceCreateInfoKHR wayland_ci = {
.display = static_cast<wl_display*>(window_info.display_connection),
.surface = static_cast<wl_surface*>(window_info.render_surface),
};
if (instance.createWaylandSurfaceKHR(&wayland_ci, nullptr, &surface) !=
vk::Result::eSuccess) {
LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface");
UNREACHABLE();
}
}
#endif
if (!surface) {
LOG_CRITICAL(Render_Vulkan, "Presentation not supported on this platform");
UNREACHABLE();
}
return surface;
}
std::vector<const char*> GetInstanceExtensions(Frontend::WindowSystemType window_type,
bool enable_debug_utils) {
const auto properties = vk::enumerateInstanceExtensionProperties();
if (properties.empty()) {
LOG_ERROR(Render_Vulkan, "Failed to query extension properties");
return {};
}
// Add the windowing system specific extension
std::vector<const char*> extensions;
extensions.reserve(7);
switch (window_type) {
case Frontend::WindowSystemType::Headless:
break;
#if defined(VK_USE_PLATFORM_WIN32_KHR)
case Frontend::WindowSystemType::Windows:
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
break;
#elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR)
case Frontend::WindowSystemType::X11:
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
break;
case Frontend::WindowSystemType::Wayland:
extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
break;
#endif
default:
LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
break;
}
if (window_type != Frontend::WindowSystemType::Headless) {
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
}
if (enable_debug_utils) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
// Sanitize extension list
std::erase_if(extensions, [&](const char* extension) -> bool {
const auto it =
std::find_if(properties.begin(), properties.end(), [extension](const auto& prop) {
return std::strcmp(extension, prop.extensionName) == 0;
});
if (it == properties.end()) {
LOG_INFO(Render_Vulkan, "Candidate instance extension {} is not available", extension);
return true;
}
return false;
});
return extensions;
}
vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemType window_type,
bool enable_validation, bool dump_command_buffers) {
auto vkGetInstanceProcAddr =
dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion
? vk::enumerateInstanceVersion()
: VK_API_VERSION_1_0;
if (available_version < TargetVulkanApiVersion) {
throw std::runtime_error(fmt::format(
"Vulkan {}.{} is required, but only {}.{} is supported by instance!",
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion),
VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version)));
}
const auto extensions = GetInstanceExtensions(window_type, enable_validation);
const vk::ApplicationInfo application_info = {
.pApplicationName = "shadPS4",
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "shadPS4 Vulkan",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = available_version,
};
u32 num_layers = 0;
std::array<const char*, 2> layers;
if (enable_validation) {
layers[num_layers++] = "VK_LAYER_KHRONOS_validation";
}
if (dump_command_buffers) {
layers[num_layers++] = "VK_LAYER_LUNARG_api_dump";
}
vk::InstanceCreateInfo instance_ci = {
.pApplicationInfo = &application_info,
.enabledLayerCount = num_layers,
.ppEnabledLayerNames = layers.data(),
.enabledExtensionCount = static_cast<u32>(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
};
auto instance = vk::createInstanceUnique(instance_ci);
VULKAN_HPP_DEFAULT_DISPATCHER.init(*instance);
return instance;
}
vk::UniqueDebugUtilsMessengerEXT CreateDebugCallback(vk::Instance instance) {
const vk::DebugUtilsMessengerCreateInfoEXT msg_ci = {
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose,
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
.pfnUserCallback = DebugUtilsCallback,
};
return instance.createDebugUtilsMessengerEXTUnique(msg_ci);
}
} // namespace Vulkan

View file

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <variant>
#include <fmt/format.h>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Frontend {
enum class WindowSystemType : u8;
class WindowSDL;
} // namespace Frontend
namespace Vulkan {
constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_3;
vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window);
vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemType window_type,
bool enable_validation, bool dump_command_buffers);
vk::UniqueDebugUtilsMessengerEXT CreateDebugCallback(vk::Instance instance);
template <typename T>
concept VulkanHandleType = vk::isVulkanHandleType<T>::value;
template <VulkanHandleType HandleType>
void SetObjectName(vk::Device device, const HandleType& handle, std::string_view debug_name) {
const vk::DebugUtilsObjectNameInfoEXT name_info = {
.objectType = HandleType::objectType,
.objectHandle = reinterpret_cast<u64>(static_cast<typename HandleType::NativeType>(handle)),
.pObjectName = debug_name.data(),
};
device.setDebugUtilsObjectNameEXT(name_info);
}
template <VulkanHandleType HandleType, typename... Args>
void SetObjectName(vk::Device device, const HandleType& handle, const char* format,
const Args&... args) {
const std::string debug_name = fmt::vformat(format, fmt::make_format_args(args...));
SetObjectName(device, handle, debug_name);
}
} // namespace Vulkan

View file

@ -0,0 +1,190 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#include <optional>
#include <unordered_map>
#include "common/assert.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
namespace Vulkan {
ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_, std::size_t grow_step_)
: master_semaphore{master_semaphore_}, grow_step{grow_step_} {}
std::size_t ResourcePool::CommitResource() {
u64 gpu_tick = master_semaphore->KnownGpuTick();
const auto search = [this, gpu_tick](std::size_t begin,
std::size_t end) -> std::optional<std::size_t> {
for (std::size_t iterator = begin; iterator < end; ++iterator) {
if (gpu_tick >= ticks[iterator]) {
ticks[iterator] = master_semaphore->CurrentTick();
return iterator;
}
}
return std::nullopt;
};
// Try to find a free resource from the hinted position to the end.
auto found = search(hint_iterator, ticks.size());
if (!found) {
// Refresh semaphore to query updated results
master_semaphore->Refresh();
gpu_tick = master_semaphore->KnownGpuTick();
found = search(hint_iterator, ticks.size());
}
if (!found) {
// Search from beginning to the hinted position.
found = search(0, hint_iterator);
if (!found) {
// Both searches failed, the pool is full; handle it.
const std::size_t free_resource = ManageOverflow();
ticks[free_resource] = master_semaphore->CurrentTick();
found = free_resource;
}
}
// Free iterator is hinted to the resource after the one that's been commited.
hint_iterator = (*found + 1) % ticks.size();
return *found;
}
std::size_t ResourcePool::ManageOverflow() {
const std::size_t old_capacity = ticks.size();
ticks.resize(old_capacity + grow_step);
Allocate(old_capacity, old_capacity + grow_step);
return old_capacity;
}
constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 4;
CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semaphore)
: ResourcePool{master_semaphore, COMMAND_BUFFER_POOL_SIZE}, instance{instance} {
const vk::CommandPoolCreateInfo pool_create_info = {
.flags = vk::CommandPoolCreateFlagBits::eTransient |
vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(),
};
const vk::Device device = instance.GetDevice();
cmd_pool = device.createCommandPoolUnique(pool_create_info);
if (instance.HasDebuggingToolAttached()) {
SetObjectName(device, *cmd_pool, "CommandPool");
}
}
CommandPool::~CommandPool() = default;
void CommandPool::Allocate(std::size_t begin, std::size_t end) {
cmd_buffers.resize(end);
const vk::CommandBufferAllocateInfo buffer_alloc_info = {
.commandPool = *cmd_pool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = COMMAND_BUFFER_POOL_SIZE,
};
const vk::Device device = instance.GetDevice();
const auto result =
device.allocateCommandBuffers(&buffer_alloc_info, cmd_buffers.data() + begin);
ASSERT(result == vk::Result::eSuccess);
if (instance.HasDebuggingToolAttached()) {
for (std::size_t i = begin; i < end; ++i) {
SetObjectName(device, cmd_buffers[i], "CommandPool: Command Buffer {}", i);
}
}
}
vk::CommandBuffer CommandPool::Commit() {
const std::size_t index = CommitResource();
return cmd_buffers[index];
}
constexpr u32 DESCRIPTOR_SET_BATCH = 32;
DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore,
std::span<const vk::DescriptorSetLayoutBinding> bindings,
u32 descriptor_heap_count_)
: ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()},
descriptor_heap_count{descriptor_heap_count_} {
// Create descriptor set layout.
const vk::DescriptorSetLayoutCreateInfo layout_ci = {
.bindingCount = static_cast<u32>(bindings.size()),
.pBindings = bindings.data(),
};
descriptor_set_layout = device.createDescriptorSetLayoutUnique(layout_ci);
if (instance.HasDebuggingToolAttached()) {
SetObjectName(device, *descriptor_set_layout, "DescriptorSetLayout");
}
// Build descriptor set pool counts.
std::unordered_map<vk::DescriptorType, u16> descriptor_type_counts;
for (const auto& binding : bindings) {
descriptor_type_counts[binding.descriptorType] += binding.descriptorCount;
}
for (const auto& [type, count] : descriptor_type_counts) {
auto& pool_size = pool_sizes.emplace_back();
pool_size.descriptorCount = count * descriptor_heap_count;
pool_size.type = type;
}
// Create descriptor pool
AppendDescriptorPool();
}
DescriptorHeap::~DescriptorHeap() = default;
void DescriptorHeap::Allocate(std::size_t begin, std::size_t end) {
ASSERT(end - begin == DESCRIPTOR_SET_BATCH);
descriptor_sets.resize(end);
hashes.resize(end);
std::array<vk::DescriptorSetLayout, DESCRIPTOR_SET_BATCH> layouts;
layouts.fill(*descriptor_set_layout);
u32 current_pool = 0;
vk::DescriptorSetAllocateInfo alloc_info = {
.descriptorPool = *pools[current_pool],
.descriptorSetCount = DESCRIPTOR_SET_BATCH,
.pSetLayouts = layouts.data(),
};
// Attempt to allocate the descriptor set batch. If the pool has run out of space, use a new
// one.
while (true) {
const auto result =
device.allocateDescriptorSets(&alloc_info, descriptor_sets.data() + begin);
if (result == vk::Result::eSuccess) {
break;
}
if (result == vk::Result::eErrorOutOfPoolMemory) {
current_pool++;
if (current_pool == pools.size()) {
LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!");
AppendDescriptorPool();
}
alloc_info.descriptorPool = *pools[current_pool];
}
}
}
vk::DescriptorSet DescriptorHeap::Commit() {
const std::size_t index = CommitResource();
return descriptor_sets[index];
}
void DescriptorHeap::AppendDescriptorPool() {
const vk::DescriptorPoolCreateInfo pool_info = {
.flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind,
.maxSets = descriptor_heap_count,
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
.pPoolSizes = pool_sizes.data(),
};
auto& pool = pools.emplace_back();
pool = device.createDescriptorPoolUnique(pool_info);
}
} // namespace Vulkan

View file

@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include <tsl/robin_map.h>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Vulkan {
class Instance;
class MasterSemaphore;
/**
* Handles a pool of resources protected by fences. Manages resource overflow allocating more
* resources.
*/
class ResourcePool {
public:
explicit ResourcePool() = default;
explicit ResourcePool(MasterSemaphore* master_semaphore, std::size_t grow_step);
virtual ~ResourcePool() = default;
ResourcePool& operator=(ResourcePool&&) noexcept = default;
ResourcePool(ResourcePool&&) noexcept = default;
ResourcePool& operator=(const ResourcePool&) = default;
ResourcePool(const ResourcePool&) = default;
protected:
std::size_t CommitResource();
/// Called when a chunk of resources have to be allocated.
virtual void Allocate(std::size_t begin, std::size_t end) = 0;
private:
/// Manages pool overflow allocating new resources.
std::size_t ManageOverflow();
protected:
MasterSemaphore* master_semaphore{nullptr};
std::size_t grow_step = 0; ///< Number of new resources created after an overflow
std::size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found
std::vector<u64> ticks; ///< Ticks for each resource
};
class CommandPool final : public ResourcePool {
public:
explicit CommandPool(const Instance& instance, MasterSemaphore* master_semaphore);
~CommandPool() override;
void Allocate(std::size_t begin, std::size_t end) override;
vk::CommandBuffer Commit();
private:
const Instance& instance;
vk::UniqueCommandPool cmd_pool;
std::vector<vk::CommandBuffer> cmd_buffers;
};
class DescriptorHeap final : public ResourcePool {
public:
explicit DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore,
std::span<const vk::DescriptorSetLayoutBinding> bindings,
u32 descriptor_heap_count = 1024);
~DescriptorHeap() override;
const vk::DescriptorSetLayout& Layout() const {
return *descriptor_set_layout;
}
void Allocate(std::size_t begin, std::size_t end) override;
vk::DescriptorSet Commit();
private:
void AppendDescriptorPool();
private:
vk::Device device;
vk::UniqueDescriptorSetLayout descriptor_set_layout;
u32 descriptor_heap_count;
std::vector<vk::DescriptorPoolSize> pool_sizes;
std::vector<vk::UniqueDescriptorPool> pools;
std::vector<vk::DescriptorSet> descriptor_sets;
std::vector<std::size_t> hashes;
};
} // namespace Vulkan

View file

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
namespace Vulkan {
Scheduler::Scheduler(const Instance& instance)
: master_semaphore{instance}, command_pool{instance, &master_semaphore} {
AllocateWorkerCommandBuffers();
}
Scheduler::~Scheduler() = default;
void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) {
// When flushing, we only send data to the worker thread; no waiting is necessary.
SubmitExecution(signal, wait);
}
void Scheduler::Finish(vk::Semaphore signal, vk::Semaphore wait) {
// When finishing, we need to wait for the submission to have executed on the device.
const u64 presubmit_tick = CurrentTick();
SubmitExecution(signal, wait);
Wait(presubmit_tick);
}
void Scheduler::Wait(u64 tick) {
if (tick >= master_semaphore.CurrentTick()) {
// Make sure we are not waiting for the current tick without signalling
Flush();
}
master_semaphore.Wait(tick);
}
void Scheduler::AllocateWorkerCommandBuffers() {
const vk::CommandBufferBeginInfo begin_info = {
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
current_cmdbuf = command_pool.Commit();
current_cmdbuf.begin(begin_info);
}
void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore) {
const u64 signal_value = master_semaphore.NextTick();
std::scoped_lock lk{submit_mutex};
master_semaphore.SubmitWork(current_cmdbuf, wait_semaphore, signal_semaphore, signal_value);
master_semaphore.Refresh();
AllocateWorkerCommandBuffers();
}
} // namespace Vulkan

View file

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
namespace Vulkan {
class Instance;
class Scheduler {
public:
explicit Scheduler(const Instance& instance);
~Scheduler();
/// Sends the current execution context to the GPU.
void Flush(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr);
/// Sends the current execution context to the GPU and waits for it to complete.
void Finish(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr);
/// Waits for the given tick to trigger on the GPU.
void Wait(u64 tick);
/// Returns the current command buffer.
vk::CommandBuffer CommandBuffer() const {
return current_cmdbuf;
}
/// Returns the current command buffer tick.
[[nodiscard]] u64 CurrentTick() const noexcept {
return master_semaphore.CurrentTick();
}
/// Returns true when a tick has been triggered by the GPU.
[[nodiscard]] bool IsFree(u64 tick) const noexcept {
return master_semaphore.IsFree(tick);
}
/// Returns the master timeline semaphore.
[[nodiscard]] MasterSemaphore* GetMasterSemaphore() noexcept {
return &master_semaphore;
}
std::mutex submit_mutex;
private:
void AllocateWorkerCommandBuffers();
void SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore);
private:
MasterSemaphore master_semaphore;
CommandPool command_pool;
vk::CommandBuffer current_cmdbuf;
std::condition_variable_any event_cv;
};
} // namespace Vulkan

View file

@ -0,0 +1,230 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <SPIRV/GlslangToSpv.h>
#include <glslang/Include/ResourceLimits.h>
#include <glslang/Public/ShaderLang.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
namespace Vulkan {
namespace {
constexpr TBuiltInResource DefaultTBuiltInResource = {
.maxLights = 32,
.maxClipPlanes = 6,
.maxTextureUnits = 32,
.maxTextureCoords = 32,
.maxVertexAttribs = 64,
.maxVertexUniformComponents = 4096,
.maxVaryingFloats = 64,
.maxVertexTextureImageUnits = 32,
.maxCombinedTextureImageUnits = 80,
.maxTextureImageUnits = 32,
.maxFragmentUniformComponents = 4096,
.maxDrawBuffers = 32,
.maxVertexUniformVectors = 128,
.maxVaryingVectors = 8,
.maxFragmentUniformVectors = 16,
.maxVertexOutputVectors = 16,
.maxFragmentInputVectors = 15,
.minProgramTexelOffset = -8,
.maxProgramTexelOffset = 7,
.maxClipDistances = 8,
.maxComputeWorkGroupCountX = 65535,
.maxComputeWorkGroupCountY = 65535,
.maxComputeWorkGroupCountZ = 65535,
.maxComputeWorkGroupSizeX = 1024,
.maxComputeWorkGroupSizeY = 1024,
.maxComputeWorkGroupSizeZ = 64,
.maxComputeUniformComponents = 1024,
.maxComputeTextureImageUnits = 16,
.maxComputeImageUniforms = 8,
.maxComputeAtomicCounters = 8,
.maxComputeAtomicCounterBuffers = 1,
.maxVaryingComponents = 60,
.maxVertexOutputComponents = 64,
.maxGeometryInputComponents = 64,
.maxGeometryOutputComponents = 128,
.maxFragmentInputComponents = 128,
.maxImageUnits = 8,
.maxCombinedImageUnitsAndFragmentOutputs = 8,
.maxCombinedShaderOutputResources = 8,
.maxImageSamples = 0,
.maxVertexImageUniforms = 0,
.maxTessControlImageUniforms = 0,
.maxTessEvaluationImageUniforms = 0,
.maxGeometryImageUniforms = 0,
.maxFragmentImageUniforms = 8,
.maxCombinedImageUniforms = 8,
.maxGeometryTextureImageUnits = 16,
.maxGeometryOutputVertices = 256,
.maxGeometryTotalOutputComponents = 1024,
.maxGeometryUniformComponents = 1024,
.maxGeometryVaryingComponents = 64,
.maxTessControlInputComponents = 128,
.maxTessControlOutputComponents = 128,
.maxTessControlTextureImageUnits = 16,
.maxTessControlUniformComponents = 1024,
.maxTessControlTotalOutputComponents = 4096,
.maxTessEvaluationInputComponents = 128,
.maxTessEvaluationOutputComponents = 128,
.maxTessEvaluationTextureImageUnits = 16,
.maxTessEvaluationUniformComponents = 1024,
.maxTessPatchComponents = 120,
.maxPatchVertices = 32,
.maxTessGenLevel = 64,
.maxViewports = 16,
.maxVertexAtomicCounters = 0,
.maxTessControlAtomicCounters = 0,
.maxTessEvaluationAtomicCounters = 0,
.maxGeometryAtomicCounters = 0,
.maxFragmentAtomicCounters = 8,
.maxCombinedAtomicCounters = 8,
.maxAtomicCounterBindings = 1,
.maxVertexAtomicCounterBuffers = 0,
.maxTessControlAtomicCounterBuffers = 0,
.maxTessEvaluationAtomicCounterBuffers = 0,
.maxGeometryAtomicCounterBuffers = 0,
.maxFragmentAtomicCounterBuffers = 1,
.maxCombinedAtomicCounterBuffers = 1,
.maxAtomicCounterBufferSize = 16384,
.maxTransformFeedbackBuffers = 4,
.maxTransformFeedbackInterleavedComponents = 64,
.maxCullDistances = 8,
.maxCombinedClipAndCullDistances = 8,
.maxSamples = 4,
.maxMeshOutputVerticesNV = 256,
.maxMeshOutputPrimitivesNV = 512,
.maxMeshWorkGroupSizeX_NV = 32,
.maxMeshWorkGroupSizeY_NV = 1,
.maxMeshWorkGroupSizeZ_NV = 1,
.maxTaskWorkGroupSizeX_NV = 32,
.maxTaskWorkGroupSizeY_NV = 1,
.maxTaskWorkGroupSizeZ_NV = 1,
.maxMeshViewCountNV = 4,
.maxDualSourceDrawBuffersEXT = 1,
.limits =
TLimits{
.nonInductiveForLoops = 1,
.whileLoops = 1,
.doWhileLoops = 1,
.generalUniformIndexing = 1,
.generalAttributeMatrixVectorIndexing = 1,
.generalVaryingIndexing = 1,
.generalSamplerIndexing = 1,
.generalVariableIndexing = 1,
.generalConstantMatrixVectorIndexing = 1,
},
};
EShLanguage ToEshShaderStage(vk::ShaderStageFlagBits stage) {
switch (stage) {
case vk::ShaderStageFlagBits::eVertex:
return EShLanguage::EShLangVertex;
case vk::ShaderStageFlagBits::eGeometry:
return EShLanguage::EShLangGeometry;
case vk::ShaderStageFlagBits::eFragment:
return EShLanguage::EShLangFragment;
case vk::ShaderStageFlagBits::eCompute:
return EShLanguage::EShLangCompute;
default:
UNREACHABLE_MSG("Unkown shader stage {}", vk::to_string(stage));
}
return EShLanguage::EShLangVertex;
}
bool InitializeCompiler() {
static bool glslang_initialized = false;
if (glslang_initialized) {
return true;
}
if (!glslang::InitializeProcess()) {
LOG_CRITICAL(Render_Vulkan, "Failed to initialize glslang shader compiler");
return false;
}
std::atexit([]() { glslang::FinalizeProcess(); });
glslang_initialized = true;
return true;
}
} // Anonymous namespace
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device) {
if (!InitializeCompiler()) {
return {};
}
EProfile profile = ECoreProfile;
EShMessages messages =
static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
EShLanguage lang = ToEshShaderStage(stage);
const int default_version = 450;
const char* pass_source_code = code.data();
int pass_source_code_length = static_cast<int>(code.size());
auto shader = std::make_unique<glslang::TShader>(lang);
shader->setEnvTarget(glslang::EShTargetSpv,
glslang::EShTargetLanguageVersion::EShTargetSpv_1_3);
shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1);
glslang::TShader::ForbidIncluder includer;
if (!shader->parse(&DefaultTBuiltInResource, default_version, profile, false, true, messages,
includer)) [[unlikely]] {
LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(),
shader->getInfoDebugLog());
LOG_INFO(Render_Vulkan, "Shader Source:\n{}", code);
return {};
}
// Even though there's only a single shader, we still need to link it to generate SPV
auto program = std::make_unique<glslang::TProgram>();
program->addShader(shader.get());
if (!program->link(messages)) {
LOG_INFO(Render_Vulkan, "Program Info Log:\n{}\n{}", program->getInfoLog(),
program->getInfoDebugLog());
return {};
}
glslang::TIntermediate* intermediate = program->getIntermediate(lang);
std::vector<u32> out_code;
spv::SpvBuildLogger logger;
glslang::SpvOptions options;
// Enable optimizations on the generated SPIR-V code.
options.disableOptimizer = false;
options.validate = false;
options.optimizeSize = true;
glslang::GlslangToSpv(*intermediate, out_code, &logger, &options);
const std::string spv_messages = logger.getAllMessages();
if (!spv_messages.empty()) {
LOG_INFO(Render_Vulkan, "SPIR-V conversion messages: {}", spv_messages);
}
return CompileSPV(out_code, device);
}
vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device) {
const vk::ShaderModuleCreateInfo shader_info = {
.codeSize = code.size() * sizeof(u32),
.pCode = code.data(),
};
try {
return device.createShaderModule(shader_info);
} catch (vk::SystemError& err) {
UNREACHABLE_MSG("{}", err.what());
}
return {};
}
} // namespace Vulkan

View file

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Vulkan {
/**
* @brief Creates a vulkan shader module from GLSL by converting it to SPIR-V using glslang.
* @param code The string containing GLSL code.
* @param stage The pipeline stage the shader will be used in.
* @param device The vulkan device handle.
*/
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device);
/**
* @brief Creates a vulkan shader module from SPIR-V bytecode.
* @param code The SPIR-V bytecode data.
* @param device The vulkan device handle
*/
vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device);
} // namespace Vulkan

View file

@ -0,0 +1,234 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/alignment.h"
#include "common/assert.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
namespace Vulkan {
namespace {
std::string_view BufferTypeName(BufferType type) {
switch (type) {
case BufferType::Upload:
return "Upload";
case BufferType::Download:
return "Download";
case BufferType::Stream:
return "Stream";
default:
return "Invalid";
}
}
vk::MemoryPropertyFlags MakePropertyFlags(BufferType type) {
switch (type) {
case BufferType::Upload:
return vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent;
case BufferType::Download:
return vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostCached;
case BufferType::Stream:
return vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent;
default:
UNREACHABLE_MSG("Unknown buffer type {}", static_cast<u32>(type));
return vk::MemoryPropertyFlagBits::eHostVisible;
}
}
static std::optional<u32> FindMemoryType(const vk::PhysicalDeviceMemoryProperties& properties,
vk::MemoryPropertyFlags wanted) {
for (u32 i = 0; i < properties.memoryTypeCount; ++i) {
const auto flags = properties.memoryTypes[i].propertyFlags;
if ((flags & wanted) == wanted) {
return i;
}
}
return std::nullopt;
}
/// Get the preferred host visible memory type.
u32 GetMemoryType(const vk::PhysicalDeviceMemoryProperties& properties, BufferType type) {
vk::MemoryPropertyFlags flags = MakePropertyFlags(type);
std::optional preferred_type = FindMemoryType(properties, flags);
constexpr std::array remove_flags = {
vk::MemoryPropertyFlagBits::eHostCached,
vk::MemoryPropertyFlagBits::eHostCoherent,
};
for (u32 i = 0; i < remove_flags.size() && !preferred_type; i++) {
flags &= ~remove_flags[i];
preferred_type = FindMemoryType(properties, flags);
}
ASSERT_MSG(preferred_type, "No suitable memory type found");
return preferred_type.value();
}
constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000;
constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000;
} // Anonymous namespace
StreamBuffer::StreamBuffer(const Instance& instance_, Scheduler& scheduler_,
vk::BufferUsageFlags usage_, u64 size, BufferType type_)
: instance{instance_}, scheduler{scheduler_}, device{instance.GetDevice()},
stream_buffer_size{size}, usage{usage_}, type{type_} {
CreateBuffers(size);
ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE);
}
StreamBuffer::~StreamBuffer() {
device.unmapMemory(memory);
device.destroyBuffer(buffer);
device.freeMemory(memory);
}
std::tuple<u8*, u64, bool> StreamBuffer::Map(u64 size, u64 alignment) {
if (!is_coherent && type == BufferType::Stream) {
size = Common::alignUp(size, instance.NonCoherentAtomSize());
}
ASSERT(size <= stream_buffer_size);
mapped_size = size;
if (alignment > 0) {
offset = Common::alignUp(offset, alignment);
}
bool invalidate{false};
if (offset + size > stream_buffer_size) {
// The buffer would overflow, save the amount of used watches and reset the state.
invalidate = true;
invalidation_mark = current_watch_cursor;
current_watch_cursor = 0;
offset = 0;
// Swap watches and reset waiting cursors.
std::swap(previous_watches, current_watches);
wait_cursor = 0;
wait_bound = 0;
}
const u64 mapped_upper_bound = offset + size;
WaitPendingOperations(mapped_upper_bound);
return std::make_tuple(mapped + offset, offset, invalidate);
}
void StreamBuffer::Commit(u64 size) {
if (!is_coherent && type == BufferType::Stream) {
size = Common::alignUp(size, instance.NonCoherentAtomSize());
}
ASSERT_MSG(size <= mapped_size, "Reserved size {} is too small compared to {}", mapped_size,
size);
const vk::MappedMemoryRange range = {
.memory = memory,
.offset = offset,
.size = size,
};
if (!is_coherent && type == BufferType::Download) {
device.invalidateMappedMemoryRanges(range);
} else if (!is_coherent) {
device.flushMappedMemoryRanges(range);
}
offset += size;
if (current_watch_cursor + 1 >= current_watches.size()) {
// Ensure that there are enough watches.
ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK);
}
auto& watch = current_watches[current_watch_cursor++];
watch.upper_bound = offset;
watch.tick = scheduler.CurrentTick();
}
void StreamBuffer::CreateBuffers(u64 prefered_size) {
const vk::Device device = instance.GetDevice();
const auto memory_properties = instance.GetPhysicalDevice().getMemoryProperties();
const u32 preferred_type = GetMemoryType(memory_properties, type);
const vk::MemoryType mem_type = memory_properties.memoryTypes[preferred_type];
const u32 preferred_heap = mem_type.heapIndex;
is_coherent =
static_cast<bool>(mem_type.propertyFlags & vk::MemoryPropertyFlagBits::eHostCoherent);
// Substract from the preferred heap size some bytes to avoid getting out of memory.
const vk::DeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size;
// As per DXVK's example, using `heap_size / 2`
const vk::DeviceSize allocable_size = heap_size / 2;
buffer = device.createBuffer({
.size = std::min(prefered_size, allocable_size),
.usage = usage,
});
const auto requirements_chain =
device
.getBufferMemoryRequirements2<vk::MemoryRequirements2, vk::MemoryDedicatedRequirements>(
{.buffer = buffer});
const auto& requirements = requirements_chain.get<vk::MemoryRequirements2>();
const auto& dedicated_requirements = requirements_chain.get<vk::MemoryDedicatedRequirements>();
stream_buffer_size = static_cast<u64>(requirements.memoryRequirements.size);
LOG_INFO(Render_Vulkan, "Creating {} buffer with size {} KiB with flags {}",
BufferTypeName(type), stream_buffer_size / 1024,
vk::to_string(mem_type.propertyFlags));
if (dedicated_requirements.prefersDedicatedAllocation) {
vk::StructureChain<vk::MemoryAllocateInfo, vk::MemoryDedicatedAllocateInfo> alloc_chain =
{};
auto& alloc_info = alloc_chain.get<vk::MemoryAllocateInfo>();
alloc_info.allocationSize = requirements.memoryRequirements.size;
alloc_info.memoryTypeIndex = preferred_type;
auto& dedicated_alloc_info = alloc_chain.get<vk::MemoryDedicatedAllocateInfo>();
dedicated_alloc_info.buffer = buffer;
memory = device.allocateMemory(alloc_chain.get());
} else {
memory = device.allocateMemory({
.allocationSize = requirements.memoryRequirements.size,
.memoryTypeIndex = preferred_type,
});
}
device.bindBufferMemory(buffer, memory, 0);
mapped = reinterpret_cast<u8*>(device.mapMemory(memory, 0, VK_WHOLE_SIZE));
if (instance.HasDebuggingToolAttached()) {
SetObjectName(device, buffer, "StreamBuffer({}): {} KiB {}", BufferTypeName(type),
stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags));
SetObjectName(device, memory, "StreamBufferMemory({}): {} Kib {}", BufferTypeName(type),
stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags));
}
}
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size) {
watches.resize(watches.size() + grow_size);
}
void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) {
if (!invalidation_mark) {
return;
}
while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) {
auto& watch = previous_watches[wait_cursor];
wait_bound = watch.upper_bound;
scheduler.Wait(watch.tick);
++wait_cursor;
}
}
} // namespace Vulkan

View file

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <span>
#include <tuple>
#include <vector>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Vulkan {
enum class BufferType : u32 {
Upload = 0,
Download = 1,
Stream = 2,
};
class Instance;
class Scheduler;
class StreamBuffer final {
static constexpr std::size_t MAX_BUFFER_VIEWS = 3;
public:
explicit StreamBuffer(const Instance& instance, Scheduler& scheduler,
vk::BufferUsageFlags usage, u64 size,
BufferType type = BufferType::Stream);
~StreamBuffer();
/**
* Reserves a region of memory from the stream buffer.
* @param size Size to reserve.
* @returns A pair of a raw memory pointer (with offset added), and the buffer offset
*/
std::tuple<u8*, u64, bool> Map(u64 size, u64 alignment);
/// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy.
void Commit(u64 size);
vk::Buffer Handle() const noexcept {
return buffer;
}
private:
struct Watch {
u64 tick{};
u64 upper_bound{};
};
/// Creates Vulkan buffer handles committing the required the required memory.
void CreateBuffers(u64 prefered_size);
/// Increases the amount of watches available.
void ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size);
void WaitPendingOperations(u64 requested_upper_bound);
private:
const Instance& instance; ///< Vulkan instance.
Scheduler& scheduler; ///< Command scheduler.
vk::Device device;
vk::Buffer buffer; ///< Mapped buffer.
vk::DeviceMemory memory; ///< Memory allocation.
u8* mapped{}; ///< Pointer to the mapped memory
u64 stream_buffer_size{}; ///< Stream buffer size.
vk::BufferUsageFlags usage{};
BufferType type;
u64 offset{}; ///< Buffer iterator.
u64 mapped_size{}; ///< Size reserved for the current copy.
bool is_coherent{}; ///< True if the buffer is coherent
std::vector<Watch> current_watches; ///< Watches recorded in the current iteration.
std::size_t current_watch_cursor{}; ///< Count of watches, reset on invalidation.
std::optional<std::size_t> invalidation_mark; ///< Number of watches used in the previous cycle.
std::vector<Watch> previous_watches; ///< Watches used in the previous iteration.
std::size_t wait_cursor{}; ///< Last watch being waited for completion.
u64 wait_bound{}; ///< Highest offset being watched for completion.
};
} // namespace Vulkan

View file

@ -0,0 +1,225 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <limits>
#include "common/assert.h"
#include "common/logging/log.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
namespace Vulkan {
Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window)
: instance{instance_}, surface{CreateSurface(instance.GetInstance(), window)} {
FindPresentFormat();
Create(window.getWidth(), window.getHeight(), surface);
}
Swapchain::~Swapchain() {
Destroy();
instance.GetInstance().destroySurfaceKHR(surface);
}
void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) {
width = width_;
height = height_;
surface = surface_;
needs_recreation = false;
Destroy();
SetSurfaceProperties();
const std::array queue_family_indices = {
instance.GetGraphicsQueueFamilyIndex(),
instance.GetPresentQueueFamilyIndex(),
};
const bool exclusive = queue_family_indices[0] == queue_family_indices[1];
const u32 queue_family_indices_count = exclusive ? 1u : 2u;
const vk::SharingMode sharing_mode =
exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent;
const vk::SwapchainCreateInfoKHR swapchain_info = {
.surface = surface,
.minImageCount = image_count,
.imageFormat = surface_format.format,
.imageColorSpace = surface_format.colorSpace,
.imageExtent = extent,
.imageArrayLayers = 1,
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment |
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst,
.imageSharingMode = sharing_mode,
.queueFamilyIndexCount = queue_family_indices_count,
.pQueueFamilyIndices = queue_family_indices.data(),
.preTransform = transform,
.compositeAlpha = composite_alpha,
.presentMode = vk::PresentModeKHR::eMailbox,
.clipped = true,
.oldSwapchain = nullptr,
};
try {
swapchain = instance.GetDevice().createSwapchainKHR(swapchain_info);
} catch (vk::SystemError& err) {
LOG_CRITICAL(Render_Vulkan, "{}", err.what());
UNREACHABLE();
}
SetupImages();
RefreshSemaphores();
}
bool Swapchain::AcquireNextImage() {
vk::Device device = instance.GetDevice();
vk::Result result =
device.acquireNextImageKHR(swapchain, std::numeric_limits<u64>::max(),
image_acquired[frame_index], VK_NULL_HANDLE, &image_index);
switch (result) {
case vk::Result::eSuccess:
break;
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorSurfaceLostKHR:
case vk::Result::eErrorOutOfDateKHR:
needs_recreation = true;
break;
default:
LOG_CRITICAL(Render_Vulkan, "Swapchain acquire returned unknown result {}",
vk::to_string(result));
UNREACHABLE();
break;
}
return !needs_recreation;
}
void Swapchain::Present() {
if (needs_recreation) {
return;
}
const vk::PresentInfoKHR present_info = {
.waitSemaphoreCount = 1,
.pWaitSemaphores = &present_ready[image_index],
.swapchainCount = 1,
.pSwapchains = &swapchain,
.pImageIndices = &image_index,
};
try {
[[maybe_unused]] vk::Result result = instance.GetPresentQueue().presentKHR(present_info);
} catch (vk::OutOfDateKHRError&) {
needs_recreation = true;
} catch (const vk::SystemError& err) {
LOG_CRITICAL(Render_Vulkan, "Swapchain presentation failed {}", err.what());
UNREACHABLE();
}
frame_index = (frame_index + 1) % image_count;
}
void Swapchain::FindPresentFormat() {
const auto formats = instance.GetPhysicalDevice().getSurfaceFormatsKHR(surface);
// If there is a single undefined surface format, the device doesn't care, so we'll just use
// RGBA.
if (formats[0].format == vk::Format::eUndefined) {
surface_format.format = vk::Format::eR8G8B8A8Unorm;
surface_format.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;
return;
}
// Try to find a suitable format.
for (const vk::SurfaceFormatKHR& sformat : formats) {
vk::Format format = sformat.format;
if (format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eB8G8R8A8Unorm) {
continue;
}
surface_format.format = format;
surface_format.colorSpace = sformat.colorSpace;
return;
}
UNREACHABLE_MSG("Unable to find required swapchain format!");
}
void Swapchain::SetSurfaceProperties() {
const vk::SurfaceCapabilitiesKHR capabilities =
instance.GetPhysicalDevice().getSurfaceCapabilitiesKHR(surface);
extent = capabilities.currentExtent;
if (capabilities.currentExtent.width == std::numeric_limits<u32>::max()) {
extent.width = std::max(capabilities.minImageExtent.width,
std::min(capabilities.maxImageExtent.width, width));
extent.height = std::max(capabilities.minImageExtent.height,
std::min(capabilities.maxImageExtent.height, height));
}
// Select number of images in swap chain, we prefer one buffer in the background to work on
image_count = capabilities.minImageCount + 1;
if (capabilities.maxImageCount > 0) {
image_count = std::min(image_count, capabilities.maxImageCount);
}
// Prefer identity transform if possible
transform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
if (!(capabilities.supportedTransforms & transform)) {
transform = capabilities.currentTransform;
}
// Opaque is not supported everywhere.
composite_alpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
if (!(capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque)) {
composite_alpha = vk::CompositeAlphaFlagBitsKHR::eInherit;
}
}
void Swapchain::Destroy() {
vk::Device device = instance.GetDevice();
if (swapchain) {
device.destroySwapchainKHR(swapchain);
}
for (u32 i = 0; i < image_count; i++) {
device.destroySemaphore(image_acquired[i]);
device.destroySemaphore(present_ready[i]);
}
image_acquired.clear();
present_ready.clear();
}
void Swapchain::RefreshSemaphores() {
const vk::Device device = instance.GetDevice();
image_acquired.resize(image_count);
present_ready.resize(image_count);
for (vk::Semaphore& semaphore : image_acquired) {
semaphore = device.createSemaphore({});
}
for (vk::Semaphore& semaphore : present_ready) {
semaphore = device.createSemaphore({});
}
if (instance.HasDebuggingToolAttached()) {
for (u32 i = 0; i < image_count; ++i) {
SetObjectName(device, image_acquired[i], "Swapchain Semaphore: image_acquired {}", i);
SetObjectName(device, present_ready[i], "Swapchain Semaphore: present_ready {}", i);
}
}
}
void Swapchain::SetupImages() {
vk::Device device = instance.GetDevice();
images = device.getSwapchainImagesKHR(swapchain);
image_count = static_cast<u32>(images.size());
if (instance.HasDebuggingToolAttached()) {
for (u32 i = 0; i < image_count; ++i) {
SetObjectName(device, images[i], "Swapchain Image {}", i);
}
}
}
} // namespace Vulkan

View file

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <vector>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Frontend {
class WindowSDL;
}
namespace Vulkan {
class Instance;
class Scheduler;
class Swapchain {
public:
explicit Swapchain(const Instance& instance, const Frontend::WindowSDL& window);
~Swapchain();
/// Creates (or recreates) the swapchain with a given size.
void Create(u32 width, u32 height, vk::SurfaceKHR surface);
/// Acquires the next image in the swapchain.
bool AcquireNextImage();
/// Presents the current image and move to the next one
void Present();
vk::SurfaceKHR GetSurface() const {
return surface;
}
vk::Image Image() const {
return images[image_index];
}
vk::SurfaceFormatKHR GetSurfaceFormat() const {
return surface_format;
}
vk::SwapchainKHR GetHandle() const {
return swapchain;
}
u32 GetWidth() const {
return width;
}
u32 GetHeight() const {
return height;
}
u32 GetImageCount() const {
return image_count;
}
u32 GetFrameIndex() const {
return frame_index;
}
vk::Extent2D GetExtent() const {
return extent;
}
[[nodiscard]] vk::Semaphore GetImageAcquiredSemaphore() const {
return image_acquired[frame_index];
}
[[nodiscard]] vk::Semaphore GetPresentReadySemaphore() const {
return present_ready[image_index];
}
private:
/// Selects the best available swapchain image format
void FindPresentFormat();
/// Sets the surface properties according to device capabilities
void SetSurfaceProperties();
/// Destroys current swapchain resources
void Destroy();
/// Performs creation of image views and framebuffers from the swapchain images
void SetupImages();
/// Creates the image acquired and present ready semaphores
void RefreshSemaphores();
private:
const Instance& instance;
vk::SwapchainKHR swapchain{};
vk::SurfaceKHR surface{};
vk::SurfaceFormatKHR surface_format;
vk::Extent2D extent;
vk::SurfaceTransformFlagBitsKHR transform;
vk::CompositeAlphaFlagBitsKHR composite_alpha;
std::vector<vk::Image> images;
std::vector<vk::Semaphore> image_acquired;
std::vector<vk::Semaphore> present_ready;
u32 width = 0;
u32 height = 0;
u32 image_count = 0;
u32 image_index = 0;
u32 frame_index = 0;
bool needs_recreation = true;
};
} // namespace Vulkan

View file

@ -0,0 +1,152 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/config.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/texture_cache/image.h"
#include <vk_mem_alloc.h>
namespace VideoCore {
using namespace Vulkan;
using VideoOutFormat = Libraries::VideoOut::PixelFormat;
using Libraries::VideoOut::TilingMode;
[[nodiscard]] vk::Format ConvertPixelFormat(const VideoOutFormat format) {
switch (format) {
case VideoOutFormat::A8R8G8B8Srgb:
return vk::Format::eB8G8R8A8Srgb;
case VideoOutFormat::A8B8G8R8Srgb:
return vk::Format::eA8B8G8R8SrgbPack32;
case VideoOutFormat::A2R10G10B10:
case VideoOutFormat::A2R10G10B10Srgb:
return vk::Format::eA2R10G10B10UnormPack32;
default:
break;
}
UNREACHABLE_MSG("Unknown format={}", static_cast<u32>(format));
return {};
}
[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(const vk::Format format) {
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eTransferSrc |
vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eSampled;
if (false /*&& IsDepthStencilFormat(format)*/) {
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
} else {
// usage |= vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage;
}
return usage;
}
ImageInfo::ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group) noexcept {
const auto& attrib = group.attrib;
is_tiled = attrib.tiling_mode == TilingMode::Tile;
pixel_format = ConvertPixelFormat(attrib.pixel_format);
type = vk::ImageType::e2D;
size.width = attrib.width;
size.height = attrib.height;
pitch = attrib.tiling_mode == TilingMode::Linear ? size.width : (size.width + 127) >> 7;
const bool is_32bpp = pixel_format == vk::Format::eB8G8R8A8Srgb ||
pixel_format == vk::Format::eA8B8G8R8SrgbPack32;
ASSERT(is_32bpp);
if (!is_tiled) {
guest_size_bytes = pitch * size.height * 4;
return;
}
if (Config::isNeoMode()) {
guest_size_bytes = pitch * 128 * ((size.height + 127) & (~127)) * 4;
} else {
guest_size_bytes = pitch * 128 * ((size.height + 63) & (~63)) * 4;
}
}
UniqueImage::UniqueImage(vk::Device device_, VmaAllocator allocator_)
: device{device_}, allocator{allocator_} {}
UniqueImage::~UniqueImage() {
if (image) {
vmaDestroyImage(allocator, image, allocation);
}
}
void UniqueImage::Create(const vk::ImageCreateInfo& image_ci) {
const VmaAllocationCreateInfo alloc_info = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
};
const VkImageCreateInfo image_ci_unsafe = static_cast<VkImageCreateInfo>(image_ci);
VkImage unsafe_image{};
VkResult result = vmaCreateImage(allocator, &image_ci_unsafe, &alloc_info, &unsafe_image,
&allocation, nullptr);
ASSERT_MSG(result == VK_SUCCESS, "Failed allocating image with error {}",
vk::to_string(vk::Result{result}));
image = vk::Image{unsafe_image};
}
Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
const ImageInfo& info_, VAddr cpu_addr)
: instance{&instance_}, scheduler{&scheduler_}, info{info_}, image{instance->GetDevice(),
instance->GetAllocator()},
cpu_addr{cpu_addr}, cpu_addr_end{cpu_addr + info.guest_size_bytes} {
vk::ImageCreateFlags flags{};
if (info.type == vk::ImageType::e2D && info.resources.layers >= 6 &&
info.size.width == info.size.height) {
flags |= vk::ImageCreateFlagBits::eCubeCompatible;
}
if (info.type == vk::ImageType::e3D) {
flags |= vk::ImageCreateFlagBits::e2DArrayCompatible;
}
const vk::ImageCreateInfo image_ci = {
.flags = flags,
.imageType = info.type,
.format = info.pixel_format,
.extent{
.width = info.size.width,
.height = info.size.height,
.depth = info.size.depth,
},
.mipLevels = static_cast<u32>(info.resources.levels),
.arrayLayers = static_cast<u32>(info.resources.layers),
.tiling = vk::ImageTiling::eOptimal,
.usage = ImageUsageFlags(info.pixel_format),
.initialLayout = vk::ImageLayout::eUndefined,
};
image.Create(image_ci);
const vk::ImageMemoryBarrier init_barrier = {
.srcAccessMask = vk::AccessFlagBits::eNone,
.dstAccessMask = vk::AccessFlagBits::eNone,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eGeneral,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
const auto cmdbuf = scheduler->CommandBuffer();
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTopOfPipe, vk::DependencyFlagBits::eByRegion,
{}, {}, init_barrier);
}
Image::~Image() = default;
} // namespace VideoCore

View file

@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/enum.h"
#include "common/types.h"
#include "core/libraries/videoout/buffer.h"
#include "video_core/pixel_format.h"
#include "video_core/renderer_vulkan/vk_common.h"
#include "video_core/texture_cache/types.h"
namespace Vulkan {
class Instance;
class Scheduler;
} // namespace Vulkan
VK_DEFINE_HANDLE(VmaAllocation)
VK_DEFINE_HANDLE(VmaAllocator)
namespace VideoCore {
enum ImageFlagBits : u32 {
CpuModified = 1 << 2, ///< Contents have been modified from the CPU
GpuModified = 1 << 3, ///< Contents have been modified from the GPU
Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU
Registered = 1 << 6, ///< True when the image is registered
Picked = 1 << 7, ///< Temporary flag to mark the image as picked
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
struct ImageInfo {
ImageInfo() = default;
explicit ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group) noexcept;
bool is_tiled = false;
vk::Format pixel_format = vk::Format::eUndefined;
vk::ImageType type = vk::ImageType::e1D;
SubresourceExtent resources;
Extent3D size{1, 1, 1};
u32 pitch = 0;
u32 guest_size_bytes = 0;
};
struct Handle {
VmaAllocation allocation;
VkImage image;
Handle() = default;
Handle(Handle&& other)
: image{std::exchange(other.image, VK_NULL_HANDLE)},
allocation{std::exchange(other.allocation, VK_NULL_HANDLE)} {}
Handle& operator=(Handle&& other) {
image = std::exchange(other.image, VK_NULL_HANDLE);
allocation = std::exchange(other.allocation, VK_NULL_HANDLE);
return *this;
}
};
struct UniqueImage {
explicit UniqueImage(vk::Device device, VmaAllocator allocator);
~UniqueImage();
UniqueImage(const UniqueImage&) = delete;
UniqueImage& operator=(const UniqueImage&) = delete;
UniqueImage(UniqueImage&& other) : image{std::exchange(other.image, VK_NULL_HANDLE)} {}
UniqueImage& operator=(UniqueImage&& other) {
image = std::exchange(other.image, VK_NULL_HANDLE);
return *this;
}
void Create(const vk::ImageCreateInfo& image_ci);
operator vk::Image() const {
return image;
}
private:
vk::Device device;
VmaAllocator allocator;
VmaAllocation allocation;
vk::Image image{};
};
struct Image {
explicit Image(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
const ImageInfo& info, VAddr cpu_addr);
~Image();
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
Image(Image&&) = default;
Image& operator=(Image&&) = default;
[[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept {
const VAddr overlap_end = overlap_cpu_addr + overlap_size;
return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;
}
const Vulkan::Instance* instance;
Vulkan::Scheduler* scheduler;
ImageInfo info;
UniqueImage image;
vk::ImageAspectFlags aspect_mask;
ImageFlagBits flags = ImageFlagBits::CpuModified;
VAddr cpu_addr = 0;
VAddr cpu_addr_end = 0;
};
} // namespace VideoCore

View file

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/texture_cache/image_view.h"
namespace VideoCore {
[[nodiscard]] vk::ImageViewType ConvertImageViewType(const ImageViewType type) {
switch (type) {
case ImageViewType::e1D:
return vk::ImageViewType::e1D;
case ImageViewType::e2D:
return vk::ImageViewType::e2D;
case ImageViewType::e3D:
return vk::ImageViewType::e3D;
case ImageViewType::Buffer:
break;
default:
break;
}
UNREACHABLE_MSG("Invalid image type={}", static_cast<u32>(type));
return {};
}
[[nodiscard]] vk::Format ConvertPixelFormat(const PixelFormat format) {
switch (format) {
default:
break;
}
UNREACHABLE_MSG("Unknown format={}", static_cast<u32>(format));
return {};
}
ImageView::ImageView(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
const ImageViewInfo& info_, vk::Image image)
: info{info_} {
const vk::ImageViewCreateInfo image_view_ci = {
.image = image,
.viewType = ConvertImageViewType(info.type),
.format = ConvertPixelFormat(info.format),
.components{
.r = vk::ComponentSwizzle::eIdentity,
.g = vk::ComponentSwizzle::eIdentity,
.b = vk::ComponentSwizzle::eIdentity,
.a = vk::ComponentSwizzle::eIdentity,
},
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0U,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
image_view = instance.GetDevice().createImageViewUnique(image_view_ci);
}
ImageView::~ImageView() = default;
} // namespace VideoCore

View file

@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "video_core/pixel_format.h"
#include "video_core/renderer_vulkan/vk_common.h"
#include "video_core/texture_cache/types.h"
namespace Vulkan {
class Instance;
class Scheduler;
} // namespace Vulkan
namespace VideoCore {
enum class ImageViewType : u32 {
e1D,
e2D,
Cube,
e3D,
e1DArray,
e2DArray,
CubeArray,
Buffer,
};
enum class SwizzleSource : u32 {
Zero = 0,
One = 1,
R = 2,
G = 3,
B = 4,
A = 5,
};
struct ImageViewInfo {
ImageViewType type{};
PixelFormat format{};
SubresourceRange range;
u8 x_source = static_cast<u8>(SwizzleSource::R);
u8 y_source = static_cast<u8>(SwizzleSource::G);
u8 z_source = static_cast<u8>(SwizzleSource::B);
u8 w_source = static_cast<u8>(SwizzleSource::A);
};
class ImageView {
explicit ImageView(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
const ImageViewInfo& info, vk::Image image);
~ImageView();
ImageId image_id{};
Extent3D size{0, 0, 0};
ImageViewInfo info{};
vk::UniqueImageView image_view;
};
} // namespace VideoCore

View file

@ -0,0 +1,176 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <bit>
#include <compare>
#include <numeric>
#include <type_traits>
#include <utility>
#include <vector>
#include "common/assert.h"
#include "common/types.h"
namespace VideoCore {
struct SlotId {
static constexpr u32 INVALID_INDEX = std::numeric_limits<u32>::max();
constexpr auto operator<=>(const SlotId&) const noexcept = default;
constexpr explicit operator bool() const noexcept {
return index != INVALID_INDEX;
}
u32 index = INVALID_INDEX;
};
template <class T>
class SlotVector {
constexpr static std::size_t InitialCapacity = 1024;
public:
SlotVector() {
Reserve(InitialCapacity);
}
~SlotVector() noexcept {
std::size_t index = 0;
for (u64 bits : stored_bitset) {
for (std::size_t bit = 0; bits; ++bit, bits >>= 1) {
if ((bits & 1) != 0) {
values[index + bit].object.~T();
}
}
index += 64;
}
delete[] values;
}
[[nodiscard]] T& operator[](SlotId id) noexcept {
ValidateIndex(id);
return values[id.index].object;
}
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
ValidateIndex(id);
return values[id.index].object;
}
template <typename... Args>
[[nodiscard]] SlotId insert(Args&&... args) noexcept {
const u32 index = FreeValueIndex();
new (&values[index].object) T(std::forward<Args>(args)...);
SetStorageBit(index);
return SlotId{index};
}
template <typename... Args>
[[nodiscard]] SlotId swap_and_insert(SlotId existing_id, Args&&... args) noexcept {
const u32 index = FreeValueIndex();
T& existing_value = values[existing_id.index].object;
new (&values[index].object) T(std::move(existing_value));
existing_value.~T();
new (&values[existing_id.index].object) T(std::forward<Args>(args)...);
SetStorageBit(index);
return SlotId{index};
}
void erase(SlotId id) noexcept {
values[id.index].object.~T();
free_list.push_back(id.index);
ResetStorageBit(id.index);
}
std::size_t size() const noexcept {
return values_capacity - free_list.size();
}
private:
struct NonTrivialDummy {
NonTrivialDummy() noexcept {}
};
union Entry {
Entry() noexcept : dummy{} {}
~Entry() noexcept {}
NonTrivialDummy dummy;
T object;
};
void SetStorageBit(u32 index) noexcept {
stored_bitset[index / 64] |= u64(1) << (index % 64);
}
void ResetStorageBit(u32 index) noexcept {
stored_bitset[index / 64] &= ~(u64(1) << (index % 64));
}
bool ReadStorageBit(u32 index) noexcept {
return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0;
}
void ValidateIndex([[maybe_unused]] SlotId id) const noexcept {
DEBUG_ASSERT(id);
DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
}
[[nodiscard]] u32 FreeValueIndex() noexcept {
if (free_list.empty()) {
Reserve(values_capacity ? (values_capacity << 1) : 1);
}
const u32 free_index = free_list.back();
free_list.pop_back();
return free_index;
}
void Reserve(std::size_t new_capacity) noexcept {
Entry* const new_values = new Entry[new_capacity];
std::size_t index = 0;
for (u64 bits : stored_bitset) {
for (std::size_t bit = 0; bits; ++bit, bits >>= 1) {
const std::size_t i = index + bit;
if ((bits & 1) == 0) {
continue;
}
T& old_value = values[i].object;
new (&new_values[i].object) T(std::move(old_value));
old_value.~T();
}
index += 64;
}
stored_bitset.resize((new_capacity + 63) / 64);
const std::size_t old_free_size = free_list.size();
free_list.resize(old_free_size + (new_capacity - values_capacity));
std::iota(free_list.begin() + old_free_size, free_list.end(),
static_cast<u32>(values_capacity));
delete[] values;
values = new_values;
values_capacity = new_capacity;
}
Entry* values = nullptr;
std::size_t values_capacity = 0;
std::vector<u64> stored_bitset;
std::vector<u32> free_list;
};
} // namespace VideoCore
template <>
struct std::hash<VideoCore::SlotId> {
std::size_t operator()(const VideoCore::SlotId& id) const noexcept {
return std::hash<u32>{}(id.index);
}
};

View file

@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/config.h"
#include "core/libraries/videoout/buffer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/texture_cache/texture_cache.h"
#include "video_core/texture_cache/tile_manager.h"
#ifndef _WIN64
#include <signal.h>
#include <sys/mman.h>
#define PAGE_NOACCESS PROT_NONE
#define PAGE_READWRITE (PROT_READ | PROT_WRITE)
#else
#include <Windows.h>
void mprotect(void* addr, size_t len, int prot) {
DWORD old_prot{};
BOOL result = VirtualProtect(addr, len, prot, &old_prot);
ASSERT_MSG(result != 0, "Region protection failed");
}
#endif
namespace VideoCore {
static TextureCache* g_texture_cache = nullptr;
#ifndef _WIN64
void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) {
ucontext_t* ctx = reinterpret_cast<ucontext_t*>(raw_context);
const VAddr address = reinterpret_cast<VAddr>(info->si_addr);
if (ctx->uc_mcontext.gregs[REG_ERR] & 0x2) {
g_texture_cache->OnCpuWrite(address);
} else {
// Read not supported!
UNREACHABLE();
}
}
#else
LONG WINAPI GuestFaultSignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
const u32 ec = pExp->ExceptionRecord->ExceptionCode;
if (ec == EXCEPTION_ACCESS_VIOLATION) {
const auto info = pExp->ExceptionRecord->ExceptionInformation;
if (info[0] == 1) { // Write violation
g_texture_cache->OnCpuWrite(info[1]);
return EXCEPTION_CONTINUE_EXECUTION;
} /* else {
UNREACHABLE();
}*/
}
return EXCEPTION_CONTINUE_SEARCH; // pass further
}
#endif
static constexpr u64 StreamBufferSize = 128_MB;
TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_)
: instance{instance_}, scheduler{scheduler_},
staging{instance, scheduler, vk::BufferUsageFlagBits::eTransferSrc, StreamBufferSize,
Vulkan::BufferType::Upload} {
#ifndef _WIN64
sigset_t signal_mask;
sigemptyset(&signal_mask);
sigaddset(&signal_mask, SIGSEGV);
using HandlerType = decltype(sigaction::sa_sigaction);
struct sigaction guest_access_fault {};
guest_access_fault.sa_flags = SA_SIGINFO | SA_ONSTACK;
guest_access_fault.sa_sigaction = &GuestFaultSignalHandler;
guest_access_fault.sa_mask = signal_mask;
sigaction(SIGSEGV, &guest_access_fault, nullptr);
#else
veh_handle = AddVectoredExceptionHandler(1, GuestFaultSignalHandler);
ASSERT_MSG(veh_handle, "Failed to register an exception handler");
#endif
g_texture_cache = this;
}
TextureCache::~TextureCache() {
#if _WIN64
RemoveVectoredExceptionHandler(veh_handle);
#endif
}
void TextureCache::OnCpuWrite(VAddr address) {
const VAddr address_aligned = address & ~((1 << PageBits) - 1);
ForEachImageInRegion(address_aligned, 1 << PageBits, [&](ImageId image_id, Image& image) {
// Ensure image is reuploaded when accessed again.
image.flags |= ImageFlagBits::CpuModified;
// Untrack image, so the range is unprotected and the guest can write freely.
UntrackImage(image, image_id);
});
}
Image& TextureCache::FindDisplayBuffer(const Libraries::VideoOut::BufferAttributeGroup& group,
VAddr cpu_address) {
boost::container::small_vector<ImageId, 2> image_ids;
ForEachImageInRegion(cpu_address, group.size_in_bytes, [&](ImageId image_id, Image& image) {
if (image.cpu_addr == cpu_address) {
image_ids.push_back(image_id);
}
});
ASSERT_MSG(image_ids.size() <= 1, "Overlapping framebuffers not allowed!");
ImageId image_id{};
if (image_ids.empty()) {
image_id = slot_images.insert(instance, scheduler, ImageInfo{group}, cpu_address);
RegisterImage(image_id);
} else {
image_id = image_ids[0];
}
Image& image = slot_images[image_id];
if (True(image.flags & ImageFlagBits::CpuModified)) {
RefreshImage(image);
TrackImage(image, image_id);
}
return image;
}
void TextureCache::RefreshImage(Image& image) {
// Mark image as validated.
image.flags &= ~ImageFlagBits::CpuModified;
// Upload data to the staging buffer.
const auto [data, offset, _] = staging.Map(image.info.guest_size_bytes, 0);
const u8* image_data = reinterpret_cast<const u8*>(image.cpu_addr);
if (image.info.is_tiled) {
ConvertTileToLinear(data, image_data, image.info.size.width, image.info.size.height,
Config::isNeoMode());
} else {
std::memcpy(data, image_data, image.info.guest_size_bytes);
}
staging.Commit(image.info.guest_size_bytes);
// Copy to the image.
const vk::BufferImageCopy image_copy = {
.bufferOffset = offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {0, 0, 0},
.imageExtent = {image.info.size.width, image.info.size.height, 1},
};
const auto cmdbuf = scheduler.CommandBuffer();
const vk::ImageSubresourceRange range = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
};
const vk::ImageMemoryBarrier read_barrier = {
.srcAccessMask = vk::AccessFlagBits::eShaderRead,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eGeneral,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image.image,
.subresourceRange = range,
};
const vk::ImageMemoryBarrier write_barrier = {
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eGeneral,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image.image,
.subresourceRange = range,
};
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics,
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
{}, {}, read_barrier);
cmdbuf.copyBufferToImage(staging.Handle(), image.image, vk::ImageLayout::eTransferDstOptimal,
image_copy);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllGraphics,
vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier);
}
void TextureCache::RegisterImage(ImageId image_id) {
Image& image = slot_images[image_id];
ASSERT_MSG(False(image.flags & ImageFlagBits::Registered),
"Trying to register an already registered image");
image.flags |= ImageFlagBits::Registered;
ForEachPage(image.cpu_addr, image.info.guest_size_bytes,
[this, image_id](u64 page) { page_table[page].push_back(image_id); });
}
void TextureCache::UnregisterImage(ImageId image_id) {
Image& image = slot_images[image_id];
ASSERT_MSG(True(image.flags & ImageFlagBits::Registered),
"Trying to unregister an already registered image");
image.flags &= ~ImageFlagBits::Registered;
ForEachPage(image.cpu_addr, image.info.guest_size_bytes, [this, image_id](u64 page) {
const auto page_it = page_table.find(page);
if (page_it == page_table.end()) {
ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PageBits);
return;
}
auto& image_ids = page_it.value();
const auto vector_it = std::ranges::find(image_ids, image_id);
if (vector_it == image_ids.end()) {
ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", page << PageBits);
return;
}
image_ids.erase(vector_it);
});
slot_images.erase(image_id);
}
void TextureCache::TrackImage(Image& image, ImageId image_id) {
if (True(image.flags & ImageFlagBits::Tracked)) {
return;
}
image.flags |= ImageFlagBits::Tracked;
UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, 1);
}
void TextureCache::UntrackImage(Image& image, ImageId image_id) {
if (False(image.flags & ImageFlagBits::Tracked)) {
return;
}
image.flags &= ~ImageFlagBits::Tracked;
UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, -1);
}
void TextureCache::UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta) {
const u64 num_pages = ((addr + size - 1) >> PageBits) - (addr >> PageBits) + 1;
const u64 page_start = addr >> PageBits;
const u64 page_end = page_start + num_pages;
const auto pages_interval =
decltype(cached_pages)::interval_type::right_open(page_start, page_end);
if (delta > 0) {
cached_pages.add({pages_interval, delta});
}
const auto& range = cached_pages.equal_range(pages_interval);
for (const auto& [range, count] : boost::make_iterator_range(range)) {
const auto interval = range & pages_interval;
const VAddr interval_start_addr = boost::icl::first(interval) << PageBits;
const VAddr interval_end_addr = boost::icl::last_next(interval) << PageBits;
const u32 interval_size = interval_end_addr - interval_start_addr;
void* addr = reinterpret_cast<void*>(interval_start_addr);
if (delta > 0 && count == delta) {
mprotect(addr, interval_size, PAGE_NOACCESS);
} else if (delta < 0 && count == -delta) {
mprotect(addr, interval_size, PAGE_READWRITE);
} else {
ASSERT(count >= 0);
}
}
if (delta < 0) {
cached_pages.add({pages_interval, delta});
}
}
} // namespace VideoCore

View file

@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <forward_list>
#include <boost/container/small_vector.hpp>
#include <boost/icl/interval_map.hpp>
#include <tsl/robin_map.h>
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
#include "video_core/texture_cache/image.h"
#include "video_core/texture_cache/slot_vector.h"
namespace Core::Libraries::VideoOut {
struct BufferAttributeGroup;
}
namespace VideoCore {
class TextureCache {
static constexpr u64 PageBits = 14;
public:
explicit TextureCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler);
~TextureCache();
/// Invalidates any image in the logical page range.
void OnCpuWrite(VAddr address);
/// Retrieves the image handle of the image with the provided attributes and address.
Image& FindDisplayBuffer(const Libraries::VideoOut::BufferAttributeGroup& attribute,
VAddr cpu_address);
private:
/// Iterate over all page indices in a range
template <typename Func>
static void ForEachPage(PAddr addr, size_t size, Func&& func) {
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
const u64 page_end = (addr + size - 1) >> PageBits;
for (u64 page = addr >> PageBits; page <= page_end; ++page) {
if constexpr (RETURNS_BOOL) {
if (func(page)) {
break;
}
} else {
func(page);
}
}
}
template <typename Func>
void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 32> images;
ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) {
const auto it = page_table.find(page);
if (it == page_table.end()) {
if constexpr (BOOL_BREAK) {
return false;
} else {
return;
}
}
for (const ImageId image_id : it->second) {
Image& image = slot_images[image_id];
if (image.flags & ImageFlagBits::Picked) {
continue;
}
image.flags |= ImageFlagBits::Picked;
images.push_back(image_id);
if constexpr (BOOL_BREAK) {
if (func(image_id, image)) {
return true;
}
} else {
func(image_id, image);
}
}
if constexpr (BOOL_BREAK) {
return false;
}
});
for (const ImageId image_id : images) {
slot_images[image_id].flags &= ~ImageFlagBits::Picked;
}
}
/// Create an image from the given parameters
[[nodiscard]] ImageId InsertImage(const ImageInfo& info, VAddr cpu_addr);
/// Reuploads image contents.
void RefreshImage(Image& image);
/// Register image in the page table
void RegisterImage(ImageId image);
/// Unregister image from the page table
void UnregisterImage(ImageId image);
/// Track CPU reads and writes for image
void TrackImage(Image& image, ImageId image_id);
/// Stop tracking CPU reads and writes for image
void UntrackImage(Image& image, ImageId image_id);
/// Increase/decrease the number of surface in pages touching the specified region
void UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta);
private:
const Vulkan::Instance& instance;
Vulkan::Scheduler& scheduler;
Vulkan::StreamBuffer staging;
SlotVector<Image> slot_images;
tsl::robin_pg_map<u64, std::vector<ImageId>> page_table;
boost::icl::interval_map<VAddr, s32> cached_pages;
#ifdef _WIN64
void* veh_handle{};
#endif
};
} // namespace VideoCore

View file

@ -1,23 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/singleton.h"
#include "core/PS4/GPU/tile_manager.h"
#include <cstring>
#include "video_core/texture_cache/tile_manager.h"
namespace GPU {
namespace VideoCore {
static u32 IntLog2(u32 i) {
return 31 - __builtin_clz(i | 1u);
}
class TileManager {
public:
TileManager() {}
virtual ~TileManager() {}
std::mutex m_mutex;
};
class TileManager32 {
public:
u32 m_macro_tile_height = 0;
@ -148,14 +140,10 @@ public:
}
};
void convertTileToLinear(void* dst, const void* src, u32 width, u32 height, bool is_neo) {
void ConvertTileToLinear(u8* dst, const u8* src, u32 width, u32 height, bool is_neo) {
TileManager32 t;
t.Init(width, height, is_neo);
auto* g_TileManager = Common::Singleton<TileManager>::Instance();
std::scoped_lock lock{g_TileManager->m_mutex};
for (u32 y = 0; y < height; y++) {
u32 x = 0;
u64 linear_offset = y * width * 4;
@ -163,16 +151,14 @@ void convertTileToLinear(void* dst, const void* src, u32 width, u32 height, bool
for (; x + 1 < width; x += 2) {
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
*reinterpret_cast<u64*>(static_cast<u8*>(dst) + linear_offset) =
*reinterpret_cast<const u64*>(static_cast<const u8*>(src) + tiled_offset);
std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u64));
linear_offset += 8;
}
if (x < width) {
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
*reinterpret_cast<u32*>(static_cast<u8*>(dst) + linear_offset) =
*reinterpret_cast<const u32*>(static_cast<const u8*>(src) + tiled_offset);
std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u32));
}
}
}
} // namespace GPU
} // namespace VideoCore

View file

@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace VideoCore {
/// Converts tiled texture data to linear format.
void ConvertTileToLinear(u8* dst, const u8* src, u32 width, u32 height, bool neo);
} // namespace VideoCore

View file

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "video_core/texture_cache/slot_vector.h"
namespace VideoCore {
using ImageId = SlotId;
using ImageViewId = SlotId;
struct Offset2D {
s32 x;
s32 y;
};
struct Offset3D {
s32 x;
s32 y;
s32 z;
};
struct Region2D {
Offset2D start;
Offset2D end;
};
struct Extent2D {
u32 width;
u32 height;
};
struct Extent3D {
u32 width;
u32 height;
u32 depth;
};
struct SubresourceLayers {
s32 base_level = 0;
s32 base_layer = 0;
s32 num_layers = 1;
};
struct SubresourceBase {
s32 level = 0;
s32 layer = 0;
};
struct SubresourceExtent {
s32 levels = 1;
s32 layers = 1;
};
struct SubresourceRange {
SubresourceBase base;
SubresourceExtent extent;
};
struct ImageCopy {
SubresourceLayers src_subresource;
SubresourceLayers dst_subresource;
Offset3D src_offset;
Offset3D dst_offset;
Extent3D extent;
};
struct BufferImageCopy {
std::size_t buffer_offset;
std::size_t buffer_size;
u32 buffer_row_length;
u32 buffer_image_height;
SubresourceLayers image_subresource;
Offset3D image_offset;
Extent3D image_extent;
};
struct BufferCopy {
u64 src_offset;
u64 dst_offset;
std::size_t size;
};
} // namespace VideoCore

View file

@ -15,7 +15,7 @@
#include <algorithm>
void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) {
Emu::VulkanExt ext;
/*Emu::VulkanExt ext;
vulkanGetInstanceExtensions(&ext);
VkApplicationInfo app_info{};
@ -68,12 +68,13 @@ void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) {
ASSERT_MSG(ctx->m_graphic_ctx.m_device, "Can't create vulkan device");
vulkanCreateQueues(&ctx->m_graphic_ctx, queues);
ctx->swapchain = vulkanCreateSwapchain(&ctx->m_graphic_ctx, 2);
ctx->swapchain = vulkanCreateSwapchain(&ctx->m_graphic_ctx, 2);*/
}
Emu::VulkanSwapchain Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics::GraphicCtx* ctx,
u32 image_count) {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
return {};
/*auto window_ctx = Common::Singleton<Emu::WindowCtx>::Instance();
const auto& capabilities = window_ctx->m_surface_capabilities.capabilities;
Emu::VulkanSwapchain s{};
@ -166,7 +167,7 @@ Emu::VulkanSwapchain Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics
result = vkCreateFence(ctx->m_device, &fence_info, nullptr, &s.present_complete_fence);
ASSERT_MSG(result == VK_SUCCESS, "Can't create vulkan fence");
return s;
return s;*/
}
void Graphics::Vulkan::vulkanCreateQueues(HLE::Libs::Graphics::GraphicCtx* ctx,
@ -195,7 +196,7 @@ VkDevice Graphics::Vulkan::vulkanCreateDevice(VkPhysicalDevice physical_device,
VkSurfaceKHR surface, const Emu::VulkanExt* r,
const Emu::VulkanQueues& queues,
const std::vector<const char*>& device_extensions) {
std::vector<VkDeviceQueueCreateInfo> queue_create_info(queues.family_count);
/*std::vector<VkDeviceQueueCreateInfo> queue_create_info(queues.family_count);
std::vector<std::vector<float>> queue_priority(queues.family_count);
uint32_t queue_create_info_num = 0;
@ -237,10 +238,10 @@ VkDevice Graphics::Vulkan::vulkanCreateDevice(VkPhysicalDevice physical_device,
vkCreateDevice(physical_device, &create_info, nullptr, &device);
return device;
return device;*/
}
void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) {
u32 required_extensions_count = 0;
/*u32 required_extensions_count = 0;
u32 available_extensions_count = 0;
u32 available_layers_count = 0;
ext->required_extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count);
@ -270,14 +271,14 @@ void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) {
LOG_INFO(Render_Vulkan,
"Vulkan available layer: {}, specVersion = {}, implVersion = {}, {}", l.layerName,
l.specVersion, l.implementationVersion, l.description);
}
}*/
}
void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice(
VkInstance instance, VkSurfaceKHR surface, const std::vector<const char*>& device_extensions,
Emu::VulkanSurfaceCapabilities* out_capabilities, VkPhysicalDevice* out_device,
Emu::VulkanQueues* out_queues) {
u32 count_devices = 0;
/*u32 count_devices = 0;
vkEnumeratePhysicalDevices(instance, &count_devices, nullptr);
std::vector<VkPhysicalDevice> devices(count_devices);
@ -306,14 +307,14 @@ void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice(
found_best_queues = qs;
}
*out_device = found_best_device;
*out_queues = found_best_queues;
*out_queues = found_best_queues;*/
}
Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device,
VkSurfaceKHR surface) {
Emu::VulkanQueues qs;
u32 queue_family_count = 0;
/*u32 queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr);
std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data());
@ -391,14 +392,15 @@ Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device,
}
index++;
}
}
}*/
return qs;
}
void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_device,
VkSurfaceKHR surface,
Emu::VulkanSurfaceCapabilities* surfaceCap) {
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surfaceCap->capabilities);
/*vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface,
&surfaceCap->capabilities);
uint32_t formats_count = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, nullptr);
@ -426,7 +428,7 @@ void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_de
surfaceCap->is_format_unorm_bgra32 = true;
break;
}
}
}*/
}
static void set_image_layout(VkCommandBuffer buffer, HLE::Libs::Graphics::VulkanImage* dst_image,
@ -484,8 +486,8 @@ static void set_image_layout(VkCommandBuffer buffer, HLE::Libs::Graphics::Vulkan
VkPipelineStageFlags src_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkPipelineStageFlags dest_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
vkCmdPipelineBarrier(buffer, src_stages, dest_stages, 0, 0, nullptr, 0, nullptr, 1,
&imageMemoryBarrier);
// vkCmdPipelineBarrier(buffer, src_stages, dest_stages, 0, 0, nullptr, 0, nullptr, 1,
// &imageMemoryBarrier);
dst_image->layout = new_image_layout;
}
@ -528,9 +530,9 @@ void Graphics::Vulkan::vulkanBlitImage(GPU::CommandBuffer* buffer,
region.dstOffsets[1].y = static_cast<int>(dst_swapchain->swapchain_extent.height);
region.dstOffsets[1].z = 1;
vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region,
VK_FILTER_LINEAR);
// vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
// swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region,
// VK_FILTER_LINEAR);
set_image_layout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
@ -548,10 +550,10 @@ void Graphics::Vulkan::vulkanFillImage(HLE::Libs::Graphics::GraphicCtx* ctx,
vulkanCreateBuffer(ctx, size, &staging_buffer);
void* data = nullptr;
vkMapMemory(ctx->m_device, staging_buffer.memory.memory, staging_buffer.memory.offset,
staging_buffer.memory.requirements.size, 0, &data);
std::memcpy(data, src_data, size);
vkUnmapMemory(ctx->m_device, staging_buffer.memory.memory);
// vkMapMemory(ctx->m_device, staging_buffer.memory.memory, staging_buffer.memory.offset,
// staging_buffer.memory.requirements.size, 0, &data);
// std::memcpy(data, src_data, size);
// vkUnmapMemory(ctx->m_device, staging_buffer.memory.memory);
GPU::CommandBuffer buffer(9);
@ -587,8 +589,8 @@ void Graphics::Vulkan::vulkanBufferToImage(GPU::CommandBuffer* buffer,
region.imageOffset = {0, 0, 0};
region.imageExtent = {dst_image->extent.width, dst_image->extent.height, 1};
vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
// vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image,
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
set_image_layout(vk_buffer, dst_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<VkImageLayout>(dst_layout));
@ -602,22 +604,23 @@ void Graphics::Vulkan::vulkanCreateBuffer(HLE::Libs::Graphics::GraphicCtx* ctx,
buffer_info.usage = buffer->usage;
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
vkCreateBuffer(ctx->m_device, &buffer_info, nullptr, &buffer->buffer);
// vkCreateBuffer(ctx->m_device, &buffer_info, nullptr, &buffer->buffer);
vkGetBufferMemoryRequirements(ctx->m_device, buffer->buffer, &buffer->memory.requirements);
// vkGetBufferMemoryRequirements(ctx->m_device, buffer->buffer, &buffer->memory.requirements);
bool allocated = GPU::vulkanAllocateMemory(ctx, &buffer->memory);
if (!allocated) {
fmt::print("Can't allocate vulkan\n");
std::exit(0);
}
vkBindBufferMemory(ctx->m_device, buffer->buffer, buffer->memory.memory, buffer->memory.offset);
// vkBindBufferMemory(ctx->m_device, buffer->buffer, buffer->memory.memory,
// buffer->memory.offset);
}
void Graphics::Vulkan::vulkanDeleteBuffer(HLE::Libs::Graphics::GraphicCtx* ctx,
HLE::Libs::Graphics::VulkanBuffer* buffer) {
vkDestroyBuffer(ctx->m_device, buffer->buffer, nullptr);
vkFreeMemory(ctx->m_device, buffer->memory.memory, nullptr);
// vkDestroyBuffer(ctx->m_device, buffer->buffer, nullptr);
// vkFreeMemory(ctx->m_device, buffer->memory.memory, nullptr);
buffer->memory.memory = nullptr;
buffer->buffer = nullptr;
}