Merge pull request #111 from raphaelthegreat/main
core: Rewrite videoout library and bringup new vulkan backend
This commit is contained in:
commit
d496fab492
|
@ -46,3 +46,18 @@
|
||||||
[submodule "externals/fmt"]
|
[submodule "externals/fmt"]
|
||||||
path = externals/fmt
|
path = externals/fmt
|
||||||
url = https://github.com/shadps4-emu/ext-fmt.git
|
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
|
||||||
|
|
|
@ -65,15 +65,9 @@ if (CLANG_FORMAT)
|
||||||
set(SRCS ${PROJECT_SOURCE_DIR}/src)
|
set(SRCS ${PROJECT_SOURCE_DIR}/src)
|
||||||
set(CCOMMENT "Running clang format against all the .h and .cpp files in src/")
|
set(CCOMMENT "Running clang format against all the .h and .cpp files in src/")
|
||||||
if (WIN32)
|
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
|
add_custom_target(clang-format
|
||||||
COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h,*.mm -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}"
|
COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h,*.mm -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}"
|
||||||
COMMENT ${CCOMMENT})
|
COMMENT ${CCOMMENT})
|
||||||
endif()
|
|
||||||
else()
|
else()
|
||||||
add_custom_target(clang-format
|
add_custom_target(clang-format
|
||||||
COMMAND find ${SRCS} -iname *.h -o -iname *.cpp -o -iname *.mm | xargs ${CLANG_FORMAT} -i
|
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
|
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
|
set(LIBC_SOURCES src/core/libraries/libc/libc.cpp
|
||||||
src/core/libraries/libc/libc.h
|
src/core/libraries/libc/libc.h
|
||||||
src/core/libraries/libc/printf.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.cpp
|
||||||
src/common/logging/text_formatter.h
|
src/common/logging/text_formatter.h
|
||||||
src/common/logging/types.h
|
src/common/logging/types.h
|
||||||
|
src/common/alignment.h
|
||||||
src/common/assert.cpp
|
src/common/assert.cpp
|
||||||
src/common/assert.h
|
src/common/assert.h
|
||||||
src/common/bounded_threadsafe_queue.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.cpp
|
||||||
src/common/discord.h
|
src/common/discord.h
|
||||||
src/common/endian.h
|
src/common/endian.h
|
||||||
|
src/common/enum.h
|
||||||
src/common/io_file.cpp
|
src/common/io_file.cpp
|
||||||
src/common/io_file.h
|
src/common/io_file.h
|
||||||
src/common/error.cpp
|
src/common/error.cpp
|
||||||
|
@ -205,6 +208,7 @@ set(COMMON src/common/logging/backend.cpp
|
||||||
src/common/native_clock.h
|
src/common/native_clock.h
|
||||||
src/common/path_util.cpp
|
src/common/path_util.cpp
|
||||||
src/common/path_util.h
|
src/common/path_util.h
|
||||||
|
src/common/polyfill_thread.h
|
||||||
src/common/rdtsc.cpp
|
src/common/rdtsc.cpp
|
||||||
src/common/rdtsc.h
|
src/common/rdtsc.h
|
||||||
src/common/singleton.h
|
src/common/singleton.h
|
||||||
|
@ -249,6 +253,7 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||||
${SYSTEM_LIBS}
|
${SYSTEM_LIBS}
|
||||||
${LIBC_SOURCES}
|
${LIBC_SOURCES}
|
||||||
${PAD_LIB}
|
${PAD_LIB}
|
||||||
|
${VIDEOOUT_LIB}
|
||||||
src/core/linker.cpp
|
src/core/linker.cpp
|
||||||
src/core/linker.h
|
src/core/linker.h
|
||||||
src/core/tls.cpp
|
src/core/tls.cpp
|
||||||
|
@ -257,21 +262,39 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||||
src/core/virtual_memory.h
|
src/core/virtual_memory.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(VIDEO_CORE src/core/PS4/HLE/Graphics/video_out.cpp
|
set(VIDEO_CORE src/video_core/pixel_format.h
|
||||||
src/core/PS4/HLE/Graphics/video_out.h
|
src/video_core/renderer_vulkan/renderer_vulkan.cpp
|
||||||
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp
|
src/video_core/renderer_vulkan/renderer_vulkan.h
|
||||||
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h
|
src/video_core/renderer_vulkan/vk_common.cpp
|
||||||
src/core/PS4/HLE/Graphics/graphics_ctx.h
|
src/video_core/renderer_vulkan/vk_common.h
|
||||||
src/core/PS4/GPU/gpu_memory.cpp
|
src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp
|
||||||
src/core/PS4/GPU/gpu_memory.h
|
src/video_core/renderer_vulkan/vk_descriptor_update_queue.h
|
||||||
src/core/PS4/GPU/video_out_buffer.cpp
|
src/video_core/renderer_vulkan/vk_instance.cpp
|
||||||
src/core/PS4/GPU/video_out_buffer.h
|
src/video_core/renderer_vulkan/vk_instance.h
|
||||||
src/core/PS4/HLE/Graphics/graphics_render.cpp
|
src/video_core/renderer_vulkan/vk_master_semaphore.cpp
|
||||||
src/core/PS4/HLE/Graphics/graphics_render.h
|
src/video_core/renderer_vulkan/vk_master_semaphore.h
|
||||||
src/core/PS4/GPU/tile_manager.cpp
|
src/video_core/renderer_vulkan/vk_platform.cpp
|
||||||
src/core/PS4/GPU/tile_manager.h
|
src/video_core/renderer_vulkan/vk_platform.h
|
||||||
src/vulkan_util.cpp
|
src/video_core/renderer_vulkan/vk_resource_pool.cpp
|
||||||
src/vulkan_util.h
|
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
|
set(INPUT src/input/controller.cpp
|
||||||
|
@ -282,6 +305,7 @@ set(INPUT src/input/controller.cpp
|
||||||
|
|
||||||
if(ENABLE_QT_GUI)
|
if(ENABLE_QT_GUI)
|
||||||
qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
|
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_ui.h
|
||||||
src/qt_gui/main_window.cpp
|
src/qt_gui/main_window.cpp
|
||||||
|
@ -318,8 +342,8 @@ qt_add_executable(shadps4
|
||||||
${COMMON}
|
${COMMON}
|
||||||
${CORE}
|
${CORE}
|
||||||
${VIDEO_CORE}
|
${VIDEO_CORE}
|
||||||
src/emulator.cpp
|
src/sdl_window.h
|
||||||
src/emulator.h
|
src/sdl_window.cpp
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
add_executable(shadps4
|
add_executable(shadps4
|
||||||
|
@ -329,15 +353,15 @@ add_executable(shadps4
|
||||||
${CORE}
|
${CORE}
|
||||||
${VIDEO_CORE}
|
${VIDEO_CORE}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/emulator.cpp
|
src/sdl_window.h
|
||||||
src/emulator.h
|
src/sdl_window.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(shadps4)
|
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 magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map)
|
||||||
target_link_libraries(shadps4 PRIVATE discord-rpc vulkan-1 xxhash Zydis)
|
target_link_libraries(shadps4 PRIVATE discord-rpc boost vma vulkan-headers xxhash Zydis SPIRV glslang SDL3-shared)
|
||||||
|
|
||||||
if(NOT ENABLE_QT_GUI)
|
if(NOT ENABLE_QT_GUI)
|
||||||
target_link_libraries(shadps4 PRIVATE SDL3-shared)
|
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(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
||||||
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
||||||
add_definitions(-D_TIMESPEC_DEFINED) #needed for conflicts with time.h of windows.h
|
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()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|
|
@ -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.
|
|
@ -6,6 +6,13 @@ if (MSVC)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
||||||
endif()
|
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
|
# fmtlib
|
||||||
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
@ -35,3 +42,24 @@ add_subdirectory(zlib-ng)
|
||||||
|
|
||||||
# SDL3
|
# SDL3
|
||||||
add_subdirectory(sdl3 EXCLUDE_FROM_ALL)
|
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)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit dfb313f8357b8f6601fa7420be1a39a51ba86f77
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 2db79056b418587e61122a8a820cd832b2abdf83
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 048eb1442a76ab81ecb3c4ab0495f15a68e54a6d
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5677097bafb8477097c6e3354ce68b7a44fd01a4
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit cfebfc96b2b0bce93da7d12f2c14cc01793ae25c
|
|
@ -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
|
|
@ -8,7 +8,7 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <new>
|
#include "common/polyfill_thread.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ private:
|
||||||
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
|
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
|
||||||
// Wait until the queue is not empty.
|
// Wait until the queue is not empty.
|
||||||
std::unique_lock lock{consumer_cv_mutex};
|
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);
|
return read_index != m_write_index.load(std::memory_order::acquire);
|
||||||
});
|
});
|
||||||
if (stop_token.stop_requested()) {
|
if (stop_token.stop_requested()) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace Config {
|
||||||
bool isNeo = false;
|
bool isNeo = false;
|
||||||
u32 screenWidth = 1280;
|
u32 screenWidth = 1280;
|
||||||
u32 screenHeight = 720;
|
u32 screenHeight = 720;
|
||||||
|
u32 gpuId = 0; // Vulkan physical device no
|
||||||
std::string logFilter;
|
std::string logFilter;
|
||||||
std::string logType = "sync";
|
std::string logType = "sync";
|
||||||
bool isDebugDump = false;
|
bool isDebugDump = false;
|
||||||
|
@ -32,6 +33,10 @@ u32 getScreenHeight() {
|
||||||
return screenHeight;
|
return screenHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 getGpuId() {
|
||||||
|
return gpuId;
|
||||||
|
}
|
||||||
|
|
||||||
std::string getLogFilter() {
|
std::string getLogFilter() {
|
||||||
return logFilter;
|
return logFilter;
|
||||||
}
|
}
|
||||||
|
@ -72,12 +77,13 @@ void load(const std::filesystem::path& path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.contains("GPU")) {
|
if (data.contains("GPU")) {
|
||||||
auto generalResult = toml::expect<toml::value>(data.at("GPU"));
|
auto gpuResult = toml::expect<toml::value>(data.at("GPU"));
|
||||||
if (generalResult.is_ok()) {
|
if (gpuResult.is_ok()) {
|
||||||
auto general = generalResult.unwrap();
|
auto gpu = gpuResult.unwrap();
|
||||||
|
|
||||||
screenWidth = toml::find_or<toml::integer>(general, "screenWidth", false);
|
screenWidth = toml::find_or<toml::integer>(gpu, "screenWidth", screenWidth);
|
||||||
screenHeight = toml::find_or<toml::integer>(general, "screenHeight", false);
|
screenHeight = toml::find_or<toml::integer>(gpu, "screenHeight", screenHeight);
|
||||||
|
gpuId = toml::find_or<toml::integer>(gpu, "gpuId", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.contains("Debug")) {
|
if (data.contains("Debug")) {
|
||||||
|
@ -119,6 +125,7 @@ void save(const std::filesystem::path& path) {
|
||||||
data["General"]["isPS4Pro"] = isNeo;
|
data["General"]["isPS4Pro"] = isNeo;
|
||||||
data["General"]["logFilter"] = logFilter;
|
data["General"]["logFilter"] = logFilter;
|
||||||
data["General"]["logType"] = logType;
|
data["General"]["logType"] = logType;
|
||||||
|
data["GPU"]["gpuId"] = gpuId;
|
||||||
data["GPU"]["screenWidth"] = screenWidth;
|
data["GPU"]["screenWidth"] = screenWidth;
|
||||||
data["GPU"]["screenHeight"] = screenHeight;
|
data["GPU"]["screenHeight"] = screenHeight;
|
||||||
data["Debug"]["DebugDump"] = isDebugDump;
|
data["Debug"]["DebugDump"] = isDebugDump;
|
||||||
|
|
|
@ -16,6 +16,7 @@ std::string getLogType();
|
||||||
|
|
||||||
u32 getScreenWidth();
|
u32 getScreenWidth();
|
||||||
u32 getScreenHeight();
|
u32 getScreenHeight();
|
||||||
|
u32 getGpuId();
|
||||||
|
|
||||||
bool debugDump();
|
bool debugDump();
|
||||||
bool isLleLibc();
|
bool isLleLibc();
|
||||||
|
|
|
@ -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; \
|
||||||
|
}
|
|
@ -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
|
|
@ -22,6 +22,9 @@ using f64 = double;
|
||||||
using u128 = std::array<std::uint64_t, 2>;
|
using u128 = std::array<std::uint64_t, 2>;
|
||||||
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
|
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))
|
#define PS4_SYSV_ABI __attribute__((sysv_abi))
|
||||||
|
|
||||||
// UDLs for memory size values
|
// UDLs for memory size values
|
||||||
|
|
|
@ -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++;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -2,11 +2,9 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/PS4/GPU/gpu_memory.h"
|
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/gnmdriver/gnmdriver.h"
|
#include "core/libraries/gnmdriver/gnmdriver.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "emulator.h"
|
|
||||||
|
|
||||||
namespace Libraries::GnmDriver {
|
namespace Libraries::GnmDriver {
|
||||||
|
|
||||||
|
@ -293,7 +291,6 @@ int PS4_SYSV_ABI sceGnmFindResourcesPublic() {
|
||||||
|
|
||||||
void PS4_SYSV_ABI sceGnmFlushGarlic() {
|
void PS4_SYSV_ABI sceGnmFlushGarlic() {
|
||||||
LOG_WARNING(Lib_GnmDriver, "(STUBBED) called");
|
LOG_WARNING(Lib_GnmDriver, "(STUBBED) called");
|
||||||
GPU::flushGarlic(Emu::getGraphicCtx());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceGnmGetCoredumpAddress() {
|
int PS4_SYSV_ABI sceGnmGetCoredumpAddress() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "core/libraries/kernel/event_queue.h"
|
#include "core/libraries/kernel/event_queue.h"
|
||||||
|
|
||||||
|
@ -11,16 +12,11 @@ EqueueInternal::~EqueueInternal() = default;
|
||||||
int EqueueInternal::addEvent(const EqueueEvent& event) {
|
int EqueueInternal::addEvent(const EqueueEvent& event) {
|
||||||
std::scoped_lock lock{m_mutex};
|
std::scoped_lock lock{m_mutex};
|
||||||
|
|
||||||
if (m_events.size() > 0) {
|
ASSERT(m_events.empty());
|
||||||
BREAKPOINT();
|
ASSERT(!event.isTriggered);
|
||||||
}
|
|
||||||
// TODO check if event is already exists and return it. Currently we just add in m_events array
|
// TODO check if event is already exists and return it. Currently we just add in m_events array
|
||||||
m_events.push_back(event);
|
m_events.push_back(event);
|
||||||
|
|
||||||
if (event.isTriggered) {
|
|
||||||
BREAKPOINT(); // we don't support that either yet
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,29 +28,27 @@ int EqueueInternal::waitForEvents(SceKernelEvent* ev, int num, u32 micros) {
|
||||||
ret = getTriggeredEvents(ev, num);
|
ret = getTriggeredEvents(ev, num);
|
||||||
return ret > 0;
|
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) {
|
if (micros == 0) {
|
||||||
m_cond.wait(lock, predicate);
|
m_cond.wait(lock, predicate);
|
||||||
} else {
|
} else {
|
||||||
m_cond.wait_for(lock, std::chrono::microseconds(micros), predicate);
|
m_cond.wait_for(lock, std::chrono::microseconds(micros), predicate);
|
||||||
}
|
}
|
||||||
|
fmt::print("Wait done\n");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) {
|
bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) {
|
||||||
std::scoped_lock lock{m_mutex};
|
std::scoped_lock lock{m_mutex};
|
||||||
|
|
||||||
if (m_events.size() > 1) {
|
ASSERT(m_events.size() <= 1);
|
||||||
BREAKPOINT(); // we currently support one event
|
|
||||||
}
|
|
||||||
auto& event = m_events[0];
|
auto& event = m_events[0];
|
||||||
|
event.trigger(trigger_data);
|
||||||
if (event.filter.trigger_event_func != nullptr) {
|
|
||||||
event.filter.trigger_event_func(&event, trigger_data);
|
|
||||||
} else {
|
|
||||||
event.isTriggered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_cond.notify_one();
|
m_cond.notify_one();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -63,17 +57,12 @@ bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) {
|
||||||
int EqueueInternal::getTriggeredEvents(SceKernelEvent* ev, int num) {
|
int EqueueInternal::getTriggeredEvents(SceKernelEvent* ev, int num) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if (m_events.size() > 1) {
|
ASSERT(m_events.size() <= 1);
|
||||||
BREAKPOINT(); // we currently support one event
|
|
||||||
}
|
|
||||||
auto& event = m_events[0];
|
auto& event = m_events[0];
|
||||||
|
|
||||||
if (event.isTriggered) {
|
if (event.isTriggered) {
|
||||||
ev[ret++] = event.event;
|
ev[ret++] = event.event;
|
||||||
|
event.reset();
|
||||||
if (event.filter.reset_event_func != nullptr) {
|
|
||||||
event.filter.reset_event_func(&event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -52,15 +52,24 @@ struct SceKernelEvent {
|
||||||
|
|
||||||
struct Filter {
|
struct Filter {
|
||||||
void* data = nullptr;
|
void* data = nullptr;
|
||||||
TriggerFunc trigger_event_func = nullptr;
|
|
||||||
ResetFunc reset_event_func = nullptr;
|
|
||||||
DeleteFunc delete_event_func = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EqueueEvent {
|
struct EqueueEvent {
|
||||||
bool isTriggered = false;
|
bool isTriggered = false;
|
||||||
SceKernelEvent event;
|
SceKernelEvent event;
|
||||||
Filter filter;
|
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 {
|
class EqueueInternal {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
#include "core/PS4/GPU/gpu_memory.h"
|
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/kernel/memory_management.h"
|
#include "core/libraries/kernel/memory_management.h"
|
||||||
#include "core/libraries/kernel/physical_memory.h"
|
#include "core/libraries/kernel/physical_memory.h"
|
||||||
|
@ -13,10 +13,6 @@
|
||||||
|
|
||||||
namespace Libraries::Kernel {
|
namespace Libraries::Kernel {
|
||||||
|
|
||||||
bool is16KBAligned(u64 n) {
|
|
||||||
return ((n % (16ull * 1024) == 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
|
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
|
||||||
LOG_WARNING(Kernel_Vmm, "called");
|
LOG_WARNING(Kernel_Vmm, "called");
|
||||||
return SCE_KERNEL_MAIN_DMEM_SIZE;
|
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;
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
}
|
}
|
||||||
const bool is_in_range = (searchStart < len && searchEnd > len);
|
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!");
|
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
|
||||||
return SCE_KERNEL_ERROR_EINVAL;
|
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!");
|
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
|
||||||
return SCE_KERNEL_ERROR_EINVAL;
|
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 = {:#x}, prot = {:#x}, flags = {:#x}, directMemoryStart = {:#x}, alignment = {:#x}",
|
||||||
len, prot, flags, directMemoryStart, alignment);
|
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!");
|
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!");
|
||||||
return SCE_KERNEL_ERROR_EINVAL;
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
}
|
}
|
||||||
if (!is16KBAligned(directMemoryStart)) {
|
if (!Common::is16KBAligned(directMemoryStart)) {
|
||||||
LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!");
|
LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!");
|
||||||
return SCE_KERNEL_ERROR_EINVAL;
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
}
|
}
|
||||||
if (alignment != 0) {
|
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!");
|
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
|
||||||
return SCE_KERNEL_ERROR_EINVAL;
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualMemory::MemoryMode cpu_mode = VirtualMemory::MemoryMode::NoAccess;
|
VirtualMemory::MemoryMode cpu_mode = VirtualMemory::MemoryMode::NoAccess;
|
||||||
GPU::MemoryMode gpu_mode = GPU::MemoryMode::NoAccess;
|
|
||||||
|
|
||||||
switch (prot) {
|
switch (prot) {
|
||||||
case 0x03:
|
case 0x03:
|
||||||
|
@ -91,7 +86,6 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
|
||||||
case 0x32:
|
case 0x32:
|
||||||
case 0x33: // SCE_KERNEL_PROT_CPU_READ|SCE_KERNEL_PROT_CPU_WRITE|SCE_KERNEL_PROT_GPU_READ|SCE_KERNEL_PROT_GPU_ALL
|
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;
|
cpu_mode = VirtualMemory::MemoryMode::ReadWrite;
|
||||||
gpu_mode = GPU::MemoryMode::ReadWrite;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
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();
|
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();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gpu_mode != GPU::MemoryMode::NoAccess) {
|
|
||||||
GPU::memorySetAllocArea(out_addr, len);
|
|
||||||
}
|
|
||||||
return SCE_OK;
|
return SCE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
#include "core/libraries/kernel/physical_memory.h"
|
#include "core/libraries/kernel/physical_memory.h"
|
||||||
|
|
||||||
namespace Libraries::Kernel {
|
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,
|
bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut,
|
||||||
int memoryType) {
|
int memoryType) {
|
||||||
std::scoped_lock lock{m_mutex};
|
std::scoped_lock lock{m_mutex};
|
||||||
u64 find_free_pos = 0;
|
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) {
|
for (const auto& block : m_allocatedBlocks) {
|
||||||
u64 n = block.start_addr + block.size;
|
u64 n = block.start_addr + block.size;
|
||||||
if (n > find_free_pos) {
|
if (n > find_free_pos) {
|
||||||
|
@ -22,16 +19,15 @@ bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignmen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// align free position
|
// Align free position
|
||||||
find_free_pos = AlignUp(find_free_pos, alignment);
|
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) {
|
if (find_free_pos >= searchStart && find_free_pos + len <= searchEnd) {
|
||||||
AllocatedBlock block{};
|
AllocatedBlock block{};
|
||||||
block.size = len;
|
block.size = len;
|
||||||
block.start_addr = find_free_pos;
|
block.start_addr = find_free_pos;
|
||||||
block.memoryType = memoryType;
|
block.memoryType = memoryType;
|
||||||
block.gpu_mode = GPU::MemoryMode::NoAccess;
|
|
||||||
block.map_size = 0;
|
block.map_size = 0;
|
||||||
block.map_virtual_addr = 0;
|
block.map_virtual_addr = 0;
|
||||||
block.prot = 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,
|
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};
|
std::scoped_lock lock{m_mutex};
|
||||||
for (auto& b : m_allocatedBlocks) {
|
for (auto& b : m_allocatedBlocks) {
|
||||||
if (phys_addr >= b.start_addr && phys_addr < b.start_addr + b.size) {
|
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.map_size = len;
|
||||||
b.prot = prot;
|
b.prot = prot;
|
||||||
b.cpu_mode = cpu_mode;
|
b.cpu_mode = cpu_mode;
|
||||||
b.gpu_mode = gpu_mode;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "core/PS4/GPU/gpu_memory.h"
|
|
||||||
#include "core/virtual_memory.h"
|
#include "core/virtual_memory.h"
|
||||||
|
|
||||||
namespace Libraries::Kernel {
|
namespace Libraries::Kernel {
|
||||||
|
@ -21,7 +20,6 @@ public:
|
||||||
u64 map_size;
|
u64 map_size;
|
||||||
int prot;
|
int prot;
|
||||||
VirtualMemory::MemoryMode cpu_mode;
|
VirtualMemory::MemoryMode cpu_mode;
|
||||||
GPU::MemoryMode gpu_mode;
|
|
||||||
};
|
};
|
||||||
PhysicalMemory() {}
|
PhysicalMemory() {}
|
||||||
virtual ~PhysicalMemory() {}
|
virtual ~PhysicalMemory() {}
|
||||||
|
@ -29,8 +27,8 @@ public:
|
||||||
public:
|
public:
|
||||||
bool Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut,
|
bool Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut,
|
||||||
int memoryType);
|
int memoryType);
|
||||||
bool Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, VirtualMemory::MemoryMode cpu_mode,
|
bool Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot,
|
||||||
GPU::MemoryMode gpu_mode);
|
VirtualMemory::MemoryMode cpu_mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<AllocatedBlock> m_allocatedBlocks;
|
std::vector<AllocatedBlock> m_allocatedBlocks;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#include "core/libraries/libc/libc_string.h"
|
#include "core/libraries/libc/libc_string.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
|
|
||||||
namespace Core::Libraries::LibC {
|
namespace Libraries::LibC {
|
||||||
|
|
||||||
constexpr bool log_file_libc = true; // disable it to disable logging
|
constexpr bool log_file_libc = true; // disable it to disable logging
|
||||||
static u32 g_need_sceLibc = 1;
|
static u32 g_need_sceLibc = 1;
|
||||||
|
@ -421,7 +421,7 @@ const PS4_SYSV_ABI u16* ps4__Getpctype() {
|
||||||
return &characterTypeTable[0];
|
return &characterTypeTable[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
void libcSymbolsRegister(Loader::SymbolsResolver* sym) {
|
void libcSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
||||||
// cxa functions
|
// cxa functions
|
||||||
LIB_FUNCTION("3GPpjQdAMTw", "libc", 1, "libc", 1, 1, ps4___cxa_guard_acquire);
|
LIB_FUNCTION("3GPpjQdAMTw", "libc", 1, "libc", 1, 1, ps4___cxa_guard_acquire);
|
||||||
LIB_FUNCTION("9rAeANT2tyE", "libc", 1, "libc", 1, 1, ps4___cxa_guard_release);
|
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);
|
LIB_FUNCTION("sUP1hBaouOw", "libc", 1, "libc", 1, 1, ps4__Getpctype);
|
||||||
}
|
}
|
||||||
|
|
||||||
}; // namespace Core::Libraries::LibC
|
}; // namespace Libraries::LibC
|
||||||
|
|
|
@ -7,8 +7,8 @@ namespace Core::Loader {
|
||||||
class SymbolsResolver;
|
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
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
// adapted from
|
// adapted from
|
||||||
// https://opensource.apple.com/source/libcppabi/libcppabi-14/src/cxa_guard.cxx.auto.html
|
// 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:
|
// This file implements the __cxa_guard_* functions as defined at:
|
||||||
// http://www.codesourcery.com/public/cxx-abi/abi.html
|
// 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);
|
setNotInUse(guard_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace Core::Libraries::LibC {
|
namespace Libraries::LibC {
|
||||||
|
|
||||||
int PS4_SYSV_ABI ps4___cxa_guard_acquire(u64* guard_object);
|
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_release(u64* guard_object);
|
||||||
void PS4_SYSV_ABI ps4___cxa_guard_abort(u64* guard_object);
|
void PS4_SYSV_ABI ps4___cxa_guard_abort(u64* guard_object);
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "core/libraries/libc/libc_math.h"
|
#include "core/libraries/libc/libc_math.h"
|
||||||
|
|
||||||
namespace Core::Libraries::LibC {
|
namespace Libraries::LibC {
|
||||||
|
|
||||||
float PS4_SYSV_ABI ps4_atan2f(float y, float x) {
|
float PS4_SYSV_ABI ps4_atan2f(float y, float x) {
|
||||||
return atan2f(y, x);
|
return atan2f(y, x);
|
||||||
|
@ -38,4 +38,4 @@ double PS4_SYSV_ABI ps4_exp2(double arg) {
|
||||||
return exp2(arg);
|
return exp2(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
#include "common/types.h"
|
#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_atan2f(float y, float x);
|
||||||
float PS4_SYSV_ABI ps4_acosf(float num);
|
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);
|
float PS4_SYSV_ABI ps4__Fsin(float arg);
|
||||||
double PS4_SYSV_ABI ps4_exp2(double arg);
|
double PS4_SYSV_ABI ps4_exp2(double arg);
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
#include "core/libraries/libc/libc_stdio.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) {
|
std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode) {
|
||||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
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);
|
return std::puts(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "core/libraries/libc/printf.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);
|
std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode);
|
||||||
int PS4_SYSV_ABI ps4_printf(VA_ARGS);
|
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);
|
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);
|
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
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "core/libraries/libc/libc_stdlib.h"
|
#include "core/libraries/libc/libc_stdlib.h"
|
||||||
|
|
||||||
namespace Core::Libraries::LibC {
|
namespace Libraries::LibC {
|
||||||
|
|
||||||
void PS4_SYSV_ABI ps4_exit(int code) {
|
void PS4_SYSV_ABI ps4_exit(int code) {
|
||||||
std::exit(code);
|
std::exit(code);
|
||||||
|
@ -42,4 +42,4 @@ int PS4_SYSV_ABI ps4_rand() {
|
||||||
return std::rand();
|
return std::rand();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace Core::Libraries::LibC {
|
namespace Libraries::LibC {
|
||||||
|
|
||||||
void PS4_SYSV_ABI ps4_exit(int code);
|
void PS4_SYSV_ABI ps4_exit(int code);
|
||||||
int PS4_SYSV_ABI ps4_atexit(void (*func)());
|
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* comp)(const void*, const void*));
|
||||||
int PS4_SYSV_ABI ps4_rand();
|
int PS4_SYSV_ABI ps4_rand();
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "core/libraries/libc/libc_string.h"
|
#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) {
|
int PS4_SYSV_ABI ps4_memcmp(const void* s1, const void* s2, size_t n) {
|
||||||
return std::memcmp(s1, s2, n);
|
return std::memcmp(s1, s2, n);
|
||||||
|
@ -58,4 +58,4 @@ char* PS4_SYSV_ABI ps4_strdup(const char* str1) {
|
||||||
return dup;
|
return dup;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include "common/types.h"
|
#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);
|
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);
|
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);
|
int PS4_SYSV_ABI ps4_strncmp(const char* s1, const char* s2, size_t n);
|
||||||
char* PS4_SYSV_ABI ps4_strdup(const char* str1);
|
char* PS4_SYSV_ABI ps4_strdup(const char* str1);
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
|
|
||||||
#include "va_ctx.h"
|
#include "va_ctx.h"
|
||||||
|
|
||||||
namespace Core::Libraries::LibC {
|
namespace Libraries::LibC {
|
||||||
// ntoa conversion buffer size, this must be big enough to hold
|
// ntoa conversion buffer size, this must be big enough to hold
|
||||||
// one converted numeric number including padded zeros (dynamically created on stack)
|
// one converted numeric number including padded zeros (dynamically created on stack)
|
||||||
// 32 byte is a good default
|
// 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);
|
std::strcpy(s, buffer);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
(ctx).va_list.fp_offset = offsetof(VaRegSave, fp); \
|
(ctx).va_list.fp_offset = offsetof(VaRegSave, fp); \
|
||||||
(ctx).va_list.overflow_arg_area = &overflow_arg_area;
|
(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
|
// 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);
|
return vaArgOverflowArgArea<T*, 1, 8>(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::Libraries::LibC
|
} // namespace Libraries::LibC
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
#include "core/PS4/HLE/Graphics/video_out.h"
|
|
||||||
#include "core/libraries/audio/audioin.h"
|
#include "core/libraries/audio/audioin.h"
|
||||||
#include "core/libraries/audio/audioout.h"
|
#include "core/libraries/audio/audioout.h"
|
||||||
#include "core/libraries/gnmdriver/gnmdriver.h"
|
#include "core/libraries/gnmdriver/gnmdriver.h"
|
||||||
|
@ -22,16 +21,17 @@
|
||||||
#include "core/libraries/system/sysmodule.h"
|
#include "core/libraries/system/sysmodule.h"
|
||||||
#include "core/libraries/system/systemservice.h"
|
#include "core/libraries/system/systemservice.h"
|
||||||
#include "core/libraries/system/userservice.h"
|
#include "core/libraries/system/userservice.h"
|
||||||
|
#include "core/libraries/videoout/video_out.h"
|
||||||
|
|
||||||
namespace Libraries {
|
namespace Libraries {
|
||||||
|
|
||||||
void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||||
Libraries::Kernel::LibKernel_Register(sym);
|
Libraries::Kernel::LibKernel_Register(sym);
|
||||||
HLE::Libs::Graphics::VideoOut::videoOutRegisterLib(sym);
|
Libraries::VideoOut::RegisterLib(sym);
|
||||||
Libraries::GnmDriver::RegisterlibSceGnmDriver(sym);
|
Libraries::GnmDriver::RegisterlibSceGnmDriver(sym);
|
||||||
Libraries::LibPad::padSymbolsRegister(sym);
|
Libraries::LibPad::padSymbolsRegister(sym);
|
||||||
if (!Config::isLleLibc()) {
|
if (!Config::isLleLibc()) {
|
||||||
Core::Libraries::LibC::libcSymbolsRegister(sym);
|
Libraries::LibC::libcSymbolsRegister(sym);
|
||||||
}
|
}
|
||||||
|
|
||||||
// New libraries folder from autogen
|
// New libraries folder from autogen
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -3,14 +3,14 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "core/libraries/kernel/event_queues.h"
|
#include "core/libraries/kernel/event_queues.h"
|
||||||
|
#include "core/libraries/videoout/buffer.h"
|
||||||
|
|
||||||
namespace Core::Loader {
|
namespace Core::Loader {
|
||||||
class SymbolsResolver;
|
class SymbolsResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace HLE::Libs::Graphics::VideoOut {
|
namespace Libraries::VideoOut {
|
||||||
|
|
||||||
using SceUserServiceUserId = s32; // TODO move it to proper place
|
using SceUserServiceUserId = s32; // TODO move it to proper place
|
||||||
|
|
||||||
|
@ -46,35 +46,20 @@ enum SceVideoOutEventId : s16 {
|
||||||
SCE_VIDEO_OUT_EVENT_PRE_VBLANK_START = 2
|
SCE_VIDEO_OUT_EVENT_PRE_VBLANK_START = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SceVideoOutTilingMode : s32 {
|
enum AspectRatioMode : s32 {
|
||||||
SCE_VIDEO_OUT_TILING_MODE_TILE = 0,
|
SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0,
|
||||||
SCE_VIDEO_OUT_TILING_MODE_LINEAR = 1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AspectRatioMode : s32 { SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0 };
|
struct FlipStatus {
|
||||||
|
|
||||||
struct SceVideoOutBufferAttribute {
|
|
||||||
s32 pixelFormat;
|
|
||||||
s32 tilingMode;
|
|
||||||
s32 aspectRatio;
|
|
||||||
u32 width;
|
|
||||||
u32 height;
|
|
||||||
u32 pitchInPixel;
|
|
||||||
u32 option;
|
|
||||||
u32 reserved0;
|
|
||||||
u64 reserved1;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SceVideoOutFlipStatus {
|
|
||||||
u64 count = 0;
|
u64 count = 0;
|
||||||
u64 processTime = 0;
|
u64 processTime = 0;
|
||||||
u64 tsc = 0;
|
u64 tsc = 0;
|
||||||
s64 flipArg = 0;
|
s64 flipArg = -1;
|
||||||
u64 submitTsc = 0;
|
u64 submitTsc = 0;
|
||||||
u64 reserved0 = 0;
|
u64 reserved0 = 0;
|
||||||
s32 gcQueueNum = 0;
|
s32 gcQueueNum = 0;
|
||||||
s32 flipPendingNum = 0;
|
s32 flipPendingNum = 0;
|
||||||
s32 currentBuffer = 0;
|
s32 currentBuffer = -1;
|
||||||
u32 reserved1 = 0;
|
u32 reserved1 = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,34 +84,24 @@ struct SceVideoOutVblankStatus {
|
||||||
u8 pad1[7] = {};
|
u8 pad1[7] = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VideoOutBufferSetInternal {
|
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat,
|
||||||
SceVideoOutBufferAttribute attr;
|
u32 tilingMode, u32 aspectRatio, u32 width,
|
||||||
int start_index = 0;
|
u32 height, u32 pitchInPixel);
|
||||||
int num = 0;
|
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata);
|
||||||
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);
|
|
||||||
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
|
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
|
||||||
s32 bufferNum,
|
s32 bufferNum, const BufferAttribute* attribute);
|
||||||
const SceVideoOutBufferAttribute* attribute);
|
|
||||||
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate);
|
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate);
|
||||||
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle);
|
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle);
|
||||||
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg);
|
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 sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status);
|
||||||
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
|
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
|
||||||
const void* param);
|
const void* param);
|
||||||
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
|
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
|
353
src/emulator.cpp
353
src/emulator.cpp
|
@ -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
|
|
|
@ -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
|
|
|
@ -74,7 +74,7 @@ void GameController::addState(const State& state) {
|
||||||
m_states_num++;
|
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};
|
std::scoped_lock lock{m_mutex};
|
||||||
auto state = getLastState();
|
auto state = getLastState();
|
||||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||||
|
|
|
@ -23,7 +23,7 @@ public:
|
||||||
void readState(State* state, bool* isConnected, int* connectedCount);
|
void readState(State* state, bool* isConnected, int* connectedCount);
|
||||||
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
|
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
|
||||||
State getLastState() const;
|
State getLastState() const;
|
||||||
void checKButton(int id, u32 button, bool isPressed);
|
void checkButton(int id, u32 button, bool isPressed);
|
||||||
void addState(const State& state);
|
void addState(const State& state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
37
src/main.cpp
37
src/main.cpp
|
@ -12,14 +12,17 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
#include "core/PS4/HLE/Graphics/video_out.h"
|
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
#include "core/libraries/kernel/thread_management.h"
|
#include "core/libraries/kernel/thread_management.h"
|
||||||
#include "core/libraries/libc/libc.h"
|
#include "core/libraries/libc/libc.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
|
#include "core/libraries/videoout/video_out.h"
|
||||||
#include "core/linker.h"
|
#include "core/linker.h"
|
||||||
#include "core/tls.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[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
|
@ -31,10 +34,12 @@ int main(int argc, char* argv[]) {
|
||||||
Common::Log::Initialize();
|
Common::Log::Initialize();
|
||||||
Common::Log::Start();
|
Common::Log::Start();
|
||||||
Libraries::Kernel::init_pthreads();
|
Libraries::Kernel::init_pthreads();
|
||||||
auto width = Config::getScreenWidth();
|
s32 width = Config::getScreenWidth();
|
||||||
auto height = Config::getScreenHeight();
|
s32 height = Config::getScreenHeight();
|
||||||
Emu::emuInit(width, height);
|
|
||||||
HLE::Libs::Graphics::VideoOut::videoOutInit(width, height);
|
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
|
// Argument 1 is the path of self file to boot
|
||||||
const char* const path = argv[1];
|
const char* const path = argv[1];
|
||||||
|
@ -47,7 +52,8 @@ int main(int argc, char* argv[]) {
|
||||||
Libraries::InitHLELibs(&linker->getHLESymbols());
|
Libraries::InitHLELibs(&linker->getHLESymbols());
|
||||||
Core::InstallTlsHandler();
|
Core::InstallTlsHandler();
|
||||||
linker->LoadModule(path);
|
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;
|
bool found = false;
|
||||||
if (Config::isLleLibc()) {
|
if (Config::isLleLibc()) {
|
||||||
std::filesystem::path sce_module_folder = p.parent_path() / "sce_module";
|
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
|
if (!found) {
|
||||||
{
|
Libraries::LibC::libcSymbolsRegister(&linker->getHLESymbols());
|
||||||
Core::Libraries::LibC::libcSymbolsRegister(&linker->getHLESymbols());
|
|
||||||
}
|
}
|
||||||
std::jthread mainthread([linker](std::stop_token stop_token, void*) { linker->Execute(); },
|
std::thread mainthread([linker]() { linker->Execute(); });
|
||||||
nullptr);
|
|
||||||
Discord::RPC discordRPC;
|
Discord::RPC discordRPC;
|
||||||
discordRPC.init();
|
discordRPC.init();
|
||||||
discordRPC.update(Discord::RPCStatus::Idling, "");
|
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();
|
discordRPC.stop();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
#include "qt_gui/game_install_dialog.h"
|
#include "qt_gui/game_install_dialog.h"
|
||||||
#include "qt_gui/gui_settings.h"
|
#include "qt_gui/gui_settings.h"
|
||||||
#include "qt_gui/main_window.h"
|
#include "qt_gui/main_window.h"
|
||||||
|
#include "src/sdl_window.h"
|
||||||
|
|
||||||
|
Frontend::WindowSDL* g_window;
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -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
|
|
@ -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
|
|
@ -1,23 +1,15 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <mutex>
|
#include <cstring>
|
||||||
#include "common/singleton.h"
|
#include "video_core/texture_cache/tile_manager.h"
|
||||||
#include "core/PS4/GPU/tile_manager.h"
|
|
||||||
|
|
||||||
namespace GPU {
|
namespace VideoCore {
|
||||||
|
|
||||||
static u32 IntLog2(u32 i) {
|
static u32 IntLog2(u32 i) {
|
||||||
return 31 - __builtin_clz(i | 1u);
|
return 31 - __builtin_clz(i | 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TileManager {
|
|
||||||
public:
|
|
||||||
TileManager() {}
|
|
||||||
virtual ~TileManager() {}
|
|
||||||
std::mutex m_mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TileManager32 {
|
class TileManager32 {
|
||||||
public:
|
public:
|
||||||
u32 m_macro_tile_height = 0;
|
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;
|
TileManager32 t;
|
||||||
t.Init(width, height, is_neo);
|
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++) {
|
for (u32 y = 0; y < height; y++) {
|
||||||
u32 x = 0;
|
u32 x = 0;
|
||||||
u64 linear_offset = y * width * 4;
|
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) {
|
for (; x + 1 < width; x += 2) {
|
||||||
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
|
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
|
||||||
|
|
||||||
*reinterpret_cast<u64*>(static_cast<u8*>(dst) + linear_offset) =
|
std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u64));
|
||||||
*reinterpret_cast<const u64*>(static_cast<const u8*>(src) + tiled_offset);
|
|
||||||
linear_offset += 8;
|
linear_offset += 8;
|
||||||
}
|
}
|
||||||
if (x < width) {
|
if (x < width) {
|
||||||
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
|
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
|
||||||
|
std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*reinterpret_cast<u32*>(static_cast<u8*>(dst) + linear_offset) =
|
} // namespace VideoCore
|
||||||
*reinterpret_cast<const u32*>(static_cast<const u8*>(src) + tiled_offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace GPU
|
|
|
@ -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
|
|
@ -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
|
|
@ -15,7 +15,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) {
|
void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) {
|
||||||
Emu::VulkanExt ext;
|
/*Emu::VulkanExt ext;
|
||||||
vulkanGetInstanceExtensions(&ext);
|
vulkanGetInstanceExtensions(&ext);
|
||||||
|
|
||||||
VkApplicationInfo app_info{};
|
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");
|
ASSERT_MSG(ctx->m_graphic_ctx.m_device, "Can't create vulkan device");
|
||||||
|
|
||||||
vulkanCreateQueues(&ctx->m_graphic_ctx, queues);
|
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,
|
Emu::VulkanSwapchain Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics::GraphicCtx* ctx,
|
||||||
u32 image_count) {
|
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;
|
const auto& capabilities = window_ctx->m_surface_capabilities.capabilities;
|
||||||
Emu::VulkanSwapchain s{};
|
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);
|
result = vkCreateFence(ctx->m_device, &fence_info, nullptr, &s.present_complete_fence);
|
||||||
ASSERT_MSG(result == VK_SUCCESS, "Can't create vulkan fence");
|
ASSERT_MSG(result == VK_SUCCESS, "Can't create vulkan fence");
|
||||||
|
|
||||||
return s;
|
return s;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::Vulkan::vulkanCreateQueues(HLE::Libs::Graphics::GraphicCtx* ctx,
|
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,
|
VkSurfaceKHR surface, const Emu::VulkanExt* r,
|
||||||
const Emu::VulkanQueues& queues,
|
const Emu::VulkanQueues& queues,
|
||||||
const std::vector<const char*>& device_extensions) {
|
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);
|
std::vector<std::vector<float>> queue_priority(queues.family_count);
|
||||||
uint32_t queue_create_info_num = 0;
|
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);
|
vkCreateDevice(physical_device, &create_info, nullptr, &device);
|
||||||
|
|
||||||
return device;
|
return device;*/
|
||||||
}
|
}
|
||||||
void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) {
|
void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) {
|
||||||
u32 required_extensions_count = 0;
|
/*u32 required_extensions_count = 0;
|
||||||
u32 available_extensions_count = 0;
|
u32 available_extensions_count = 0;
|
||||||
u32 available_layers_count = 0;
|
u32 available_layers_count = 0;
|
||||||
ext->required_extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count);
|
ext->required_extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count);
|
||||||
|
@ -270,14 +271,14 @@ void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) {
|
||||||
LOG_INFO(Render_Vulkan,
|
LOG_INFO(Render_Vulkan,
|
||||||
"Vulkan available layer: {}, specVersion = {}, implVersion = {}, {}", l.layerName,
|
"Vulkan available layer: {}, specVersion = {}, implVersion = {}, {}", l.layerName,
|
||||||
l.specVersion, l.implementationVersion, l.description);
|
l.specVersion, l.implementationVersion, l.description);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice(
|
void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice(
|
||||||
VkInstance instance, VkSurfaceKHR surface, const std::vector<const char*>& device_extensions,
|
VkInstance instance, VkSurfaceKHR surface, const std::vector<const char*>& device_extensions,
|
||||||
Emu::VulkanSurfaceCapabilities* out_capabilities, VkPhysicalDevice* out_device,
|
Emu::VulkanSurfaceCapabilities* out_capabilities, VkPhysicalDevice* out_device,
|
||||||
Emu::VulkanQueues* out_queues) {
|
Emu::VulkanQueues* out_queues) {
|
||||||
u32 count_devices = 0;
|
/*u32 count_devices = 0;
|
||||||
vkEnumeratePhysicalDevices(instance, &count_devices, nullptr);
|
vkEnumeratePhysicalDevices(instance, &count_devices, nullptr);
|
||||||
|
|
||||||
std::vector<VkPhysicalDevice> devices(count_devices);
|
std::vector<VkPhysicalDevice> devices(count_devices);
|
||||||
|
@ -306,14 +307,14 @@ void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice(
|
||||||
found_best_queues = qs;
|
found_best_queues = qs;
|
||||||
}
|
}
|
||||||
*out_device = found_best_device;
|
*out_device = found_best_device;
|
||||||
*out_queues = found_best_queues;
|
*out_queues = found_best_queues;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device,
|
Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device,
|
||||||
VkSurfaceKHR surface) {
|
VkSurfaceKHR surface) {
|
||||||
Emu::VulkanQueues qs;
|
Emu::VulkanQueues qs;
|
||||||
|
|
||||||
u32 queue_family_count = 0;
|
/*u32 queue_family_count = 0;
|
||||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr);
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr);
|
||||||
std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
|
std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
|
||||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data());
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data());
|
||||||
|
@ -391,14 +392,15 @@ Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device,
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
return qs;
|
return qs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_device,
|
void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_device,
|
||||||
VkSurfaceKHR surface,
|
VkSurfaceKHR surface,
|
||||||
Emu::VulkanSurfaceCapabilities* surfaceCap) {
|
Emu::VulkanSurfaceCapabilities* surfaceCap) {
|
||||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surfaceCap->capabilities);
|
/*vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface,
|
||||||
|
&surfaceCap->capabilities);
|
||||||
|
|
||||||
uint32_t formats_count = 0;
|
uint32_t formats_count = 0;
|
||||||
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, nullptr);
|
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, nullptr);
|
||||||
|
@ -426,7 +428,7 @@ void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_de
|
||||||
surfaceCap->is_format_unorm_bgra32 = true;
|
surfaceCap->is_format_unorm_bgra32 = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_image_layout(VkCommandBuffer buffer, HLE::Libs::Graphics::VulkanImage* dst_image,
|
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 src_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
VkPipelineStageFlags dest_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,
|
// vkCmdPipelineBarrier(buffer, src_stages, dest_stages, 0, 0, nullptr, 0, nullptr, 1,
|
||||||
&imageMemoryBarrier);
|
// &imageMemoryBarrier);
|
||||||
|
|
||||||
dst_image->layout = new_image_layout;
|
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].y = static_cast<int>(dst_swapchain->swapchain_extent.height);
|
||||||
region.dstOffsets[1].z = 1;
|
region.dstOffsets[1].z = 1;
|
||||||
|
|
||||||
vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
// vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion,
|
// swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion,
|
||||||
VK_FILTER_LINEAR);
|
// VK_FILTER_LINEAR);
|
||||||
|
|
||||||
set_image_layout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
|
set_image_layout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
|
@ -548,10 +550,10 @@ void Graphics::Vulkan::vulkanFillImage(HLE::Libs::Graphics::GraphicCtx* ctx,
|
||||||
vulkanCreateBuffer(ctx, size, &staging_buffer);
|
vulkanCreateBuffer(ctx, size, &staging_buffer);
|
||||||
|
|
||||||
void* data = nullptr;
|
void* data = nullptr;
|
||||||
vkMapMemory(ctx->m_device, staging_buffer.memory.memory, staging_buffer.memory.offset,
|
// vkMapMemory(ctx->m_device, staging_buffer.memory.memory, staging_buffer.memory.offset,
|
||||||
staging_buffer.memory.requirements.size, 0, &data);
|
// staging_buffer.memory.requirements.size, 0, &data);
|
||||||
std::memcpy(data, src_data, size);
|
// std::memcpy(data, src_data, size);
|
||||||
vkUnmapMemory(ctx->m_device, staging_buffer.memory.memory);
|
// vkUnmapMemory(ctx->m_device, staging_buffer.memory.memory);
|
||||||
|
|
||||||
GPU::CommandBuffer buffer(9);
|
GPU::CommandBuffer buffer(9);
|
||||||
|
|
||||||
|
@ -587,8 +589,8 @@ void Graphics::Vulkan::vulkanBufferToImage(GPU::CommandBuffer* buffer,
|
||||||
region.imageOffset = {0, 0, 0};
|
region.imageOffset = {0, 0, 0};
|
||||||
region.imageExtent = {dst_image->extent.width, dst_image->extent.height, 1};
|
region.imageExtent = {dst_image->extent.width, dst_image->extent.height, 1};
|
||||||
|
|
||||||
vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image,
|
// vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image,
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||||
|
|
||||||
set_image_layout(vk_buffer, dst_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
|
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));
|
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.usage = buffer->usage;
|
||||||
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
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);
|
bool allocated = GPU::vulkanAllocateMemory(ctx, &buffer->memory);
|
||||||
if (!allocated) {
|
if (!allocated) {
|
||||||
fmt::print("Can't allocate vulkan\n");
|
fmt::print("Can't allocate vulkan\n");
|
||||||
std::exit(0);
|
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,
|
void Graphics::Vulkan::vulkanDeleteBuffer(HLE::Libs::Graphics::GraphicCtx* ctx,
|
||||||
HLE::Libs::Graphics::VulkanBuffer* buffer) {
|
HLE::Libs::Graphics::VulkanBuffer* buffer) {
|
||||||
vkDestroyBuffer(ctx->m_device, buffer->buffer, nullptr);
|
// vkDestroyBuffer(ctx->m_device, buffer->buffer, nullptr);
|
||||||
vkFreeMemory(ctx->m_device, buffer->memory.memory, nullptr);
|
// vkFreeMemory(ctx->m_device, buffer->memory.memory, nullptr);
|
||||||
buffer->memory.memory = nullptr;
|
buffer->memory.memory = nullptr;
|
||||||
buffer->buffer = nullptr;
|
buffer->buffer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue