mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d79f19089 | ||
|
|
8fe4765f9e | ||
|
|
c71bb9fc9e | ||
|
|
f60f80b631 | ||
|
|
31ea0081a9 | ||
|
|
7921269878 | ||
|
|
e42f6c3892 | ||
|
|
7351eae117 | ||
|
|
125287dc30 | ||
|
|
142b3c20fa | ||
|
|
f43567e12c | ||
|
|
c86d4b1f0d | ||
|
|
31fb3ec5af | ||
|
|
c3b2ae9733 | ||
|
|
93774ee37c | ||
|
|
a20cf5d298 | ||
|
|
a720b28b7f | ||
|
|
7638c25d76 | ||
|
|
85876388cf | ||
|
|
f60007e923 |
@@ -0,0 +1,82 @@
|
||||
name: Build KytyPS5 (Linux)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install build dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends --yes \
|
||||
clang \
|
||||
glslang-tools \
|
||||
libgl1-mesa-dev \
|
||||
libx11-dev \
|
||||
libxcursor-dev \
|
||||
libxext-dev \
|
||||
libxfixes-dev \
|
||||
libxi-dev \
|
||||
libxrandr-dev \
|
||||
libxss-dev \
|
||||
lld \
|
||||
ninja-build
|
||||
|
||||
- name: Install Qt 6.10.3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: "6.10.3"
|
||||
host: linux
|
||||
target: desktop
|
||||
arch: linux_gcc_64
|
||||
cache: true
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
git --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
clang++ --version
|
||||
ld.lld --version
|
||||
glslangValidator --version
|
||||
echo "Qt6_DIR=$Qt6_DIR"
|
||||
|
||||
- name: Configure
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S src -B _Build/linux \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/linux --target launcher --parallel
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --install _Build/linux --prefix _Build/linux/install
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: KytyPS5-Linux
|
||||
path: _Build/linux/install/**
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Build KytyPS5 (macOS)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-15
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Select Xcode 26
|
||||
shell: bash
|
||||
run: |
|
||||
xcode_path="$(ls -d /Applications/Xcode_26*.app | tail -n 1)"
|
||||
test -n "$xcode_path"
|
||||
sudo xcode-select --switch "$xcode_path/Contents/Developer"
|
||||
xcodebuild -version
|
||||
|
||||
- name: Install build dependencies
|
||||
shell: bash
|
||||
run: brew install glslang ninja
|
||||
|
||||
- name: Install Qt 6.10.3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: "6.10.3"
|
||||
host: mac
|
||||
target: desktop
|
||||
arch: clang_64
|
||||
cache: true
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
git --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
clang++ --version
|
||||
glslangValidator --version
|
||||
echo "Host architecture: $(uname -m)"
|
||||
echo "QT_ROOT_DIR=$QT_ROOT_DIR"
|
||||
|
||||
- name: Configure
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S src -B _Build/macos \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos --target launcher --parallel
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --install _Build/macos --prefix _Build/macos/install
|
||||
|
||||
- name: Verify artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
file _Build/macos/install/launcher
|
||||
file _Build/macos/install/kyty_emulator
|
||||
lipo _Build/macos/install/launcher -verify_arch x86_64
|
||||
lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
|
||||
codesign --verify --strict _Build/macos/install/kyty_emulator
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: KytyPS5-macOS-x86_64
|
||||
path: _Build/macos/install/**
|
||||
if-no-files-found: error
|
||||
@@ -22,11 +22,31 @@ jobs:
|
||||
- name: Setup Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v5
|
||||
|
||||
- name: Install glslang
|
||||
- name: Locate vcpkg
|
||||
id: vcpkg
|
||||
shell: pwsh
|
||||
run: |
|
||||
$vcpkgRoot = Split-Path (Get-Command vcpkg).Source
|
||||
vcpkg install glslang[tools,opt]:x64-windows
|
||||
$portHash = (Get-FileHash "$vcpkgRoot\ports\glslang\vcpkg.json" -Algorithm SHA256).Hash
|
||||
"root=$vcpkgRoot" |
|
||||
Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"port_hash=$($portHash.ToLowerInvariant())" |
|
||||
Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
|
||||
- name: Cache glslang
|
||||
id: glslang_cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.vcpkg.outputs.root }}\installed\x64-windows
|
||||
key: glslang-${{ runner.os }}-${{ runner.arch }}-${{ steps.vcpkg.outputs.port_hash }}-tools-opt
|
||||
|
||||
- name: Install glslang
|
||||
shell: pwsh
|
||||
run: |
|
||||
$vcpkgRoot = "${{ steps.vcpkg.outputs.root }}"
|
||||
if ("${{ steps.glslang_cache.outputs.cache-hit }}" -ne "true") {
|
||||
vcpkg install glslang[tools,opt]:x64-windows
|
||||
}
|
||||
"$vcpkgRoot\installed\x64-windows\tools\glslang" |
|
||||
Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
|
||||
+2
-1
@@ -2,4 +2,5 @@
|
||||
.vs/
|
||||
.idea/
|
||||
build/
|
||||
_Build/vscode-clang/
|
||||
_Build/vscode-clang/
|
||||
_Build/
|
||||
Vendored
+11
-1
@@ -44,7 +44,17 @@ set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||
|
||||
if (NOT TARGET FFmpeg::ffmpeg)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
|
||||
# On macOS the target arch is driven by CMAKE_OSX_ARCHITECTURES, not the host
|
||||
# CMAKE_SYSTEM_PROCESSOR (which reports arm64 even for an x86_64/Rosetta build).
|
||||
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
|
||||
if(CMAKE_OSX_ARCHITECTURES MATCHES "^(x86_64)$")
|
||||
set(ARCHITECTURE x86_64)
|
||||
elseif(CMAKE_OSX_ARCHITECTURES MATCHES "^(arm64)$")
|
||||
set(ARCHITECTURE arm64)
|
||||
else()
|
||||
set(ARCHITECTURE x86_64 arm64) # universal
|
||||
endif()
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
|
||||
set(ARCHITECTURE arm64)
|
||||
else()
|
||||
set(ARCHITECTURE x86_64)
|
||||
|
||||
Vendored
+12
-3
@@ -5,7 +5,14 @@ set(cpuinfo_src
|
||||
deps/clog/src/clog.c
|
||||
)
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i[3-6]86|AMD64|x86(_64)?)$")
|
||||
# On Apple the target arch is driven by CMAKE_OSX_ARCHITECTURES, not the host
|
||||
# CMAKE_SYSTEM_PROCESSOR (which is arm64 even for an x86_64/Rosetta build).
|
||||
set(CPUINFO_TARGET_PROC "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
|
||||
list(GET CMAKE_OSX_ARCHITECTURES 0 CPUINFO_TARGET_PROC)
|
||||
endif()
|
||||
|
||||
if(CPUINFO_TARGET_PROC MATCHES "^(i[3-6]86|AMD64|x86(_64)?)$")
|
||||
list(APPEND cpuinfo_src
|
||||
src/x86/init.c
|
||||
src/x86/info.c
|
||||
@@ -19,11 +26,13 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i[3-6]86|AMD64|x86(_64)?)$")
|
||||
src/x86/cache/deterministic.c)
|
||||
if(LINUX OR ANDROID)
|
||||
list(APPEND cpuinfo_src src/x86/linux/init.c src/x86/linux/cpuinfo.c)
|
||||
elseif(APPLE)
|
||||
list(APPEND cpuinfo_src src/x86/mach/init.c src/mach/topology.c)
|
||||
else()
|
||||
list(APPEND cpuinfo_src src/x86/windows/init.c)
|
||||
endif()
|
||||
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv[5-8].*|aarch64|arm64)$")
|
||||
|
||||
elseif(CPUINFO_TARGET_PROC MATCHES "^(armv[5-8].*|aarch64|arm64)$")
|
||||
list(APPEND cpuinfo_src src/arm/uarch.c src/arm/cache.c)
|
||||
|
||||
if(LINUX OR ANDROID)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# KytyPS5
|
||||
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-linux.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-macos.yml)
|
||||
[](#system-requirements)
|
||||
[](#current-status)
|
||||
[](LICENSE)
|
||||
|
||||
+118
-5
@@ -10,15 +10,19 @@ if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if (NOT (WIN32 OR LINUX))
|
||||
message(FATAL_ERROR "only Windows and Linux builds are supported")
|
||||
if (NOT (WIN32 OR LINUX OR APPLE))
|
||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||
|
||||
include(utils.cmake)
|
||||
include(CTest)
|
||||
|
||||
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
|
||||
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
|
||||
|
||||
@@ -43,8 +47,9 @@ else()
|
||||
set(KYTY_BUILD KYTY_BUILD_RELEASE)
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||
if(LINUX OR APPLE)
|
||||
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||
else()
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
|
||||
endif()
|
||||
@@ -61,6 +66,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
||||
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
|
||||
set(KYTY_CLANG_CL TRUE)
|
||||
set(KYTY_LINKER LLD_LINK)
|
||||
elseif(APPLE)
|
||||
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
|
||||
else()
|
||||
set(KYTY_LINKER LLD)
|
||||
endif()
|
||||
@@ -113,6 +120,7 @@ add_custom_target( KytyGitVersion
|
||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
|
||||
COMMENT "Generate kytyGitVersion.h"
|
||||
)
|
||||
@@ -123,7 +131,7 @@ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
||||
common
|
||||
#launcher
|
||||
)
|
||||
list(APPEND KYTY_CLANG_TYDY
|
||||
list(APPEND KYTY_CLANG_TIDY
|
||||
kyty_emulator
|
||||
#common
|
||||
launcher
|
||||
@@ -183,6 +191,9 @@ set(gpu_tiler_shader_names
|
||||
prt_3d
|
||||
render_target
|
||||
depth
|
||||
promote_d16
|
||||
demote_d16
|
||||
swap_bgra16
|
||||
)
|
||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
@@ -204,6 +215,30 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
endforeach()
|
||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||
|
||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||
set(gpu_blit_shader_sources
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
|
||||
)
|
||||
foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
||||
get_filename_component(shader_name "${shader_source}" NAME_WE)
|
||||
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
||||
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
||||
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND gpu_blit_shader_headers "${shader_header}")
|
||||
endforeach()
|
||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||
|
||||
list(APPEND kyty_emulator_src
|
||||
emulator.h
|
||||
emulator.cpp
|
||||
@@ -318,17 +353,83 @@ add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(resource_mutex_tests common)
|
||||
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
../tests/EventQueueLifetimeTests.cpp
|
||||
kernel/eventQueue.cpp
|
||||
loader/timer.cpp
|
||||
)
|
||||
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
|
||||
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
|
||||
../tests/ImagePageTableTests.cpp
|
||||
)
|
||||
target_link_libraries(image_page_table_tests common fmt::fmt)
|
||||
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
|
||||
|
||||
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
|
||||
set(gpu_test_ms_depth_source
|
||||
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
|
||||
set(gpu_test_ms_depth_spv
|
||||
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
|
||||
set(gpu_test_ms_depth_header
|
||||
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${gpu_test_ms_depth_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
|
||||
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${gpu_test_ms_depth_source}"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources(shader_recompiler_compute_tests PRIVATE
|
||||
"${gpu_test_ms_depth_header}")
|
||||
|
||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
|
||||
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
|
||||
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME command_scheduler_timeline
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
|
||||
add_test(NAME stream_buffer_ring
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME gpu_tiler
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
|
||||
|
||||
if(WIN32)
|
||||
add_test(NAME texture_cache_image_overlap
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||
add_test(NAME texture_cache_htile_clear
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||
add_test(NAME texture_cache_layered_image
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
||||
add_test(NAME texture_cache_image_views
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
|
||||
add_test(NAME texture_cache_storage_sampled
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
|
||||
add_test(NAME texture_cache_depth_readback
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
add_test(NAME buffer_cache_dirty_gc
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
|
||||
|
||||
@@ -365,6 +466,18 @@ endif()
|
||||
|
||||
add_dependencies(kyty_emulator KytyGitVersion)
|
||||
|
||||
if(APPLE)
|
||||
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
|
||||
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
|
||||
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
|
||||
# reverts to the hardened defaults and aborts when it first executes written code).
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD
|
||||
COMMAND codesign -s - --force --options runtime
|
||||
--entitlements "${CMAKE_CURRENT_SOURCE_DIR}/macos_jit.entitlements"
|
||||
$<TARGET_FILE:kyty_emulator>
|
||||
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
|
||||
endif()
|
||||
|
||||
install(TARGETS kyty_emulator DESTINATION .)
|
||||
if(KYTY_BUILD_LAUNCHER)
|
||||
add_subdirectory(launcher)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace Common {
|
||||
|
||||
|
||||
@@ -93,4 +93,8 @@ bool NggRectlistDrawEnabled() {
|
||||
return g_config->ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
|
||||
@@ -36,6 +36,7 @@ struct ConfigOptions {
|
||||
bool spirv_debug_printf_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool ngg_rectlist_draw_enabled = true;
|
||||
bool readback_linear_images = false;
|
||||
};
|
||||
|
||||
void Load(const ConfigOptions& cfg);
|
||||
@@ -64,6 +65,7 @@ bool SpirvDebugPrintfEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool NggRectlistDrawEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#elif defined(__APPLE__)
|
||||
#include <csignal>
|
||||
#include <sys/ucontext.h>
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
@@ -118,6 +121,98 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
||||
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
static std::atomic<Handler> g_handler {nullptr};
|
||||
static std::atomic_uint32_t g_install_state {0};
|
||||
static thread_local bool g_in_exception_filter = false;
|
||||
|
||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||
|
||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
||||
std::fputs("HostException fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
std::_Exit(321);
|
||||
}
|
||||
|
||||
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
|
||||
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
|
||||
static AccessViolationType DecodeAccess(uint64_t err) {
|
||||
if ((err & 0x10u) != 0) {
|
||||
return AccessViolationType::Execute;
|
||||
}
|
||||
if ((err & 0x2u) != 0) {
|
||||
return AccessViolationType::Write;
|
||||
}
|
||||
return AccessViolationType::Read;
|
||||
}
|
||||
|
||||
// POSIX signal handler that mirrors the Windows vectored handler: build an ExceptionInfo
|
||||
// from the mcontext and dispatch. A resolved fault (handler returns true) simply returns,
|
||||
// re-executing the faulting instruction against the now-fixed protection. An unresolved
|
||||
// fault restores the default disposition so the retry terminates the process.
|
||||
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||
if (g_in_exception_filter) {
|
||||
FailFast("nested exception while resolving a host fault");
|
||||
}
|
||||
g_in_exception_filter = true;
|
||||
|
||||
auto* uc = static_cast<ucontext_t*>(uctx);
|
||||
const auto* mc = uc->uc_mcontext;
|
||||
const auto& ss = mc->__ss;
|
||||
|
||||
ExceptionInfo info {};
|
||||
info.exception_address = ss.__rip;
|
||||
info.native_code = static_cast<uint32_t>(si->si_code);
|
||||
info.native_context = uctx;
|
||||
|
||||
if (sig == SIGILL) {
|
||||
info.type = ExceptionType::IllegalInstruction;
|
||||
} else {
|
||||
info.type = ExceptionType::AccessViolation;
|
||||
info.access_violation_type = DecodeAccess(mc->__es.__err);
|
||||
info.access_violation_vaddr = reinterpret_cast<uint64_t>(si->si_addr);
|
||||
}
|
||||
|
||||
info.rax = ss.__rax;
|
||||
info.rbx = ss.__rbx;
|
||||
info.rcx = ss.__rcx;
|
||||
info.rdx = ss.__rdx;
|
||||
info.rsi = ss.__rsi;
|
||||
info.rdi = ss.__rdi;
|
||||
info.rbp = ss.__rbp;
|
||||
info.rsp = ss.__rsp;
|
||||
info.r8 = ss.__r8;
|
||||
info.r9 = ss.__r9;
|
||||
info.r10 = ss.__r10;
|
||||
info.r11 = ss.__r11;
|
||||
info.r12 = ss.__r12;
|
||||
info.r13 = ss.__r13;
|
||||
info.r14 = ss.__r14;
|
||||
info.r15 = ss.__r15;
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler == nullptr) {
|
||||
FailFast("host exception callback is null");
|
||||
}
|
||||
|
||||
const bool resolved = handler(info);
|
||||
g_in_exception_filter = false;
|
||||
|
||||
if (resolved) {
|
||||
return; // retry the faulting instruction against the fixed mapping
|
||||
}
|
||||
|
||||
// Unresolved: restore the default action so the re-executed instruction terminates.
|
||||
struct sigaction dfl {};
|
||||
dfl.sa_handler = SIG_DFL;
|
||||
sigemptyset(&dfl.sa_mask);
|
||||
sigaction(sig, &dfl, nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool InstallHandler(Handler handler) {
|
||||
@@ -140,6 +235,36 @@ bool InstallHandler(Handler handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_install_state.store(2, std::memory_order_release);
|
||||
return true;
|
||||
#elif defined(__APPLE__)
|
||||
if (handler == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t expected_state = 0;
|
||||
if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) {
|
||||
return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler;
|
||||
}
|
||||
|
||||
g_handler.store(handler, std::memory_order_release);
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
// macOS raises SIGBUS for protection faults on some paths and SIGSEGV on others;
|
||||
// SIGILL covers instructions the host cannot execute (routed to the x64 emulator).
|
||||
bool ok = sigaction(SIGSEGV, &sa, nullptr) == 0 && sigaction(SIGBUS, &sa, nullptr) == 0 &&
|
||||
sigaction(SIGILL, &sa, nullptr) == 0;
|
||||
if (!ok) {
|
||||
g_handler.store(nullptr, std::memory_order_release);
|
||||
g_install_state.store(0, std::memory_order_release);
|
||||
printf("sigaction() failed to install the host fault handler\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_install_state.store(2, std::memory_order_release);
|
||||
return true;
|
||||
#else
|
||||
|
||||
@@ -178,6 +178,11 @@ Direction GetDirection() {
|
||||
return g_direction;
|
||||
}
|
||||
|
||||
bool IsSilent() {
|
||||
// Before init LOGF must keep writing to stdout, so report non-silent.
|
||||
return g_initialized && g_direction == Direction::Silent;
|
||||
}
|
||||
|
||||
void Write(std::string_view text) {
|
||||
WriteImpl(text);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ KYTY_SUBSYSTEM_DEFINE(Log);
|
||||
enum class Direction { Silent, Console, File };
|
||||
|
||||
Direction GetDirection();
|
||||
bool IsSilent();
|
||||
void Write(std::string_view text);
|
||||
void Write(fmt::text_style style, std::string_view text);
|
||||
void WriteFatal(std::string_view text);
|
||||
@@ -41,8 +42,18 @@ inline constexpr auto BrightWhite = fmt::fg(fmt::terminal_color::bright_white)
|
||||
} // namespace Log
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define LOGF(...) ::Log::Write(::fmt::sprintf(__VA_ARGS__))
|
||||
#define LOGF(...) \
|
||||
do { \
|
||||
if (!::Log::IsSilent()) { \
|
||||
::Log::Write(::fmt::sprintf(__VA_ARGS__)); \
|
||||
} \
|
||||
} while (false)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define LOGF_COLOR(style, ...) ::Log::Write((style), ::fmt::sprintf(__VA_ARGS__))
|
||||
#define LOGF_COLOR(style, ...) \
|
||||
do { \
|
||||
if (!::Log::IsSilent()) { \
|
||||
::Log::Write((style), ::fmt::sprintf(__VA_ARGS__)); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#endif /* KYTY_COMMON_LOGGING_LOG_H_ */
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
#ifndef EMULATOR_SRC_COMMON_LRUCACHE_H_
|
||||
#define EMULATOR_SRC_COMMON_LRUCACHE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename Object, typename Tick>
|
||||
class LeastRecentlyUsedCache {
|
||||
struct Item {
|
||||
Object object {};
|
||||
Tick tick {};
|
||||
Item* next = nullptr;
|
||||
Item* prev = nullptr;
|
||||
};
|
||||
|
||||
public:
|
||||
[[nodiscard]] size_t Insert(Object object, Tick tick) {
|
||||
const auto id = Build();
|
||||
auto& item = m_items[id];
|
||||
item.object = std::move(object);
|
||||
item.tick = tick;
|
||||
Attach(item);
|
||||
return id;
|
||||
}
|
||||
|
||||
void Touch(size_t id, Tick tick) {
|
||||
auto& item = m_items[id];
|
||||
if (item.tick >= tick) {
|
||||
return;
|
||||
}
|
||||
item.tick = tick;
|
||||
if (&item != m_last) {
|
||||
Detach(item);
|
||||
Attach(item);
|
||||
}
|
||||
}
|
||||
|
||||
void Free(size_t id) {
|
||||
auto& item = m_items[id];
|
||||
Detach(item);
|
||||
item.next = nullptr;
|
||||
item.prev = nullptr;
|
||||
m_free.push_back(id);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void ForEachItemBelow(Tick tick, Function&& function) {
|
||||
constexpr bool ReturnsBool =
|
||||
std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
|
||||
for (auto* item = m_first; item != nullptr;) {
|
||||
if (item->tick > tick) {
|
||||
return;
|
||||
}
|
||||
auto* next = item->next;
|
||||
if constexpr (ReturnsBool) {
|
||||
if (function(item->object)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
function(item->object);
|
||||
}
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] size_t Build() {
|
||||
if (m_free.empty()) {
|
||||
const auto id = m_items.size();
|
||||
m_items.emplace_back();
|
||||
return id;
|
||||
}
|
||||
const auto id = m_free.front();
|
||||
m_free.pop_front();
|
||||
return id;
|
||||
}
|
||||
|
||||
void Attach(Item& item) {
|
||||
if (m_first == nullptr) {
|
||||
m_first = &item;
|
||||
}
|
||||
if (m_last == nullptr) {
|
||||
m_last = &item;
|
||||
return;
|
||||
}
|
||||
item.prev = m_last;
|
||||
m_last->next = &item;
|
||||
item.next = nullptr;
|
||||
m_last = &item;
|
||||
}
|
||||
|
||||
void Detach(Item& item) {
|
||||
if (item.prev != nullptr) {
|
||||
item.prev->next = item.next;
|
||||
}
|
||||
if (item.next != nullptr) {
|
||||
item.next->prev = item.prev;
|
||||
}
|
||||
if (m_first == &item) {
|
||||
m_first = item.next;
|
||||
}
|
||||
if (m_last == &item) {
|
||||
m_last = item.prev;
|
||||
}
|
||||
}
|
||||
|
||||
std::deque<Item> m_items;
|
||||
std::deque<size_t> m_free;
|
||||
Item* m_first = nullptr;
|
||||
Item* m_last = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif // EMULATOR_SRC_COMMON_LRUCACHE_H_
|
||||
@@ -11,6 +11,9 @@
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
|
||||
#endif
|
||||
|
||||
void SysStackWalk(void** /*stack*/, int* depth) {
|
||||
*depth = 0;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <utime.h>
|
||||
@@ -536,8 +537,26 @@ void SysFileFindFiles(const std::filesystem::path& /*path*/,
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& /*path*/, std::vector<sys_dir_entry_t>& /*out*/) {
|
||||
EXIT("not implemented\n");
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
|
||||
sys_dir_entry_t r {};
|
||||
r.name = entry->d_name;
|
||||
if (entry->d_type == DT_REG) {
|
||||
r.is_file = true;
|
||||
} else if (entry->d_type == DT_DIR) {
|
||||
r.is_file = false;
|
||||
} else {
|
||||
// DT_UNKNOWN / symlink: resolve with stat.
|
||||
struct stat st {};
|
||||
r.is_file = (stat((path / entry->d_name).c_str(), &st) == 0) ? S_ISREG(st.st_mode) : true;
|
||||
}
|
||||
out.push_back(std::move(r));
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
bool SysFileCopyFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <asm/mman-common.h>
|
||||
// IWYU pragma: no_include <asm/mman.h>
|
||||
// IWYU pragma: no_include <bits/pthread_types.h>
|
||||
@@ -31,8 +36,8 @@ void SysVirtualInit() {
|
||||
pthread_mutexattr_t attr {};
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP); // glibc-only fast mutex
|
||||
#else
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||
#endif
|
||||
@@ -129,6 +134,32 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
#if defined(__APPLE__)
|
||||
// Carve the aligned subrange out of the live mapping with MAP_FIXED (in-place
|
||||
// replacement) and trim the slack; never munmap the whole range first, or a
|
||||
// concurrent host mapping (dyld, Rosetta, Metal) could claim the hole and be
|
||||
// destroyed by the MAP_FIXED. Other platforms keep the original path below.
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
// NOLINTNEXTLINE
|
||||
void* fixed = mmap(reinterpret_cast<void*>(aligned_addr), size, protect,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
if (fixed == MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
} else {
|
||||
if (aligned_addr > ret_addr) {
|
||||
munmap(reinterpret_cast<void*>(ret_addr), aligned_addr - ret_addr);
|
||||
}
|
||||
const uintptr_t tail_start = aligned_addr + size;
|
||||
const uintptr_t resv_end = ret_addr + size + alignment;
|
||||
if (resv_end > tail_start) {
|
||||
munmap(reinterpret_cast<void*>(tail_start), resv_end - tail_start);
|
||||
}
|
||||
ptr = fixed;
|
||||
ret_addr = aligned_addr;
|
||||
}
|
||||
#else
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
@@ -146,6 +177,7 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +197,27 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// macOS has no /proc/self/maps; query the Mach VM map directly. mach_vm_region returns
|
||||
// the first mapped region at or above `region_addr`; if it begins before the end of the
|
||||
// requested range, the range overlaps an existing mapping.
|
||||
static bool is_mapped(void* ptr, size_t length) {
|
||||
auto query_addr = reinterpret_cast<mach_vm_address_t>(ptr);
|
||||
mach_vm_address_t region_addr = query_addr;
|
||||
mach_vm_size_t region_size = 0;
|
||||
vm_region_basic_info_data_64_t info {};
|
||||
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
mach_port_t object_name = MACH_PORT_NULL;
|
||||
|
||||
kern_return_t kr =
|
||||
mach_vm_region(mach_task_self(), ®ion_addr, ®ion_size, VM_REGION_BASIC_INFO_64,
|
||||
reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
return false; // no region at or above the address → unmapped
|
||||
}
|
||||
return region_addr < (query_addr + length);
|
||||
}
|
||||
#else
|
||||
static bool is_mapped(void* ptr, size_t length) {
|
||||
FILE* file = fopen("/proc/self/maps", "r");
|
||||
char line[1024];
|
||||
@@ -189,6 +242,7 @@ static bool is_mapped(void* ptr, size_t length) {
|
||||
fclose(file);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
@@ -261,6 +315,33 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
#if defined(__APPLE__)
|
||||
// Carve the aligned subrange out of the live reservation with MAP_FIXED (an
|
||||
// in-place replacement), then trim the slack. The range must never be
|
||||
// returned to the OS in between: another thread (dyld, Rosetta, Metal,
|
||||
// malloc) could claim the hole, and the subsequent MAP_FIXED would silently
|
||||
// destroy its mapping. Other platforms keep the original path below.
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
// NOLINTNEXTLINE
|
||||
void* fixed = mmap(reinterpret_cast<void*>(aligned_addr), size, PROT_NONE,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
if (fixed == MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
} else {
|
||||
if (aligned_addr > ret_addr) {
|
||||
munmap(reinterpret_cast<void*>(ret_addr), aligned_addr - ret_addr);
|
||||
}
|
||||
const uintptr_t tail_start = aligned_addr + size;
|
||||
const uintptr_t resv_end = ret_addr + size + alignment;
|
||||
if (resv_end > tail_start) {
|
||||
munmap(reinterpret_cast<void*>(tail_start), resv_end - tail_start);
|
||||
}
|
||||
ptr = fixed;
|
||||
ret_addr = aligned_addr;
|
||||
}
|
||||
#else
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
@@ -278,6 +359,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef KYTY_COMMON_UNIQUEFUNCTION_H_
|
||||
#define KYTY_COMMON_UNIQUEFUNCTION_H_
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename Result, typename... Args>
|
||||
class UniqueFunction {
|
||||
class CallableBase {
|
||||
public:
|
||||
virtual ~CallableBase() = default;
|
||||
virtual Result Invoke(Args&&... args) = 0;
|
||||
};
|
||||
|
||||
template <typename Function>
|
||||
class Callable final: public CallableBase {
|
||||
public:
|
||||
explicit Callable(Function function): m_function(std::move(function)) {}
|
||||
|
||||
Result Invoke(Args&&... args) override {
|
||||
return m_function(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Function m_function;
|
||||
};
|
||||
|
||||
public:
|
||||
UniqueFunction() = default;
|
||||
|
||||
template <typename Function>
|
||||
UniqueFunction(Function&& function)
|
||||
: m_callable(std::make_unique<Callable<std::decay_t<Function>>>(
|
||||
std::forward<Function>(function))) {}
|
||||
|
||||
UniqueFunction(UniqueFunction&&) noexcept = default;
|
||||
UniqueFunction& operator=(UniqueFunction&&) noexcept = default;
|
||||
UniqueFunction(const UniqueFunction&) = delete;
|
||||
UniqueFunction& operator=(const UniqueFunction&) = delete;
|
||||
|
||||
Result operator()(Args... args) const {
|
||||
return m_callable->Invoke(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return m_callable != nullptr; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<CallableBase> m_callable;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif // KYTY_COMMON_UNIQUEFUNCTION_H_
|
||||
+11
-20
@@ -159,25 +159,16 @@ static void LoadElf(const std::filesystem::path& elf, bool dbg_print_reloc = fal
|
||||
}
|
||||
}
|
||||
|
||||
static void Execute() {
|
||||
int thread_model = 1;
|
||||
|
||||
if (thread_model == 0) {
|
||||
Common::Thread t([](void* /*unused*/) { Libs::Graphics::WindowRun(); }, nullptr);
|
||||
t.Detach();
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
} else {
|
||||
Common::Thread t(
|
||||
[](void* /*unused*/) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
},
|
||||
nullptr);
|
||||
t.Detach();
|
||||
Libs::Graphics::WindowRun();
|
||||
t.Join();
|
||||
}
|
||||
static void Execute(const std::filesystem::path& game_patch) {
|
||||
auto patch_path = game_patch;
|
||||
Common::Thread guest_thread(
|
||||
[](void* param) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute(*static_cast<const std::filesystem::path*>(param));
|
||||
},
|
||||
&patch_path);
|
||||
Libs::Graphics::WindowRun();
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
void Run(const RunOptions& options) {
|
||||
@@ -217,7 +208,7 @@ void Run(const RunOptions& options) {
|
||||
|
||||
LoadElf(options.elf);
|
||||
|
||||
Execute();
|
||||
Execute(options.game_patch);
|
||||
}
|
||||
|
||||
} // namespace Emulator
|
||||
|
||||
@@ -12,6 +12,7 @@ struct RunOptions {
|
||||
Config::ConfigOptions config;
|
||||
std::filesystem::path app0_dir;
|
||||
std::filesystem::path elf;
|
||||
std::filesystem::path game_patch;
|
||||
};
|
||||
|
||||
void Run(const RunOptions& options);
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
#message("${GIT_EXECUTABLE}")
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always OUTPUT_VARIABLE KYTY_GIT_VERSION)
|
||||
else()
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" describe --tags --always
|
||||
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
|
||||
OUTPUT_VARIABLE KYTY_GIT_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE GIT_RESULT
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT GIT_RESULT EQUAL 0)
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
endif()
|
||||
endif()
|
||||
string(STRIP ${KYTY_GIT_VERSION} KYTY_GIT_VERSION)
|
||||
configure_file(${INPUT_FILE} ${OUTPUT_FILE})
|
||||
configure_file("${INPUT_FILE}" "${OUTPUT_FILE}")
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class AsyncJob final {
|
||||
public:
|
||||
using Task = std::function<void()>;
|
||||
|
||||
explicit AsyncJob(std::string thread_name = {})
|
||||
: m_thread_name(std::move(thread_name)), m_worker_thread(WorkerEntry, this) {}
|
||||
|
||||
~AsyncJob() {
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_stop_requested = true;
|
||||
m_task_available_condition.Signal();
|
||||
}
|
||||
|
||||
m_worker_thread.Join();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(AsyncJob);
|
||||
|
||||
void Execute(Task task) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (m_is_busy) {
|
||||
m_idle_condition.Wait(&m_mutex);
|
||||
}
|
||||
|
||||
m_task = std::move(task);
|
||||
m_is_busy = true;
|
||||
m_task_available_condition.Signal();
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (m_is_busy) {
|
||||
m_idle_condition.Wait(&m_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void WorkerEntry(void* data) { static_cast<AsyncJob*>(data)->WorkerLoop(); }
|
||||
|
||||
void WorkerLoop() {
|
||||
if (!m_thread_name.empty()) {
|
||||
KYTY_PROFILER_THREAD(m_thread_name.c_str());
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
Task task;
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (!m_is_busy && !m_stop_requested) {
|
||||
m_task_available_condition.Wait(&m_mutex);
|
||||
}
|
||||
|
||||
if (!m_is_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
task = std::move(m_task);
|
||||
}
|
||||
|
||||
task();
|
||||
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_is_busy = false;
|
||||
m_idle_condition.SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_thread_name;
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_task_available_condition;
|
||||
Common::CondVar m_idle_condition;
|
||||
Task m_task;
|
||||
bool m_is_busy = false;
|
||||
bool m_stop_requested = false;
|
||||
Common::Thread m_worker_thread;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_ */
|
||||
@@ -2,109 +2,36 @@
|
||||
#define GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
inline constexpr uint32_t AcquireGcrGl2Writeback = 1u << 15u;
|
||||
|
||||
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func);
|
||||
|
||||
class CommandScheduler {
|
||||
enum class Pm4ProcessResult { Complete, Blocked };
|
||||
|
||||
class Pm4Execution {
|
||||
public:
|
||||
static constexpr int BuffersNum = 8;
|
||||
|
||||
CommandScheduler(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders)
|
||||
: m_registers(registers), m_user_config(user_config), m_shaders(shaders) {}
|
||||
|
||||
bool Active() const { return m_current >= 0 && m_current < BuffersNum; }
|
||||
void CheckActive() const { EXIT_IF(!Active()); }
|
||||
|
||||
RenderCommandBuffer& Current() const {
|
||||
CheckActive();
|
||||
EXIT_IF(m_buffers[m_current] == nullptr);
|
||||
return *m_buffers[m_current];
|
||||
}
|
||||
|
||||
void Init() {
|
||||
if (m_current >= 0) {
|
||||
return;
|
||||
}
|
||||
for (auto& buf: m_buffers) {
|
||||
EXIT_IF(buf != nullptr);
|
||||
buf = new RenderCommandBuffer(m_registers, m_user_config, m_shaders);
|
||||
}
|
||||
m_current = 0;
|
||||
Current().Begin();
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
SubmitCurrent();
|
||||
BeginNext();
|
||||
}
|
||||
|
||||
CommandBuffer& FlushAndGetSubmitted() {
|
||||
auto& submitted = SubmitCurrent();
|
||||
BeginNext();
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CopyBuffers(std::array<CommandBuffer*, BuffersNum>& out) const {
|
||||
for (int i = 0; i < BuffersNum; i++) {
|
||||
auto* buf = m_buffers[i];
|
||||
EXIT_IF(buf == nullptr);
|
||||
out[i] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitAll() {
|
||||
for (auto* buf: m_buffers) {
|
||||
EXIT_IF(buf == nullptr);
|
||||
buf->WaitForFenceAndReset();
|
||||
}
|
||||
}
|
||||
|
||||
void SubmitForReadback() {
|
||||
if (!Active()) {
|
||||
return;
|
||||
}
|
||||
SubmitCurrent();
|
||||
}
|
||||
|
||||
void ResumeAfterReadback() {
|
||||
if (!Active()) {
|
||||
return;
|
||||
}
|
||||
Current().WaitForFenceAndReset();
|
||||
Current().Begin();
|
||||
}
|
||||
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
|
||||
|
||||
private:
|
||||
CommandBuffer& SubmitCurrent() {
|
||||
auto& submitted = Current();
|
||||
submitted.End();
|
||||
submitted.Execute();
|
||||
return submitted;
|
||||
}
|
||||
friend class CommandProcessor;
|
||||
|
||||
void BeginNext() {
|
||||
m_current = (m_current + 1) % BuffersNum;
|
||||
Current().WaitForFenceAndReset();
|
||||
Current().Begin();
|
||||
}
|
||||
struct BufferCursor {
|
||||
uint32_t* next_packet = nullptr;
|
||||
uint32_t remaining_dw = 0;
|
||||
uint32_t total_dw = 0;
|
||||
uint32_t deferred_advance_dw = 0;
|
||||
};
|
||||
|
||||
RenderCommandBuffer* m_buffers[BuffersNum] = {};
|
||||
int m_current = -1;
|
||||
HW::Context& m_registers;
|
||||
HW::UserConfig& m_user_config;
|
||||
HW::Shader& m_shaders;
|
||||
std::vector<BufferCursor> m_buffer_stack;
|
||||
bool m_suspended = false;
|
||||
bool m_made_progress = false;
|
||||
};
|
||||
|
||||
class CommandProcessor {
|
||||
@@ -116,8 +43,8 @@ public:
|
||||
int64_t flip_arg = 0;
|
||||
};
|
||||
|
||||
CommandProcessor();
|
||||
~CommandProcessor() { KYTY_NOT_IMPLEMENTED; }
|
||||
explicit CommandProcessor(RenderContext& renderer): m_renderer(renderer) {}
|
||||
~CommandProcessor() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandProcessor);
|
||||
|
||||
@@ -128,26 +55,18 @@ public:
|
||||
void BufferFlushAndWait();
|
||||
void BufferWait();
|
||||
void BeginReadbackTransaction() {
|
||||
m_mutex.Lock();
|
||||
if (m_readback_active) {
|
||||
EXIT("nested command-processor readback transaction\n");
|
||||
}
|
||||
m_readback_active = true;
|
||||
m_readback_finished = false;
|
||||
m_readback_active = true;
|
||||
}
|
||||
void FinishReadbackTransaction();
|
||||
void EndReadbackTransaction() {
|
||||
if (!m_readback_active) {
|
||||
EXIT("command-processor readback transaction is not active\n");
|
||||
}
|
||||
m_readback_active = false;
|
||||
m_readback_finished = false;
|
||||
m_mutex.Unlock();
|
||||
m_readback_active = false;
|
||||
}
|
||||
|
||||
void RunLock() { m_run_mutex.Lock(); }
|
||||
void RunUnlock() { m_run_mutex.Unlock(); }
|
||||
|
||||
HW::Context& GetCtx() { return m_ctx; }
|
||||
HW::UserConfig& GetUcfg() { return m_ucfg; }
|
||||
HW::Shader& GetShCtx() { return m_sh_ctx; }
|
||||
@@ -182,12 +101,10 @@ public:
|
||||
void Flip(void* dst_gpu_addr, uint32_t value);
|
||||
void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr,
|
||||
uint32_t value);
|
||||
void PrepareCpuFlip();
|
||||
void PrepareCpuFlip(uint64_t request_id);
|
||||
void SynchronizeGpu();
|
||||
void MemoryBarrier();
|
||||
void EmitGlobalBarrier();
|
||||
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
|
||||
void RenderTextureBarrier(uint64_t vaddr, uint64_t size);
|
||||
void DepthStencilBarrier(uint64_t vaddr, uint64_t size);
|
||||
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
|
||||
uint32_t mode);
|
||||
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
|
||||
@@ -196,16 +113,13 @@ public:
|
||||
|
||||
void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; }
|
||||
[[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; }
|
||||
void SetEmbeddedDataMarker(const uint32_t* buffer, uint32_t num_dw, uint32_t align) {}
|
||||
void PushMarker(const char* str) {}
|
||||
void PopMarker() {}
|
||||
|
||||
void PrefetchL2(void* addr, uint32_t size) {}
|
||||
void ResetDeCe();
|
||||
void SetCeComplete(bool complete) { m_ce_complete = complete; }
|
||||
void WaitCe();
|
||||
void WaitDeDiff(uint32_t diff);
|
||||
void IncremenetDe();
|
||||
void IncremenetCe();
|
||||
void IncrementDe();
|
||||
void IncrementCe();
|
||||
|
||||
void WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num);
|
||||
void DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num);
|
||||
@@ -222,32 +136,29 @@ public:
|
||||
const volatile void* address, uint32_t count_in_dwords);
|
||||
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
|
||||
|
||||
void Run(uint32_t* data, uint32_t num_dw);
|
||||
Pm4ProcessResult Process(Pm4Execution& execution, uint32_t* buffer, uint32_t size_dw);
|
||||
void ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw);
|
||||
|
||||
[[nodiscard]] const FlipInfo& GetFlip() const { return m_flip; }
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
|
||||
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
|
||||
|
||||
private:
|
||||
struct Counter {
|
||||
Common::Mutex mutex;
|
||||
Common::CondVar cond_var;
|
||||
uint32_t value = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type,
|
||||
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id);
|
||||
void FinishCommandProcessors();
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void SuspendPm4();
|
||||
|
||||
RenderCommandBuffer& CurrentBuffer() { return m_scheduler.Current(); }
|
||||
void CheckBuffer() const { m_scheduler.CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return GetRenderContext().GetGpuResources(); }
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
@@ -259,16 +170,10 @@ private:
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
uint32_t m_num_instances = 1;
|
||||
|
||||
inline static Common::Mutex m_mutex;
|
||||
inline static std::vector<CommandProcessor*> m_processors;
|
||||
inline static bool m_readback_active = false;
|
||||
inline static bool m_readback_finished = false;
|
||||
Common::Mutex m_run_mutex;
|
||||
|
||||
CommandScheduler m_scheduler;
|
||||
|
||||
Counter m_de_counter;
|
||||
Counter m_ce_counter;
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
bool m_ce_complete = false;
|
||||
bool m_readback_active = false;
|
||||
|
||||
uint32_t m_const_ram[0x3000] = {0};
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "graphics/guest_gpu/command_processor/pm4Dispatch.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/label.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
@@ -598,30 +597,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthRenderTarget) {
|
||||
uint32_t count = 1;
|
||||
|
||||
if (cmd_id == 0xC0016900) {
|
||||
HW::DepthZInfo r;
|
||||
|
||||
// r.expclear_enabled = (buffer[0] & 0x08000000u) != 0;
|
||||
// r.format = (buffer[0] >> Pm4::DB_Z_INFO_FORMAT_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_FORMAT_MASK; r.num_samples = (buffer[0] >>
|
||||
// Pm4::DB_Z_INFO_NUM_SAMPLES_SHIFT) & Pm4::DB_Z_INFO_NUM_SAMPLES_MASK; r.tile_mode_index =
|
||||
//(buffer[0] >> Pm4::DB_Z_INFO_TILE_MODE_INDEX_SHIFT) & Pm4::DB_Z_INFO_TILE_MODE_INDEX_MASK;
|
||||
// r.tile_surface_enable = ((buffer[0] >> Pm4::DB_Z_INFO_TILE_SURFACE_ENABLE_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_TILE_SURFACE_ENABLE_MASK) !=0
|
||||
// r.zrange_precision = (buffer[0] >> Pm4::DB_Z_INFO_ZRANGE_PRECISION_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_ZRANGE_PRECISION_MASK;
|
||||
|
||||
r.format = KYTY_PM4_GET(buffer[0], DB_Z_INFO, FORMAT);
|
||||
r.num_samples = KYTY_PM4_GET(buffer[0], DB_Z_INFO, NUM_SAMPLES);
|
||||
r.embedded_sample_locations = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(buffer[0], DB_Z_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.num_mip_levels = KYTY_PM4_GET(buffer[0], DB_Z_INFO, MAXMIP);
|
||||
r.tile_mode_index = KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_MODE_INDEX);
|
||||
r.plane_compression = KYTY_PM4_GET(buffer[0], DB_Z_INFO, DECOMPRESS_ON_N_ZPLANES);
|
||||
r.expclear_enabled = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_surface_enable = KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
r.zrange_precision = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ZRANGE_PRECISION);
|
||||
|
||||
cp.GetCtx().SetDepthZInfo(r);
|
||||
cp.GetCtx().SetDepthZInfo(HW::DepthZInfo::Decode(buffer[0]));
|
||||
} else if (cmd_id == 0xC0086900) {
|
||||
if (dw >= 22 && buffer[8] == 0xC0016900 && buffer[9] == Pm4::DB_DEPTH_INFO &&
|
||||
buffer[11] == 0xC0016900 && buffer[12] == Pm4::DB_DEPTH_VIEW &&
|
||||
@@ -632,51 +608,8 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthRenderTarget) {
|
||||
|
||||
HW::DepthRenderTarget z;
|
||||
|
||||
// z.z_info.expclear_enabled = (buffer[0] & 0x08000000u) != 0;
|
||||
// z.z_info.format = (buffer[0] >> Pm4::DB_Z_INFO_FORMAT_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_FORMAT_MASK; z.z_info.num_samples = (buffer[0] >>
|
||||
// Pm4::DB_Z_INFO_NUM_SAMPLES_SHIFT) & Pm4::DB_Z_INFO_NUM_SAMPLES_MASK;
|
||||
// z.z_info.tile_mode_index = (buffer[0] >>
|
||||
// Pm4::DB_Z_INFO_TILE_MODE_INDEX_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_TILE_MODE_INDEX_MASK; z.z_info.tile_surface_enable =
|
||||
// KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
// z.z_info.zrange_precision = (buffer[0] >> Pm4::DB_Z_INFO_ZRANGE_PRECISION_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_ZRANGE_PRECISION_MASK;
|
||||
|
||||
z.z_info.format = KYTY_PM4_GET(buffer[0], DB_Z_INFO, FORMAT);
|
||||
z.z_info.num_samples = KYTY_PM4_GET(buffer[0], DB_Z_INFO, NUM_SAMPLES);
|
||||
z.z_info.embedded_sample_locations =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, ITERATE_FLUSH) != 0;
|
||||
z.z_info.partially_resident =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
z.z_info.num_mip_levels = KYTY_PM4_GET(buffer[0], DB_Z_INFO, MAXMIP);
|
||||
z.z_info.tile_mode_index = KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_MODE_INDEX);
|
||||
z.z_info.plane_compression =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, DECOMPRESS_ON_N_ZPLANES);
|
||||
z.z_info.expclear_enabled = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
z.z_info.tile_surface_enable =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
z.z_info.zrange_precision = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ZRANGE_PRECISION);
|
||||
|
||||
// z.stencil_info.expclear_enabled = (buffer[1] & 0x08000000u) != 0;
|
||||
// z.stencil_info.tile_split = (buffer[1] >> 13u) & 0x7u;
|
||||
// z.stencil_info.format = KYTY_PM4_GET(buffer[1],
|
||||
// DB_STENCIL_INFO, FORMAT); z.stencil_info.tile_mode_index =
|
||||
// KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
// z.stencil_info.tile_stencil_disable = KYTY_PM4_GET(buffer[1],
|
||||
// DB_STENCIL_INFO, TILE_STENCIL_DISABLE);
|
||||
z.stencil_info.format = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, FORMAT);
|
||||
z.stencil_info.texture_compatible_stencil =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ITERATE_FLUSH) != 0;
|
||||
z.stencil_info.partially_resident =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
z.stencil_info.tile_split = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, RESERVED_FIELD_1);
|
||||
z.stencil_info.tile_mode_index =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
z.stencil_info.expclear_enabled =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
z.stencil_info.tile_stencil_disable =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_STENCIL_DISABLE) != 0;
|
||||
z.z_info = HW::DepthZInfo::Decode(buffer[0]);
|
||||
z.stencil_info = HW::DepthStencilInfo::Decode(buffer[1]);
|
||||
|
||||
z.z_read_base_addr = static_cast<uint64_t>(buffer[2]) << 8u;
|
||||
z.stencil_read_base_addr = static_cast<uint64_t>(buffer[3]) << 8u;
|
||||
@@ -1253,26 +1186,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetStencilInfo) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
|
||||
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_STENCIL_INFO);
|
||||
|
||||
HW::DepthStencilInfo r;
|
||||
|
||||
// r.expclear_enabled = (buffer[0] & 0x08000000u) != 0;
|
||||
// r.tile_split = (buffer[0] >> 13u) & 0x7u;
|
||||
// r.format = (buffer[0] >> Pm4::DB_STENCIL_INFO_FORMAT_SHIFT) &
|
||||
// Pm4::DB_STENCIL_INFO_FORMAT_MASK; r.tile_mode_index = (buffer[0] >>
|
||||
// Pm4::DB_STENCIL_INFO_TILE_MODE_INDEX_SHIFT) & Pm4::DB_STENCIL_INFO_TILE_MODE_INDEX_MASK;
|
||||
// r.tile_stencil_disable =
|
||||
// ((buffer[0] >> Pm4::DB_STENCIL_INFO_TILE_STENCIL_DISABLE_SHIFT) &
|
||||
// Pm4::DB_STENCIL_INFO_TILE_STENCIL_DISABLE_MASK) != 0;
|
||||
|
||||
r.format = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, FORMAT);
|
||||
r.texture_compatible_stencil = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.tile_split = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, RESERVED_FIELD_1);
|
||||
r.tile_mode_index = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
r.expclear_enabled = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_stencil_disable = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_STENCIL_DISABLE) != 0;
|
||||
|
||||
cp.GetCtx().SetDepthStencilInfo(r);
|
||||
cp.GetCtx().SetDepthStencilInfo(HW::DepthStencilInfo::Decode(buffer[0]));
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1764,231 +1678,11 @@ static bool HwUcTrySetFakeRegisterRange(uint32_t cmd_offset, const uint32_t* buf
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
KYTY_CP_OP_PARSER(CpOpAcquireMem) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0055800 && cmd_id != 0xc0061050);
|
||||
|
||||
bool custom = (cmd_id == 0xc0061050);
|
||||
|
||||
uint32_t engine = buffer[0] >> 31u;
|
||||
uint32_t stall_mode = (custom ? 1u : engine);
|
||||
uint32_t cache_action = buffer[0] & 0x7fffffffu;
|
||||
uint64_t size_lo = buffer[1];
|
||||
uint32_t size_hi = buffer[2];
|
||||
uint64_t base_lo = buffer[3];
|
||||
uint32_t base_hi = buffer[4];
|
||||
uint32_t poll = buffer[5];
|
||||
[[maybe_unused]] uint32_t gcr_cntl = (custom ? buffer[6] : 0);
|
||||
|
||||
uint32_t target_mask = cache_action & 0x00007FC0u;
|
||||
uint32_t extended_action = cache_action & 0x2E000000u;
|
||||
uint32_t action =
|
||||
((cache_action & 0x00C00000u) >> 0x12u) | ((cache_action & 0x00058000u) >> 0xfu);
|
||||
|
||||
if (custom && engine > 1) {
|
||||
LOGF("\t warning: custom acquire_mem unsupported engine: %" PRIu32 "\n", engine);
|
||||
}
|
||||
|
||||
// EXIT_NOT_IMPLEMENTED(stall_mode != 1);
|
||||
EXIT_NOT_IMPLEMENTED(size_hi != 0);
|
||||
EXIT_NOT_IMPLEMENTED(base_hi != 0);
|
||||
if (poll != 10) {
|
||||
LOGF("\t warning: acquire_mem unexpected poll interval: %" PRIu32 "\n", poll);
|
||||
}
|
||||
|
||||
switch (cache_action) {
|
||||
case 0x00000000: {
|
||||
if (custom && gcr_cntl != 0) {
|
||||
LOGF("\t custom acquire_mem GCR-only barrier, gcr_cntl = 0x%08" PRIx32
|
||||
", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
|
||||
gcr_cntl, base_lo << 8u, size_lo << 8u);
|
||||
|
||||
cp.MemoryBarrier();
|
||||
if ((gcr_cntl & AcquireGcrGl2Writeback) != 0) {
|
||||
cp.SynchronizeGpu();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 0x00000040:
|
||||
case 0x00003fc0:
|
||||
case 0x00004000:
|
||||
case 0x00007fc0: {
|
||||
// target_mask set, no CB/DB action bits. Treat as an ordering barrier.
|
||||
EXIT_IF(target_mask != cache_action);
|
||||
EXIT_IF(extended_action != 0x00000000);
|
||||
EXIT_IF(action != 0x00);
|
||||
|
||||
LOGF("\t temporary: acquire_mem target-mask-only barrier, target_mask = 0x%08" PRIx32
|
||||
", gcr_cntl = 0x%08" PRIx32 ", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
|
||||
target_mask, gcr_cntl, base_lo << 8u, size_lo << 8u);
|
||||
|
||||
cp.MemoryBarrier();
|
||||
} break;
|
||||
case 0x02000000: {
|
||||
// target_mask: 0x00000000 (none)
|
||||
// extended_action: 0x02000000 (FlushAndInvalidateCbCache)
|
||||
// action: 0x00 (none)
|
||||
EXIT_IF(target_mask != 0x00000000);
|
||||
EXIT_IF(extended_action != 0x02000000);
|
||||
EXIT_IF(action != 0x00);
|
||||
|
||||
LOGF("\t temporary: acquire_mem CB-cache-only barrier, gcr_cntl = 0x%08" PRIx32
|
||||
", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
|
||||
gcr_cntl, base_lo << 8u, size_lo << 8u);
|
||||
|
||||
cp.MemoryBarrier();
|
||||
} break;
|
||||
case 0x04000000: {
|
||||
// target_mask: 0x00000000 (none)
|
||||
// extended_action: 0x04000000 (FlushAndInvalidateDbCache)
|
||||
// action: 0x00 (none)
|
||||
EXIT_IF(target_mask != 0x00000000);
|
||||
EXIT_IF(extended_action != 0x04000000);
|
||||
EXIT_IF(action != 0x00);
|
||||
|
||||
LOGF("\t temporary: acquire_mem DB-cache-only barrier, gcr_cntl = 0x%08" PRIx32
|
||||
", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
|
||||
gcr_cntl, base_lo << 8u, size_lo << 8u);
|
||||
|
||||
cp.MemoryBarrier();
|
||||
} break;
|
||||
case 0x04004000:
|
||||
case 0x04007fc0: {
|
||||
// target_mask: 0x00004000 (Depth Target), 0x00007fc0 (all rt and depth)
|
||||
// extended_action: 0x04000000 (FlushAndInvalidateDbCache)
|
||||
// action: 0x00 (none)
|
||||
EXIT_IF(target_mask != 0x00004000 && target_mask != 0x00007FC0);
|
||||
EXIT_IF(extended_action != 0x04000000);
|
||||
EXIT_IF(action != 0x00);
|
||||
|
||||
LOGF("\t temporary: acquire_mem DB target barrier, target_mask = 0x%08" PRIx32
|
||||
", gcr_cntl = 0x%08" PRIx32 ", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
|
||||
target_mask, gcr_cntl, base_lo << 8u, size_lo << 8u);
|
||||
|
||||
if (size_lo != 0) {
|
||||
cp.DepthStencilBarrier(base_lo << 8u, size_lo << 8u);
|
||||
} else {
|
||||
cp.MemoryBarrier();
|
||||
}
|
||||
} break;
|
||||
case 0x02c40040:
|
||||
case 0x02c43fc0:
|
||||
case 0x02c47fc0: {
|
||||
// target_mask: 0x00000040 (rt0), 0x00003fc0 (all rt), 0x00007fc0 (all rt and depth)
|
||||
// extended_action: 0x02000000 (FlushAndInvalidateCbCache)
|
||||
// action: 0x38 (WriteBackAndInvalidateL1andL2)
|
||||
EXIT_IF(target_mask != 0x00000040 && target_mask != 0x00003FC0 &&
|
||||
target_mask != 0x00007FC0);
|
||||
EXIT_IF(extended_action != 0x02000000);
|
||||
EXIT_IF(action != 0x38);
|
||||
EXIT_NOT_IMPLEMENTED(size_lo == 0);
|
||||
EXIT_NOT_IMPLEMENTED(base_lo == 0);
|
||||
|
||||
cp.RenderTextureBarrier(base_lo << 8u, size_lo << 8u);
|
||||
cp.SynchronizeGpu();
|
||||
} break;
|
||||
case 0x02003fc0:
|
||||
case 0x02007fc0: {
|
||||
// target_mask: 0x00003FC0 (all rt), 0x00007fc0 (all rt and depth)
|
||||
// extended_action: 0x02000000 (FlushAndInvalidateCbCache)
|
||||
// action: 0x00 (none)
|
||||
EXIT_IF(target_mask != 0x00003FC0 && target_mask != 0x00007fc0);
|
||||
EXIT_IF(extended_action != 0x02000000);
|
||||
EXIT_IF(action != 0x00);
|
||||
|
||||
if (size_lo == 0) {
|
||||
if (base_lo != 0) {
|
||||
LOGF("\t warning: acquire_mem CB-cache barrier with non-zero base");
|
||||
}
|
||||
|
||||
cp.MemoryBarrier();
|
||||
} else {
|
||||
EXIT_NOT_IMPLEMENTED(base_lo == 0);
|
||||
|
||||
cp.RenderTextureBarrier(base_lo << 8u, size_lo << 8u);
|
||||
}
|
||||
} break;
|
||||
case 0x00C40000: {
|
||||
// target_mask: 0x00000000 (none)
|
||||
// extended_action: 0x00000000 (none)
|
||||
// action: 0x38 (WriteBackAndInvalidateL1andL2)
|
||||
EXIT_IF(target_mask != 0x00000000);
|
||||
EXIT_IF(extended_action != 0x00000000);
|
||||
EXIT_IF(action != 0x38);
|
||||
EXIT_NOT_IMPLEMENTED(size_lo != 1);
|
||||
EXIT_NOT_IMPLEMENTED(base_lo != 0);
|
||||
|
||||
cp.MemoryBarrier();
|
||||
cp.SynchronizeGpu();
|
||||
} break;
|
||||
case 0x00400000: {
|
||||
// target_mask: 0x00000000 (none)
|
||||
// extended_action: 0x00000000 (none)
|
||||
// action: 0x10 (InvalidateL1)
|
||||
EXIT_IF(target_mask != 0x00000000);
|
||||
EXIT_IF(extended_action != 0x00000000);
|
||||
EXIT_IF(action != 0x10);
|
||||
EXIT_NOT_IMPLEMENTED(size_lo != 1);
|
||||
EXIT_NOT_IMPLEMENTED(base_lo != 0);
|
||||
|
||||
cp.MemoryBarrier();
|
||||
} break;
|
||||
case 0x04c44000: {
|
||||
// target_mask: 0x00004000 (Depth Target)
|
||||
// extended_action: 0x04000000 (FlushAndInvalidateDbCache)
|
||||
// action: 0x38 (WriteBackAndInvalidateL1andL2)
|
||||
EXIT_IF(target_mask != 0x00004000);
|
||||
EXIT_IF(extended_action != 0x04000000);
|
||||
EXIT_IF(action != 0x38);
|
||||
|
||||
cp.DepthStencilBarrier(base_lo << 8u, size_lo << 8u);
|
||||
cp.SynchronizeGpu();
|
||||
} break;
|
||||
|
||||
case 0x06000040:
|
||||
case 0x06000080:
|
||||
case 0x06003fc0:
|
||||
case 0x06007fc0: {
|
||||
// target_mask: 0x00000040 (rt0), 0x00000080 (rt1), 0x00003fc0 (all rt), 0x00007fc0
|
||||
// (all rt and depth) extended_action: 0x06000000 (Flush Cb & Db) action: 0x00
|
||||
// (none)
|
||||
if (gcr_cntl != 0 && gcr_cntl != 0x280 && gcr_cntl != 0x300) {
|
||||
LOGF("\t temporary: acquire_mem CB+DB barrier with unhandled GCR control "
|
||||
"0x%08" PRIx32 "\n",
|
||||
gcr_cntl);
|
||||
}
|
||||
|
||||
EXIT_IF(target_mask != 0x00000040 && target_mask != 0x00000080 &&
|
||||
target_mask != 0x00003fc0 && target_mask != 0x00007fc0);
|
||||
EXIT_IF(extended_action != 0x06000000);
|
||||
EXIT_IF(action != 0x00);
|
||||
|
||||
if (size_lo != 0) {
|
||||
if ((target_mask & 0x00003fc0) != 0) {
|
||||
cp.RenderTextureBarrier(base_lo << 8u, size_lo << 8u);
|
||||
}
|
||||
if ((target_mask & 0x00004000) != 0) {
|
||||
cp.DepthStencilBarrier(base_lo << 8u, size_lo << 8u);
|
||||
}
|
||||
} else {
|
||||
cp.MemoryBarrier();
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
EXIT("unknown barrier: 0x%08" PRIx32 ", 0x%08" PRIx32 ", 0x%08" PRIx32 ", 0x%08" PRIx32
|
||||
"\n",
|
||||
cache_action, target_mask, extended_action, action);
|
||||
}
|
||||
|
||||
if (stall_mode == 0) {
|
||||
cp.BufferFlush();
|
||||
cp.BufferWait();
|
||||
}
|
||||
|
||||
return (custom ? 7 : 6);
|
||||
return (cmd_id == 0xc0061050 ? 7 : 6);
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpDispatchDirect) {
|
||||
@@ -2166,10 +1860,10 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
|
||||
reinterpret_cast<uint64_t>(else_buffer), else_num_dw);
|
||||
|
||||
if (take_then) {
|
||||
cp.Run(then_buffer, then_num_dw);
|
||||
cp.ProcessIndirectBuffer(then_buffer, then_num_dw);
|
||||
} else if (mode == 2 && else_num_dw != 0) {
|
||||
EXIT_NOT_IMPLEMENTED(else_buffer == nullptr);
|
||||
cp.Run(else_buffer, else_num_dw);
|
||||
cp.ProcessIndirectBuffer(else_buffer, else_num_dw);
|
||||
}
|
||||
|
||||
return payload_dw;
|
||||
@@ -2250,9 +1944,6 @@ KYTY_CP_OP_PARSER(CpOpDmaData) {
|
||||
const uint64_t dst = buffer[3] | (static_cast<uint64_t>(buffer[4]) << 32u);
|
||||
|
||||
if (control == 0x60000000 && dst == 0x0003022c && (control2 >> 21u) == 0x141u) {
|
||||
auto* addr = reinterpret_cast<void*>(src);
|
||||
|
||||
cp.PrefetchL2(addr, control2 & 0x1fffffu);
|
||||
return 6;
|
||||
}
|
||||
|
||||
@@ -2496,7 +2187,7 @@ KYTY_CP_OP_PARSER(CpOpIncrementCeCounter) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008400);
|
||||
EXIT_NOT_IMPLEMENTED(buffer[0] != 1);
|
||||
|
||||
cp.IncremenetCe();
|
||||
cp.IncrementCe();
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -2507,7 +2198,7 @@ KYTY_CP_OP_PARSER(CpOpIncrementDeCounter) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008500);
|
||||
EXIT_NOT_IMPLEMENTED(buffer[0] != 0);
|
||||
|
||||
cp.IncremenetDe();
|
||||
cp.IncrementDe();
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -2607,7 +2298,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
|
||||
|
||||
GraphicsDbgDumpDcb("ci", indirect_num_dw, indirect_buffer);
|
||||
|
||||
cp.Run(indirect_buffer, indirect_num_dw);
|
||||
cp.ProcessIndirectBuffer(indirect_buffer, indirect_num_dw);
|
||||
|
||||
return 3;
|
||||
}
|
||||
@@ -2800,11 +2491,10 @@ KYTY_CP_OP_PARSER(CpOpMarker) {
|
||||
// EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0001000);
|
||||
|
||||
uint32_t id = buffer[0] & 0xfffu;
|
||||
uint32_t align = (buffer[0] >> 12u) & 0xfu;
|
||||
uint32_t len_dw = ((cmd_id >> 16u) & 0x3fffu);
|
||||
|
||||
switch (id) {
|
||||
case 0x0: cp.SetEmbeddedDataMarker(buffer + 1, len_dw, align); break;
|
||||
case 0x0: break;
|
||||
case 0x4: cp.SetUserDataMarker(HW::UserSgprType::Vsharp); break;
|
||||
case 0xd: cp.SetUserDataMarker(HW::UserSgprType::Region); break;
|
||||
case 0x777: {
|
||||
@@ -2876,8 +2566,6 @@ KYTY_CP_OP_PARSER(CpOpPopMarker) {
|
||||
LOGF("Pop marker\n");
|
||||
}
|
||||
|
||||
cp.PopMarker();
|
||||
|
||||
return dw_num + 1;
|
||||
}
|
||||
|
||||
@@ -2893,8 +2581,6 @@ KYTY_CP_OP_PARSER(CpOpPushMarker) {
|
||||
LOGF("Push marker: %s\n", str);
|
||||
}
|
||||
|
||||
cp.PushMarker(str);
|
||||
|
||||
return dw_num + 1;
|
||||
}
|
||||
|
||||
@@ -2939,7 +2625,7 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
|
||||
if (data_sel == 0 || interrupt_selector == 4) {
|
||||
if (eop_event_type != 0x28 || gcr_cntl != 0) {
|
||||
cp.MemoryBarrier();
|
||||
cp.EmitGlobalBarrier();
|
||||
}
|
||||
|
||||
if (gl2_writeback) {
|
||||
@@ -2953,7 +2639,7 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
|
||||
if (release_dst == ReleaseMemDstMemory && dst_gpu_addr == nullptr) {
|
||||
if (eop_event_type != 0x28 || gcr_cntl != 0) {
|
||||
cp.MemoryBarrier();
|
||||
cp.EmitGlobalBarrier();
|
||||
}
|
||||
|
||||
if (gl2_writeback) {
|
||||
@@ -2966,7 +2652,7 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
}
|
||||
|
||||
if (ReleaseMemGcrNeedsBarrier(eop_event_type, gcr_cntl)) {
|
||||
cp.MemoryBarrier();
|
||||
cp.EmitGlobalBarrier();
|
||||
}
|
||||
|
||||
auto cache_action = ReleaseMemCacheActionFromGcr(gcr_cntl);
|
||||
@@ -3986,18 +3672,7 @@ void GraphicsInitJmpTablesCxIndirect() {
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_Z_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HW::DepthZInfo r;
|
||||
r.format = KYTY_PM4_GET(value, DB_Z_INFO, FORMAT);
|
||||
r.num_samples = KYTY_PM4_GET(value, DB_Z_INFO, NUM_SAMPLES);
|
||||
r.embedded_sample_locations = KYTY_PM4_GET(value, DB_Z_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(value, DB_Z_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.num_mip_levels = KYTY_PM4_GET(value, DB_Z_INFO, MAXMIP);
|
||||
r.tile_mode_index = KYTY_PM4_GET(value, DB_Z_INFO, TILE_MODE_INDEX);
|
||||
r.plane_compression = KYTY_PM4_GET(value, DB_Z_INFO, DECOMPRESS_ON_N_ZPLANES);
|
||||
r.expclear_enabled = KYTY_PM4_GET(value, DB_Z_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_surface_enable = KYTY_PM4_GET(value, DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
r.zrange_precision = KYTY_PM4_GET(value, DB_Z_INFO, ZRANGE_PRECISION);
|
||||
cp.GetCtx().SetDepthZInfo(r);
|
||||
cp.GetCtx().SetDepthZInfo(HW::DepthZInfo::Decode(value));
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_DEPTH_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
@@ -4025,15 +3700,7 @@ void GraphicsInitJmpTablesCxIndirect() {
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_STENCIL_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HW::DepthStencilInfo r;
|
||||
r.format = KYTY_PM4_GET(value, DB_STENCIL_INFO, FORMAT);
|
||||
r.texture_compatible_stencil = KYTY_PM4_GET(value, DB_STENCIL_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(value, DB_STENCIL_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.tile_split = KYTY_PM4_GET(value, DB_STENCIL_INFO, RESERVED_FIELD_1);
|
||||
r.tile_mode_index = KYTY_PM4_GET(value, DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
r.expclear_enabled = KYTY_PM4_GET(value, DB_STENCIL_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_stencil_disable = KYTY_PM4_GET(value, DB_STENCIL_INFO, TILE_STENCIL_DISABLE) != 0;
|
||||
cp.GetCtx().SetDepthStencilInfo(r);
|
||||
cp.GetCtx().SetDepthStencilInfo(HW::DepthStencilInfo::Decode(value));
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_Z_READ_BASE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
|
||||
@@ -142,6 +142,24 @@ enum class StencilFormat : uint32_t {
|
||||
k8UInt = 1,
|
||||
};
|
||||
|
||||
enum class TextureCompatiblePlaneCompression : uint32_t {
|
||||
kDisable = 0x00000000,
|
||||
kEnable = 0x02900800,
|
||||
kBitMask = 0x02900800,
|
||||
};
|
||||
|
||||
enum class TextureCompatibleStencil : uint32_t {
|
||||
kDisable = 0x00000000,
|
||||
kEnable = 0x00100800,
|
||||
kBitMask = 0x00100800,
|
||||
};
|
||||
|
||||
enum class ZCompareBase : uint32_t {
|
||||
kZMin = 0x00000000,
|
||||
kZMax = 0x80000000,
|
||||
kBitMask = 0x80000000,
|
||||
};
|
||||
|
||||
enum class TileMode : uint32_t {
|
||||
kLinear = 0x00,
|
||||
kStandard256B = 0x01,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,33 +3,59 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/uniqueFunction.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandProcessor;
|
||||
class GpuState;
|
||||
class RenderContext;
|
||||
|
||||
class GraphicsRunSubmissionLock final {
|
||||
class Gpu final {
|
||||
public:
|
||||
GraphicsRunSubmissionLock();
|
||||
~GraphicsRunSubmissionLock();
|
||||
KYTY_CLASS_NO_COPY(GraphicsRunSubmissionLock);
|
||||
explicit Gpu(RenderContext& renderer);
|
||||
~Gpu();
|
||||
KYTY_CLASS_NO_COPY(Gpu);
|
||||
|
||||
void Shutdown();
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command);
|
||||
|
||||
void Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
|
||||
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done = false);
|
||||
void SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
|
||||
bool trigger_agc_interrupt_on_done = false);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void Done();
|
||||
[[nodiscard]] int GetFrameNum() const;
|
||||
|
||||
[[nodiscard]] static bool IsCommandProcessorThread() noexcept;
|
||||
[[nodiscard]] static CommandProcessor* CurrentCommandProcessor() noexcept;
|
||||
[[nodiscard]] static bool SubmissionLockHeld() noexcept;
|
||||
[[nodiscard]] static bool MutexHeld() noexcept;
|
||||
|
||||
class SubmissionLock final {
|
||||
public:
|
||||
explicit SubmissionLock(Gpu& gpu);
|
||||
~SubmissionLock();
|
||||
KYTY_CLASS_NO_COPY(SubmissionLock);
|
||||
|
||||
private:
|
||||
Gpu& m_gpu;
|
||||
};
|
||||
|
||||
private:
|
||||
friend class SubmissionLock;
|
||||
|
||||
void PauseSubmissions();
|
||||
void ResumeSubmissions();
|
||||
|
||||
std::unique_ptr<GpuState> m_state;
|
||||
};
|
||||
|
||||
void GraphicsRunInit();
|
||||
|
||||
void GraphicsRunSubmit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
|
||||
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done = false);
|
||||
void GraphicsRunSubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
|
||||
bool trigger_agc_interrupt_on_done = false);
|
||||
void GraphicsRunSubmitFlipPreparation();
|
||||
void GraphicsRunWait();
|
||||
void GraphicsRunDone();
|
||||
int GraphicsRunGetFrameNum();
|
||||
[[nodiscard]] bool GraphicsRunIsCommandProcessorThread() noexcept;
|
||||
[[nodiscard]] CommandProcessor* GraphicsRunCurrentCommandProcessor() noexcept;
|
||||
void GraphicsRunFinishCommandProcessors();
|
||||
[[nodiscard]] bool GraphicsRunSubmissionLockHeld() noexcept;
|
||||
[[nodiscard]] bool GraphicsRunGpuLockHeld() noexcept;
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_ */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
|
||||
namespace Libs::Graphics::HW {
|
||||
|
||||
@@ -131,26 +132,66 @@ struct RenderTarget {
|
||||
};
|
||||
|
||||
struct DepthZInfo {
|
||||
uint32_t format = 0;
|
||||
uint32_t tile_mode_index = 0;
|
||||
uint32_t num_samples = 0;
|
||||
uint32_t zrange_precision = 0;
|
||||
bool tile_surface_enable = false;
|
||||
bool expclear_enabled = false;
|
||||
bool embedded_sample_locations = false;
|
||||
bool partially_resident = false;
|
||||
uint8_t num_mip_levels = 0;
|
||||
uint8_t plane_compression = 0;
|
||||
uint32_t format = 0;
|
||||
uint32_t num_samples = 0;
|
||||
Prospero::TextureCompatiblePlaneCompression texture_compatibility =
|
||||
Prospero::TextureCompatiblePlaneCompression::kDisable;
|
||||
Prospero::ZCompareBase z_compare_base = Prospero::ZCompareBase::kZMin;
|
||||
bool htile_acceleration = false;
|
||||
bool expclear_enabled = false;
|
||||
bool partially_resident = false;
|
||||
uint8_t max_mip_level = 0;
|
||||
|
||||
[[nodiscard]] static DepthZInfo Decode(uint32_t value) {
|
||||
DepthZInfo info;
|
||||
info.format = value & 0x3u;
|
||||
info.num_samples = (value >> 2u) & 0x3u;
|
||||
info.texture_compatibility = static_cast<Prospero::TextureCompatiblePlaneCompression>(
|
||||
value & Prospero::GpuEnumValue(Prospero::TextureCompatiblePlaneCompression::kBitMask));
|
||||
info.partially_resident = (value & 0x00001000u) != 0;
|
||||
info.max_mip_level = static_cast<uint8_t>((value >> 16u) & 0x0fu);
|
||||
info.expclear_enabled = (value & 0x08000000u) != 0;
|
||||
info.htile_acceleration = (value & 0x20000000u) != 0;
|
||||
info.z_compare_base = static_cast<Prospero::ZCompareBase>(
|
||||
value & Prospero::GpuEnumValue(Prospero::ZCompareBase::kBitMask));
|
||||
return info;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasValidTextureCompatibility() const {
|
||||
switch (texture_compatibility) {
|
||||
case Prospero::TextureCompatiblePlaneCompression::kDisable:
|
||||
case Prospero::TextureCompatiblePlaneCompression::kEnable: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DepthStencilInfo {
|
||||
uint32_t format = 0;
|
||||
uint32_t tile_mode_index = 0;
|
||||
uint32_t tile_split = 0;
|
||||
bool expclear_enabled = false;
|
||||
bool tile_stencil_disable = false;
|
||||
bool texture_compatible_stencil = false;
|
||||
bool partially_resident = false;
|
||||
uint32_t format = 0;
|
||||
Prospero::TextureCompatibleStencil texture_compatibility =
|
||||
Prospero::TextureCompatibleStencil::kDisable;
|
||||
bool expclear_enabled = false;
|
||||
bool htile_stencil_disabled = false;
|
||||
bool partially_resident = false;
|
||||
|
||||
[[nodiscard]] static DepthStencilInfo Decode(uint32_t value) {
|
||||
DepthStencilInfo info;
|
||||
info.format = value & 0x1u;
|
||||
info.texture_compatibility = static_cast<Prospero::TextureCompatibleStencil>(
|
||||
value & Prospero::GpuEnumValue(Prospero::TextureCompatibleStencil::kBitMask));
|
||||
info.partially_resident = (value & 0x00001000u) != 0;
|
||||
info.expclear_enabled = (value & 0x08000000u) != 0;
|
||||
info.htile_stencil_disabled = (value & 0x20000000u) != 0;
|
||||
return info;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasValidTextureCompatibility() const {
|
||||
switch (texture_compatibility) {
|
||||
case Prospero::TextureCompatibleStencil::kDisable:
|
||||
case Prospero::TextureCompatibleStencil::kEnable: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DepthRenderTargetDepthInfo {
|
||||
|
||||
@@ -406,8 +406,8 @@ struct TextureBlockLayout {
|
||||
|
||||
static bool GetTextureBlockLayout(uint32_t format, uint32_t tile, TextureBlockLayout& out) {
|
||||
uint32_t width_log2 = 0, height_log2 = 0;
|
||||
if (tile == 1 && Gen5Standard256BLayout(format, &out.bytes, &out.texel_width,
|
||||
&out.texel_height, &width_log2, &height_log2)) {
|
||||
if (tile == 1 && Gen5Standard256BLayout(format, &out.bytes, &out.texel_width, &out.texel_height,
|
||||
&width_log2, &height_log2)) {
|
||||
out.block_size = 256;
|
||||
} else if (tile == 5 && Gen5Standard4KBLayout(format, &out.bytes, &out.texel_width,
|
||||
&out.texel_height, &width_log2, &height_log2)) {
|
||||
@@ -524,7 +524,7 @@ static void SetMacroMipLayout(const TextureBlockLayout& block, uint32_t tile, ui
|
||||
}
|
||||
|
||||
bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t levels, uint32_t tile, TileVolumeLayout& out) {
|
||||
uint32_t levels, uint32_t tile, TileVolumeLayout& out) {
|
||||
if (width == 0 || height == 0 || depth == 0 || levels == 0 || levels > 16) {
|
||||
return false;
|
||||
}
|
||||
@@ -543,21 +543,26 @@ bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height
|
||||
TileBlockLayout block {};
|
||||
if (!TileGetBlockLayout(family, element.bytes, block)) return false;
|
||||
|
||||
out = {};
|
||||
out.family = family;
|
||||
out.bytes_per_element = element.bytes;
|
||||
out.texel_width = element.texel_width;
|
||||
out.texel_height = element.texel_height;
|
||||
out.block_depth = block.block_depth;
|
||||
out.first_tail_level = levels;
|
||||
out = {};
|
||||
out.family = family;
|
||||
out.bytes_per_element = element.bytes;
|
||||
out.texel_width = element.texel_width;
|
||||
out.texel_height = element.texel_height;
|
||||
out.block_depth = block.block_depth;
|
||||
out.first_tail_level = levels;
|
||||
const uint32_t width0 = (width + element.texel_width - 1u) / element.texel_width;
|
||||
const uint32_t height0 = (height + element.texel_height - 1u) / element.texel_height;
|
||||
const bool thick4 = family == TileBlockFamily::Standard4KB3D;
|
||||
const bool thick64 =
|
||||
family == TileBlockFamily::Standard64KB3D || family == TileBlockFamily::Prt64KB3D;
|
||||
const uint32_t max_tail = thick4 ? 5u : (thick64 ? 10u : 12u);
|
||||
uint32_t tail_width = thick4 ? block.block_width : block.block_width >> 1u;
|
||||
uint32_t tail_height = thick4 ? block.block_height >> 1u : block.block_height;
|
||||
uint32_t max_tail = 12u;
|
||||
if (thick4) {
|
||||
max_tail = 5u;
|
||||
} else if (thick64) {
|
||||
max_tail = 10u;
|
||||
}
|
||||
uint32_t tail_width = thick4 ? block.block_width : block.block_width >> 1u;
|
||||
uint32_t tail_height = thick4 ? block.block_height >> 1u : block.block_height;
|
||||
if (family == TileBlockFamily::Depth64KB && element.bytes < 4) {
|
||||
tail_width = 64;
|
||||
tail_height = 128;
|
||||
@@ -574,18 +579,23 @@ bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height
|
||||
}
|
||||
out.level_widths[level] = AlignUp(mip_width, block.block_width);
|
||||
out.level_heights[level] = AlignUp(mip_height, block.block_height);
|
||||
out.level_sizes[level] = static_cast<uint64_t>(block.block_depth) *
|
||||
out.level_widths[level] * out.level_heights[level] *
|
||||
element.bytes;
|
||||
out.level_sizes[level] = static_cast<uint64_t>(block.block_depth) *
|
||||
out.level_widths[level] * out.level_heights[level] * element.bytes;
|
||||
out.block_slice_size += out.level_sizes[level];
|
||||
}
|
||||
|
||||
const auto bytes_log2 = std::countr_zero(element.bytes);
|
||||
const auto bytes_log2 = std::countr_zero(element.bytes);
|
||||
const Gen5MipTailLocation* tail_locations = nullptr;
|
||||
if (thick4) {
|
||||
tail_locations = GEN5_MIP_TAIL_LOCATIONS_THICK_4KB[bytes_log2];
|
||||
} else if (thick64) {
|
||||
tail_locations = GEN5_MIP_TAIL_LOCATIONS_THICK_64KB[bytes_log2];
|
||||
} else {
|
||||
tail_locations = GEN5_MIP_TAIL_LOCATIONS_THIN_64KB[bytes_log2];
|
||||
}
|
||||
for (uint32_t level = out.first_tail_level; level < levels; ++level) {
|
||||
const auto index = level - out.first_tail_level;
|
||||
const auto tail = thick4 ? GEN5_MIP_TAIL_LOCATIONS_THICK_4KB[bytes_log2][index]
|
||||
: (thick64 ? GEN5_MIP_TAIL_LOCATIONS_THICK_64KB[bytes_log2][index]
|
||||
: GEN5_MIP_TAIL_LOCATIONS_THIN_64KB[bytes_log2][index]);
|
||||
const auto index = level - out.first_tail_level;
|
||||
const auto tail = tail_locations[index];
|
||||
out.level_sizes[level] = block.block_size;
|
||||
out.level_widths[level] = block.block_width;
|
||||
out.level_heights[level] = block.block_height;
|
||||
@@ -1047,7 +1057,7 @@ static constexpr uint32_t Depth64KB64XOffsetBytes(uint32_t x);
|
||||
static constexpr uint32_t Depth64KB64YOffsetBytes(uint32_t y);
|
||||
|
||||
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
|
||||
TileBlockLayout& layout) {
|
||||
TileBlockLayout& layout) {
|
||||
if (!std::has_single_bit(bytes_per_element) || bytes_per_element > 16) {
|
||||
return false;
|
||||
}
|
||||
@@ -1067,13 +1077,25 @@ bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
|
||||
};
|
||||
switch (family) {
|
||||
case TileBlockFamily::Standard256B:
|
||||
result.block_size = 256;
|
||||
result.block_width = bytes_per_element <= 2 ? 16 : (bytes_per_element <= 8 ? 8 : 4);
|
||||
result.block_size = 256;
|
||||
if (bytes_per_element <= 2) {
|
||||
result.block_width = 16;
|
||||
} else if (bytes_per_element <= 8) {
|
||||
result.block_width = 8;
|
||||
} else {
|
||||
result.block_width = 4;
|
||||
}
|
||||
result.block_height = result.block_size / (result.block_width * bytes_per_element);
|
||||
break;
|
||||
case TileBlockFamily::Standard4KB:
|
||||
result.block_size = 4096;
|
||||
result.block_width = bytes_per_element <= 2 ? 64 : (bytes_per_element <= 8 ? 32 : 16);
|
||||
result.block_size = 4096;
|
||||
if (bytes_per_element <= 2) {
|
||||
result.block_width = 64;
|
||||
} else if (bytes_per_element <= 8) {
|
||||
result.block_width = 32;
|
||||
} else {
|
||||
result.block_width = 16;
|
||||
}
|
||||
result.block_height = result.block_size / (result.block_width * bytes_per_element);
|
||||
break;
|
||||
case TileBlockFamily::Standard4KB3D: {
|
||||
@@ -1126,7 +1148,7 @@ bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
|
||||
}
|
||||
|
||||
bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t& byte_offset) {
|
||||
uint32_t& byte_offset) {
|
||||
TileBlockLayout expected {};
|
||||
if (!TileGetBlockLayout(layout.family, layout.bytes_per_element, expected) ||
|
||||
layout.block_size != expected.block_size || layout.block_width != expected.block_width ||
|
||||
@@ -1226,12 +1248,12 @@ bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, u
|
||||
}
|
||||
|
||||
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
|
||||
uint32_t& byte_offset) {
|
||||
uint32_t& byte_offset) {
|
||||
return TileGetBlockXor(layout, block_x, block_y, 0, byte_offset);
|
||||
}
|
||||
|
||||
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
|
||||
uint32_t block_z, uint32_t& byte_offset) {
|
||||
uint32_t block_z, uint32_t& byte_offset) {
|
||||
TileBlockLayout expected {};
|
||||
if (!TileGetBlockLayout(layout.family, layout.bytes_per_element, expected) ||
|
||||
layout.block_size != expected.block_size || layout.block_width != expected.block_width ||
|
||||
@@ -1377,8 +1399,8 @@ bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& htile_size
|
||||
}
|
||||
|
||||
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
|
||||
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
|
||||
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
|
||||
uint32_t num_fragments_log2) {
|
||||
EXIT_IF(pitch != 0);
|
||||
// Prospero derives uncompressed depth/stencil as independent 64 KiB block surfaces.
|
||||
@@ -1410,8 +1432,8 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
|
||||
if (depth_bytes_total <= UINT32_MAX && stencil_bytes_total <= UINT32_MAX && htile_valid) {
|
||||
depth_size = {static_cast<uint32_t>(depth_bytes_total), 65536};
|
||||
stencil_size = stencil_format == 1
|
||||
? TileSizeAlign {static_cast<uint32_t>(stencil_bytes_total), 65536}
|
||||
: TileSizeAlign {};
|
||||
? TileSizeAlign {static_cast<uint32_t>(stencil_bytes_total), 65536}
|
||||
: TileSizeAlign {};
|
||||
htile_size = calculated_htile;
|
||||
return true;
|
||||
}
|
||||
@@ -1441,7 +1463,7 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
|
||||
}
|
||||
|
||||
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t num_fragments_log2) {
|
||||
total_size = {};
|
||||
uint32_t block_width = 0;
|
||||
@@ -1466,7 +1488,7 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TilePaddedSize* padded_size) {
|
||||
total_size = {};
|
||||
if (width == 0 || height == 0 || levels == 0 || levels > 16 ||
|
||||
@@ -1578,7 +1600,7 @@ void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32
|
||||
|
||||
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture,
|
||||
TileSizeAlign& total_size) {
|
||||
TileSizeAlign& total_size) {
|
||||
EXIT_NOT_IMPLEMENTED(depth == 0);
|
||||
if (volume_texture) {
|
||||
TileVolumeLayout volume {};
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
#include "graphics/host_gpu/gpuTiler.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_depth_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_prt_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_prt_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_render_target_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard256_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard4_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard4_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t GROUP_SIZE = 64;
|
||||
constexpr uint32_t FAMILY_COUNT = static_cast<uint32_t>(TileBlockFamily::Count);
|
||||
constexpr uint32_t BYTES_PER_ELEMENT_COUNT = 5;
|
||||
constexpr uint32_t DIRECTION_COUNT = 2;
|
||||
constexpr uint32_t PIPELINE_COUNT = FAMILY_COUNT * BYTES_PER_ELEMENT_COUNT * DIRECTION_COUNT;
|
||||
static_assert(FAMILY_COUNT == 9);
|
||||
|
||||
struct Push {
|
||||
uint32_t src_base;
|
||||
uint32_t dst_base;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
uint32_t surface_z;
|
||||
uint32_t pitch_bytes;
|
||||
uint32_t slice_bytes;
|
||||
uint32_t blocks_per_row;
|
||||
uint32_t blocks_per_slice;
|
||||
uint32_t tail_x;
|
||||
uint32_t tail_y;
|
||||
uint32_t tail;
|
||||
uint32_t first;
|
||||
uint32_t count;
|
||||
};
|
||||
static_assert(sizeof(Push) == 60);
|
||||
|
||||
struct Shader {
|
||||
const uint32_t* code;
|
||||
size_t words;
|
||||
};
|
||||
|
||||
constexpr std::array<Shader, FAMILY_COUNT> SHADERS {{
|
||||
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
|
||||
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
|
||||
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
|
||||
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
|
||||
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
|
||||
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
|
||||
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
|
||||
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
|
||||
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
|
||||
}};
|
||||
|
||||
struct Dispatch {
|
||||
Push push {};
|
||||
uint32_t pipeline_slot = 0;
|
||||
uint32_t elements = 0;
|
||||
};
|
||||
|
||||
struct Resources {
|
||||
vk::DescriptorSetLayout descriptor_layout = nullptr;
|
||||
vk::PipelineLayout pipeline_layout = nullptr;
|
||||
vk::DescriptorPool descriptor_pool = nullptr;
|
||||
vk::DescriptorSet descriptor_set = nullptr;
|
||||
std::array<vk::Pipeline, PIPELINE_COUNT> pipelines {};
|
||||
VulkanBuffer staging;
|
||||
VulkanBuffer linear;
|
||||
void* mapped = nullptr;
|
||||
};
|
||||
|
||||
bool CheckedAdd(uint64_t a, uint64_t b, uint64_t& result) {
|
||||
return b <= UINT64_MAX - a && (result = a + b, true);
|
||||
}
|
||||
|
||||
bool CheckedMultiply(uint64_t a, uint64_t b, uint64_t& result) {
|
||||
return (a == 0 || b <= UINT64_MAX / a) && (result = a * b, true);
|
||||
}
|
||||
|
||||
bool CheckedAddProduct(uint64_t& value, uint64_t count, uint64_t stride) {
|
||||
uint64_t bytes = 0;
|
||||
return CheckedMultiply(count, stride, bytes) && CheckedAdd(value, bytes, value);
|
||||
}
|
||||
|
||||
bool IsRangeValid(uint64_t offset, uint64_t size, uint64_t capacity) {
|
||||
return size != 0 && offset <= capacity && size <= capacity - offset;
|
||||
}
|
||||
|
||||
uint64_t AlignToDword(uint64_t value) {
|
||||
return (value + 3u) & ~uint64_t {3};
|
||||
}
|
||||
|
||||
uint32_t GetPipelineSlot(bool to_tiled, TileBlockFamily family, uint32_t bytes_per_element) {
|
||||
const uint32_t direction_index = to_tiled ? 1u : 0u;
|
||||
const uint32_t family_index = static_cast<uint32_t>(family);
|
||||
const uint32_t element_size_index = std::countr_zero(bytes_per_element);
|
||||
return (direction_index * FAMILY_COUNT + family_index) * BYTES_PER_ELEMENT_COUNT +
|
||||
element_size_index;
|
||||
}
|
||||
|
||||
void Barrier(vk::CommandBuffer command, vk::Buffer buffer, vk::AccessFlags src_access,
|
||||
vk::AccessFlags dst_access, vk::PipelineStageFlags src_stage,
|
||||
vk::PipelineStageFlags dst_stage) {
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barrier.srcAccessMask = src_access;
|
||||
barrier.dstAccessMask = dst_access;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = buffer;
|
||||
barrier.size = VK_WHOLE_SIZE;
|
||||
command.pipelineBarrier(src_stage, dst_stage, {}, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
class TileCompute final {
|
||||
public:
|
||||
explicit TileCompute(GraphicContext& graphics): graphics(graphics) {}
|
||||
void Run(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
const GpuTileRecord& record);
|
||||
void Release();
|
||||
|
||||
private:
|
||||
void Prepare(bool to_tiled, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, std::vector<Dispatch>& dispatches) const;
|
||||
void Init();
|
||||
void CreatePipelines(std::span<const Dispatch> dispatches);
|
||||
void CreatePipeline(uint32_t pipeline_slot);
|
||||
void Resize(uint64_t staging_size, uint64_t linear_size);
|
||||
void CreateBuffer(uint64_t size, bool mapped, VulkanBuffer& buffer, void** data) const;
|
||||
void Execute(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const Dispatch> dispatches,
|
||||
const GpuTileRecord& record);
|
||||
void Destroy(Resources& target) const;
|
||||
|
||||
GraphicContext& graphics;
|
||||
Resources resources;
|
||||
};
|
||||
|
||||
Common::Mutex g_tiler_mutex;
|
||||
std::unique_ptr<TileCompute> g_tiler;
|
||||
|
||||
void TileCompute::Prepare(bool to_tiled, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos,
|
||||
std::vector<Dispatch>& dispatches) const {
|
||||
EXIT_IF(infos.empty() || tiled_capacity == 0 || linear_capacity == 0);
|
||||
const auto& limits = graphics.GetPhysicalDeviceProperties().limits;
|
||||
EXIT_NOT_IMPLEMENTED(tiled_capacity > UINT32_MAX || linear_capacity > UINT32_MAX ||
|
||||
AlignToDword(tiled_capacity) > limits.maxStorageBufferRange ||
|
||||
AlignToDword(linear_capacity) > limits.maxStorageBufferRange);
|
||||
|
||||
dispatches.clear();
|
||||
dispatches.reserve(infos.size());
|
||||
for (const auto& info: infos) {
|
||||
TileBlockLayout block {};
|
||||
const uint32_t tiled_width = info.tiled_width != 0 ? info.tiled_width : info.pitch;
|
||||
const uint32_t tiled_height = info.tiled_height != 0 ? info.tiled_height : info.height;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetBlockLayout(info.family, info.bytes_per_element, block) || info.width == 0 ||
|
||||
info.height == 0 || info.depth == 0 || info.pitch < info.width ||
|
||||
(!info.tail && (tiled_width < info.width || tiled_height < info.height)) ||
|
||||
!IsRangeValid(info.linear_offset, info.linear_size, linear_capacity) ||
|
||||
!IsRangeValid(info.tiled_offset, info.tiled_size, tiled_capacity) ||
|
||||
(block.block_depth == 1 && info.depth != 1));
|
||||
|
||||
uint64_t elements = 0, pitch_bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(info.width, info.height, elements) ||
|
||||
!CheckedMultiply(elements, info.depth, elements) ||
|
||||
!CheckedMultiply(info.pitch, info.bytes_per_element, pitch_bytes) ||
|
||||
elements > UINT32_MAX || pitch_bytes > UINT32_MAX);
|
||||
uint64_t slice_bytes = info.linear_slice_stride;
|
||||
EXIT_NOT_IMPLEMENTED(slice_bytes == 0 &&
|
||||
!CheckedMultiply(pitch_bytes, info.height, slice_bytes));
|
||||
uint64_t linear_used = 0, minimum_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(pitch_bytes, info.height, minimum_slice) ||
|
||||
(info.depth > 1 && slice_bytes < minimum_slice) ||
|
||||
!CheckedAddProduct(linear_used, info.depth - 1u, slice_bytes) ||
|
||||
!CheckedAddProduct(linear_used, info.height - 1u, pitch_bytes) ||
|
||||
!CheckedAddProduct(linear_used, info.width, info.bytes_per_element) ||
|
||||
linear_used > info.linear_size || slice_bytes > UINT32_MAX);
|
||||
|
||||
const uint64_t columns =
|
||||
(static_cast<uint64_t>(tiled_width) + block.block_width - 1u) / block.block_width;
|
||||
const uint64_t rows =
|
||||
(static_cast<uint64_t>(tiled_height) + block.block_height - 1u) / block.block_height;
|
||||
uint64_t blocks_per_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(columns, rows, blocks_per_slice) ||
|
||||
columns > UINT32_MAX || blocks_per_slice > UINT32_MAX ||
|
||||
rows * block.block_height > UINT32_MAX);
|
||||
if (info.tail) {
|
||||
const bool supported = info.family != TileBlockFamily::Standard256B;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!supported || info.depth > block.block_depth || info.tail_x >= block.block_width ||
|
||||
info.width > block.block_width - info.tail_x || info.tail_y >= block.block_height ||
|
||||
info.height > block.block_height - info.tail_y ||
|
||||
info.tiled_size < block.block_size);
|
||||
} else {
|
||||
const uint64_t slices =
|
||||
(static_cast<uint64_t>(info.depth) + block.block_depth - 1u) / block.block_depth;
|
||||
uint64_t tiled_used = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(blocks_per_slice, slices, tiled_used) ||
|
||||
!CheckedMultiply(tiled_used, block.block_size, tiled_used) ||
|
||||
tiled_used > info.tiled_size);
|
||||
}
|
||||
const uint32_t alignment = std::min(info.bytes_per_element, 4u);
|
||||
EXIT_NOT_IMPLEMENTED(((info.linear_offset | info.tiled_offset | pitch_bytes | slice_bytes) &
|
||||
(alignment - 1u)) != 0);
|
||||
|
||||
Dispatch dispatch {};
|
||||
dispatch.elements = static_cast<uint32_t>(elements);
|
||||
dispatch.pipeline_slot = GetPipelineSlot(to_tiled, info.family, info.bytes_per_element);
|
||||
dispatch.push.src_base =
|
||||
static_cast<uint32_t>(to_tiled ? info.linear_offset : info.tiled_offset);
|
||||
dispatch.push.dst_base =
|
||||
static_cast<uint32_t>(to_tiled ? info.tiled_offset : info.linear_offset);
|
||||
dispatch.push.width = info.width;
|
||||
dispatch.push.height = info.height;
|
||||
dispatch.push.depth = info.depth;
|
||||
dispatch.push.surface_z = info.surface_z;
|
||||
dispatch.push.pitch_bytes = static_cast<uint32_t>(pitch_bytes);
|
||||
dispatch.push.slice_bytes = static_cast<uint32_t>(slice_bytes);
|
||||
dispatch.push.blocks_per_row = static_cast<uint32_t>(columns);
|
||||
dispatch.push.blocks_per_slice = static_cast<uint32_t>(blocks_per_slice);
|
||||
dispatch.push.tail_x = info.tail_x;
|
||||
dispatch.push.tail_y = info.tail_y;
|
||||
dispatch.push.tail = info.tail;
|
||||
dispatches.push_back(dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
void TileCompute::Destroy(Resources& target) const {
|
||||
if (target.mapped != nullptr) {
|
||||
graphics.UnmapMemory(target.staging.memory);
|
||||
}
|
||||
if (target.staging.buffer != nullptr) {
|
||||
graphics.DeleteBuffer(target.staging);
|
||||
}
|
||||
if (target.linear.buffer != nullptr) {
|
||||
graphics.DeleteBuffer(target.linear);
|
||||
}
|
||||
for (auto pipeline: target.pipelines) {
|
||||
if (pipeline != nullptr) {
|
||||
graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (target.descriptor_pool != nullptr) {
|
||||
graphics.device.destroyDescriptorPool(target.descriptor_pool, nullptr);
|
||||
}
|
||||
if (target.pipeline_layout != nullptr) {
|
||||
graphics.device.destroyPipelineLayout(target.pipeline_layout, nullptr);
|
||||
}
|
||||
if (target.descriptor_layout != nullptr) {
|
||||
graphics.device.destroyDescriptorSetLayout(target.descriptor_layout, nullptr);
|
||||
}
|
||||
target = {};
|
||||
}
|
||||
|
||||
void TileCompute::Init() {
|
||||
if (resources.pipeline_layout != nullptr) {
|
||||
return;
|
||||
}
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> bindings {};
|
||||
for (uint32_t i = 0; i < bindings.size(); i++) {
|
||||
bindings[i] = {i, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute,
|
||||
nullptr};
|
||||
}
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
descriptor_info.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
descriptor_info.pBindings = bindings.data();
|
||||
RequireVulkanSuccess(graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
|
||||
&resources.descriptor_layout),
|
||||
"create GPU tiler descriptor layout");
|
||||
|
||||
vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0, sizeof(Push)};
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &resources.descriptor_layout;
|
||||
layout_info.pushConstantRangeCount = 1;
|
||||
layout_info.pPushConstantRanges = &push_range;
|
||||
RequireVulkanSuccess(
|
||||
graphics.device.createPipelineLayout(&layout_info, nullptr, &resources.pipeline_layout),
|
||||
"create GPU tiler pipeline layout");
|
||||
vk::DescriptorPoolSize pool_size {vk::DescriptorType::eStorageBuffer, 2};
|
||||
vk::DescriptorPoolCreateInfo pool_info {};
|
||||
pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
|
||||
pool_info.maxSets = 1;
|
||||
pool_info.poolSizeCount = 1;
|
||||
pool_info.pPoolSizes = &pool_size;
|
||||
RequireVulkanSuccess(
|
||||
graphics.device.createDescriptorPool(&pool_info, nullptr, &resources.descriptor_pool),
|
||||
"create GPU tiler descriptor pool");
|
||||
vk::DescriptorSetAllocateInfo set_info {};
|
||||
set_info.sType = vk::StructureType::eDescriptorSetAllocateInfo;
|
||||
set_info.descriptorPool = resources.descriptor_pool;
|
||||
set_info.descriptorSetCount = 1;
|
||||
set_info.pSetLayouts = &resources.descriptor_layout;
|
||||
RequireVulkanSuccess(
|
||||
graphics.device.allocateDescriptorSets(&set_info, &resources.descriptor_set),
|
||||
"allocate GPU tiler descriptor set");
|
||||
}
|
||||
|
||||
void TileCompute::CreatePipeline(uint32_t pipeline_slot) {
|
||||
const uint32_t element_size_index = pipeline_slot % BYTES_PER_ELEMENT_COUNT;
|
||||
const uint32_t family_direction_index = pipeline_slot / BYTES_PER_ELEMENT_COUNT;
|
||||
const uint32_t family_index = family_direction_index % FAMILY_COUNT;
|
||||
const uint32_t direction_index = family_direction_index / FAMILY_COUNT;
|
||||
const uint32_t specialization_values[] {1u << element_size_index, direction_index};
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
vk::SpecializationInfo specialization {2, entries, sizeof(specialization_values),
|
||||
specialization_values};
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = SHADERS[family_index].words * sizeof(uint32_t);
|
||||
module_info.pCode = SHADERS[family_index].code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create GPU tiler shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo info {};
|
||||
info.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
info.stage = stage;
|
||||
info.layout = resources.pipeline_layout;
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
const auto result =
|
||||
graphics.device.createComputePipelines(nullptr, 1, &info, nullptr, &pipeline);
|
||||
graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create GPU tiler pipeline");
|
||||
resources.pipelines[pipeline_slot] = pipeline;
|
||||
}
|
||||
|
||||
void TileCompute::CreatePipelines(std::span<const Dispatch> dispatches) {
|
||||
for (const auto& dispatch: dispatches) {
|
||||
if (resources.pipelines[dispatch.pipeline_slot] == nullptr) {
|
||||
CreatePipeline(dispatch.pipeline_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TileCompute::CreateBuffer(uint64_t size, bool mapped, VulkanBuffer& buffer,
|
||||
void** data) const {
|
||||
buffer.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc |
|
||||
vk::BufferUsageFlagBits::eTransferDst;
|
||||
buffer.memory.property =
|
||||
mapped
|
||||
? vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
||||
: vk::MemoryPropertyFlags(vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||
graphics.CreateBuffer(size, buffer);
|
||||
if (mapped) graphics.MapMemory(buffer.memory, *data);
|
||||
}
|
||||
|
||||
void TileCompute::Resize(uint64_t staging_size, uint64_t linear_size) {
|
||||
if (resources.staging.buffer_size >= staging_size &&
|
||||
resources.linear.buffer_size >= linear_size) {
|
||||
return;
|
||||
}
|
||||
staging_size = std::max(staging_size, resources.staging.buffer_size);
|
||||
linear_size = std::max(linear_size, resources.linear.buffer_size);
|
||||
VulkanBuffer staging {}, linear {};
|
||||
void* mapped = nullptr;
|
||||
CreateBuffer(staging_size, true, staging, &mapped);
|
||||
CreateBuffer(linear_size, false, linear, nullptr);
|
||||
if (resources.mapped != nullptr) graphics.UnmapMemory(resources.staging.memory);
|
||||
if (resources.staging.buffer != nullptr) graphics.DeleteBuffer(resources.staging);
|
||||
if (resources.linear.buffer != nullptr) graphics.DeleteBuffer(resources.linear);
|
||||
resources.staging = staging;
|
||||
resources.linear = linear;
|
||||
resources.mapped = mapped;
|
||||
}
|
||||
|
||||
void TileCompute::Execute(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const Dispatch> dispatches,
|
||||
const GpuTileRecord& record) {
|
||||
const uint64_t tiled_size = AlignToDword(tiled_capacity);
|
||||
const uint64_t linear_size = AlignToDword(linear_capacity);
|
||||
const uint64_t input_size = to_tiled ? linear_capacity : tiled_capacity;
|
||||
if (input != nullptr) {
|
||||
std::memcpy(resources.mapped, input, static_cast<size_t>(input_size));
|
||||
std::memset(static_cast<uint8_t*>(resources.mapped) + input_size, 0,
|
||||
static_cast<size_t>(AlignToDword(input_size) - input_size));
|
||||
}
|
||||
|
||||
std::array<vk::DescriptorBufferInfo, 2> buffer_info {{
|
||||
{to_tiled ? resources.linear.buffer : resources.staging.buffer, 0,
|
||||
to_tiled ? linear_size : tiled_size},
|
||||
{to_tiled ? resources.staging.buffer : resources.linear.buffer, 0,
|
||||
to_tiled ? tiled_size : linear_size},
|
||||
}};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t i = 0; i < writes.size(); i++) {
|
||||
writes[i].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[i].dstSet = resources.descriptor_set;
|
||||
writes[i].dstBinding = i;
|
||||
writes[i].descriptorCount = 1;
|
||||
writes[i].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[i].pBufferInfo = &buffer_info[i];
|
||||
}
|
||||
graphics.device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0,
|
||||
nullptr);
|
||||
|
||||
CommandBuffer command;
|
||||
command.Begin();
|
||||
auto vk_command = command.Handle();
|
||||
if (input != nullptr) {
|
||||
Barrier(vk_command, resources.staging.buffer, vk::AccessFlagBits::eHostWrite,
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead,
|
||||
vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer);
|
||||
}
|
||||
if (to_tiled && input != nullptr) {
|
||||
const vk::BufferCopy copy {0, 0, linear_size};
|
||||
vk_command.copyBuffer(resources.staging.buffer, resources.linear.buffer, 1, ©);
|
||||
Barrier(vk_command, resources.linear.buffer, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eComputeShader);
|
||||
Barrier(vk_command, resources.staging.buffer, vk::AccessFlagBits::eTransferRead,
|
||||
vk::AccessFlagBits::eTransferWrite, vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eTransfer);
|
||||
}
|
||||
if (to_tiled && record) {
|
||||
record(command, resources.linear);
|
||||
Barrier(vk_command, resources.linear.buffer,
|
||||
vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eComputeShader);
|
||||
}
|
||||
|
||||
const auto output_buffer = to_tiled ? resources.staging.buffer : resources.linear.buffer;
|
||||
const auto output_size = to_tiled ? tiled_size : linear_size;
|
||||
vk_command.fillBuffer(output_buffer, 0, output_size, 0);
|
||||
Barrier(vk_command, output_buffer, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
vk_command.bindDescriptorSets(vk::PipelineBindPoint::eCompute, resources.pipeline_layout, 0, 1,
|
||||
&resources.descriptor_set, 0, nullptr);
|
||||
const uint64_t limit =
|
||||
static_cast<uint64_t>(
|
||||
graphics.GetPhysicalDeviceProperties().limits.maxComputeWorkGroupCount[0]) *
|
||||
GROUP_SIZE;
|
||||
for (const auto& dispatch: dispatches) {
|
||||
vk_command.bindPipeline(vk::PipelineBindPoint::eCompute,
|
||||
resources.pipelines[dispatch.pipeline_slot]);
|
||||
for (uint32_t first = 0; first < dispatch.elements;) {
|
||||
auto push = dispatch.push;
|
||||
push.first = first;
|
||||
push.count =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(dispatch.elements - first, limit));
|
||||
vk_command.pushConstants(resources.pipeline_layout, vk::ShaderStageFlagBits::eCompute,
|
||||
0, sizeof(push), &push);
|
||||
vk_command.dispatch((push.count - 1u) / GROUP_SIZE + 1u, 1, 1);
|
||||
first += push.count;
|
||||
}
|
||||
}
|
||||
Barrier(vk_command, output_buffer, vk::AccessFlagBits::eShaderWrite,
|
||||
vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eHostRead,
|
||||
vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost);
|
||||
if (!to_tiled && record) {
|
||||
record(command, resources.linear);
|
||||
}
|
||||
if (!to_tiled && output != nullptr) {
|
||||
const vk::BufferCopy copy {0, 0, linear_size};
|
||||
vk_command.copyBuffer(resources.linear.buffer, resources.staging.buffer, 1, ©);
|
||||
Barrier(vk_command, resources.staging.buffer, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eHostRead, vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eHost);
|
||||
}
|
||||
command.End();
|
||||
command.Execute();
|
||||
command.WaitForFence();
|
||||
if (output != nullptr) {
|
||||
std::memcpy(output, resources.mapped,
|
||||
static_cast<size_t>(to_tiled ? tiled_capacity : linear_capacity));
|
||||
}
|
||||
}
|
||||
|
||||
void TileCompute::Run(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
const GpuTileRecord& record) {
|
||||
EXIT_IF((to_tiled && (output == nullptr || (input == nullptr && !record))) ||
|
||||
(!to_tiled && (input == nullptr || (output == nullptr && !record))));
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(to_tiled, tiled_capacity, linear_capacity, infos, dispatches);
|
||||
Init();
|
||||
CreatePipelines(dispatches);
|
||||
const uint64_t staging_size =
|
||||
std::max(AlignToDword(tiled_capacity), AlignToDword(linear_capacity));
|
||||
const uint64_t linear_size = AlignToDword(linear_capacity);
|
||||
Resize(staging_size, linear_size);
|
||||
Execute(to_tiled, input, output, tiled_capacity, linear_capacity, dispatches, record);
|
||||
}
|
||||
|
||||
void TileCompute::Release() {
|
||||
Destroy(resources);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void GpuDetile(const void* tiled, void* linear, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, const GpuTileRecord& after) {
|
||||
Common::LockGuard lock(g_tiler_mutex);
|
||||
if (!g_tiler) {
|
||||
g_tiler = std::make_unique<TileCompute>(GetRenderContext().GetGraphics());
|
||||
}
|
||||
g_tiler->Run(false, tiled, linear, tiled_capacity, linear_capacity, infos, after);
|
||||
}
|
||||
|
||||
void GpuTile(const void* linear, void* tiled, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, const GpuTileRecord& before) {
|
||||
Common::LockGuard lock(g_tiler_mutex);
|
||||
if (!g_tiler) {
|
||||
g_tiler = std::make_unique<TileCompute>(GetRenderContext().GetGraphics());
|
||||
}
|
||||
g_tiler->Run(true, linear, tiled, tiled_capacity, linear_capacity, infos, before);
|
||||
}
|
||||
|
||||
void GpuTileRelease() {
|
||||
Common::LockGuard lock(g_tiler_mutex);
|
||||
if (g_tiler) {
|
||||
g_tiler->Release();
|
||||
g_tiler.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
struct VulkanBuffer;
|
||||
class CommandBuffer;
|
||||
|
||||
struct GpuTileInfo {
|
||||
TileBlockFamily family = TileBlockFamily::Count;
|
||||
uint32_t bytes_per_element = 0;
|
||||
uint64_t linear_offset = 0;
|
||||
uint64_t linear_size = 0;
|
||||
uint64_t tiled_offset = 0;
|
||||
uint64_t tiled_size = 0;
|
||||
uint64_t linear_slice_stride = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 1;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t tail_x = 0;
|
||||
uint32_t tail_y = 0;
|
||||
bool tail = false;
|
||||
uint32_t tiled_width = 0;
|
||||
uint32_t tiled_height = 0;
|
||||
uint32_t surface_z = 0;
|
||||
};
|
||||
|
||||
using GpuTileRecord = std::function<void(CommandBuffer&, VulkanBuffer&)>;
|
||||
|
||||
void GpuDetile(const void* tiled, void* linear, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
const GpuTileRecord& after = {});
|
||||
void GpuTile(const void* linear, void* tiled, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
const GpuTileRecord& before = {});
|
||||
void GpuTileRelease();
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export
|
||||
#include "graphics/host_gpu/vulkanInstance.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
@@ -18,32 +16,20 @@ struct VulkanBuffer;
|
||||
struct VulkanImage;
|
||||
struct VulkanMemory;
|
||||
|
||||
struct VulkanSwapchain {
|
||||
~VulkanSwapchain();
|
||||
|
||||
vk::SwapchainKHR swapchain = nullptr;
|
||||
vk::Format swapchain_format = vk::Format::eUndefined;
|
||||
vk::Extent2D swapchain_extent = {};
|
||||
std::unique_ptr<vk::Image[]> swapchain_images;
|
||||
std::unique_ptr<vk::ImageView[]> swapchain_image_views;
|
||||
uint32_t swapchain_images_count = 0;
|
||||
std::unique_ptr<vk::Semaphore[]> image_acquired_semaphores;
|
||||
std::unique_ptr<vk::Semaphore[]> render_complete_semaphores;
|
||||
uint32_t current_index = 0;
|
||||
uint32_t present_frame = 0;
|
||||
};
|
||||
|
||||
struct GraphicContext: public VulkanInstance {
|
||||
[[nodiscard]] bool CreateAllocator();
|
||||
void DestroyAllocator();
|
||||
void LogMemoryBudget() const;
|
||||
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
|
||||
void DeleteBuffer(VulkanBuffer& buffer);
|
||||
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
|
||||
void DeleteImage(VulkanImage& image);
|
||||
void MapMemory(VulkanMemory& memory, void*& data);
|
||||
void UnmapMemory(VulkanMemory& memory);
|
||||
void AppendHardwareRayTracingDeviceExtensions(
|
||||
[[nodiscard]] bool CanReportMemoryUsage() const noexcept { return memory_budget_ext_enabled; }
|
||||
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
|
||||
[[nodiscard]] uint64_t GetTotalMemoryBudget() const;
|
||||
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
|
||||
void DeleteBuffer(VulkanBuffer& buffer);
|
||||
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
|
||||
void DeleteImage(VulkanImage& image);
|
||||
void MapMemory(VulkanMemory& memory, void*& data);
|
||||
void UnmapMemory(VulkanMemory& memory);
|
||||
void AppendHardwareRayTracingDeviceExtensions(
|
||||
const std::vector<vk::ExtensionProperties>& available_extensions,
|
||||
std::vector<const char*>& device_extensions);
|
||||
void LoadHardwareRayTracingFunctions() const;
|
||||
@@ -64,92 +50,29 @@ struct VulkanMemory {
|
||||
uint64_t unique_id = 0;
|
||||
};
|
||||
|
||||
enum class VulkanImageType {
|
||||
Unknown,
|
||||
VideoOut,
|
||||
DepthStencil,
|
||||
Texture,
|
||||
StorageTexture,
|
||||
RenderTexture
|
||||
};
|
||||
|
||||
struct ImageViewInfo {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::ImageViewType type = vk::ImageViewType::e2D;
|
||||
vk::ImageAspectFlags aspect = {};
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 0;
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
uint32_t swizzle = 0;
|
||||
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
|
||||
|
||||
bool operator==(const ImageViewInfo&) const = default;
|
||||
};
|
||||
|
||||
struct CachedImageView {
|
||||
ImageViewInfo info;
|
||||
vk::ImageView view = nullptr;
|
||||
};
|
||||
|
||||
struct ImageViewCache {
|
||||
std::mutex mutex;
|
||||
std::vector<CachedImageView> views;
|
||||
|
||||
ImageViewCache() = default;
|
||||
KYTY_CLASS_NO_COPY(ImageViewCache);
|
||||
struct VulkanImageState {
|
||||
vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
};
|
||||
|
||||
struct VulkanImage {
|
||||
static constexpr int VIEW_MAX = 4;
|
||||
static constexpr int VIEW_DEFAULT = 0;
|
||||
static constexpr int VIEW_DEFAULT_ARRAY = 1;
|
||||
static constexpr int VIEW_STORAGE = 2;
|
||||
static constexpr int VIEW_STORAGE_ARRAY = 3;
|
||||
|
||||
explicit VulkanImage(VulkanImageType type): type(type) {}
|
||||
VulkanImage() = default;
|
||||
KYTY_CLASS_NO_COPY(VulkanImage);
|
||||
|
||||
VulkanImageType type = VulkanImageType::Unknown;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::Extent2D extent = {};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
uint32_t samples = 1;
|
||||
vk::Image image = nullptr;
|
||||
vk::ImageView image_view[VIEW_MAX] = {};
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::ImageType image_type = vk::ImageType::e2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
uint32_t samples = 1;
|
||||
vk::ImageUsageFlags usage = {};
|
||||
vk::ImageCreateFlags flags = {};
|
||||
vk::Image image = nullptr;
|
||||
VulkanImageState state;
|
||||
std::vector<VulkanImageState> subresource_states;
|
||||
Graphics::VulkanMemory memory;
|
||||
ImageViewCache view_cache;
|
||||
};
|
||||
|
||||
struct VideoOutVulkanImage: public VulkanImage {
|
||||
VideoOutVulkanImage(): VulkanImage(VulkanImageType::VideoOut) {}
|
||||
};
|
||||
|
||||
struct DepthStencilVulkanImage: public VulkanImage {
|
||||
DepthStencilVulkanImage(): VulkanImage(VulkanImageType::DepthStencil) {}
|
||||
bool compressed = false;
|
||||
bool initial_depth_clear_pending = false;
|
||||
bool initial_stencil_clear_pending = false;
|
||||
};
|
||||
|
||||
struct GpuTextureVulkanImage: public VulkanImage {
|
||||
explicit GpuTextureVulkanImage(VulkanImageType type): VulkanImage(type) {}
|
||||
};
|
||||
|
||||
struct TextureVulkanImage: public GpuTextureVulkanImage {
|
||||
TextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::Texture) {}
|
||||
};
|
||||
|
||||
struct StorageTextureVulkanImage: public GpuTextureVulkanImage {
|
||||
StorageTextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::StorageTexture) {}
|
||||
};
|
||||
|
||||
struct RenderTextureVulkanImage: public VulkanImage {
|
||||
RenderTextureVulkanImage(): VulkanImage(VulkanImageType::RenderTexture) {}
|
||||
bool initial_clear_pending = false;
|
||||
};
|
||||
|
||||
struct VulkanBuffer {
|
||||
@@ -159,8 +82,6 @@ struct VulkanBuffer {
|
||||
uint64_t buffer_size = 0;
|
||||
};
|
||||
|
||||
struct StorageVulkanBuffer: public VulkanBuffer {};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_ */
|
||||
|
||||
@@ -31,6 +31,44 @@ MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_
|
||||
|
||||
MemoryTracker::~MemoryTracker() = default;
|
||||
|
||||
void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept {
|
||||
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr ||
|
||||
(vaddr & (TRACKER_PAGE_SIZE - 1)) != 0 || (size & (TRACKER_PAGE_SIZE - 1)) != 0) {
|
||||
EXIT("MemoryTracker: invalid dirty-page validation range\n");
|
||||
}
|
||||
for (auto page = vaddr; page < vaddr + size; page += TRACKER_PAGE_SIZE) {
|
||||
bool found = false;
|
||||
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
|
||||
[&found](RangeSet::Range) { found = true; });
|
||||
if (!found) {
|
||||
EXIT("MemoryTracker: GPU-dirty tracker page has no dirty bytes, operation=%s "
|
||||
"addr=0x%016" PRIx64 "\n",
|
||||
operation, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) {
|
||||
ValidateRange(vaddr, size);
|
||||
if (vaddr + size > UINT64_MAX - (TRACKER_PAGE_SIZE - 1)) {
|
||||
EXIT("MemoryTracker: dirty ownership range alignment overflow\n");
|
||||
}
|
||||
const auto begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) {
|
||||
bool has_dirty_bytes = false;
|
||||
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
|
||||
[&has_dirty_bytes](RangeSet::Range) { has_dirty_bytes = true; });
|
||||
if (IsRegionGpuModified(page, TRACKER_PAGE_SIZE) != has_dirty_bytes) {
|
||||
EXIT("MemoryTracker: tracker and byte ownership disagree, operation=%s "
|
||||
"addr=0x%016" PRIx64 "\n",
|
||||
operation, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/regionManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -38,6 +40,10 @@ public:
|
||||
PageFaultPhase phase) noexcept;
|
||||
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept;
|
||||
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation);
|
||||
|
||||
template <bool clear, typename Preflight, typename Func>
|
||||
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
|
||||
@@ -99,7 +105,7 @@ public:
|
||||
std::unique_lock access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
|
||||
s_upload_owner = this;
|
||||
const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->lock.lock();
|
||||
manager->Track(manager->GetCpuAddr() + offset, bytes);
|
||||
@@ -119,15 +125,15 @@ public:
|
||||
manager->lock.unlock();
|
||||
});
|
||||
}
|
||||
s_upload_owner = nullptr;
|
||||
s_upload_owner = previous_upload_owner;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t REGION_COUNT = TRACKER_ADDRESS_SIZE / TRACKER_REGION_SIZE;
|
||||
inline static thread_local const MemoryTracker* s_upload_owner = nullptr;
|
||||
|
||||
static void CheckNotInUploadCallback() noexcept {
|
||||
if (s_upload_owner != nullptr) {
|
||||
void CheckNotInUploadCallback() const noexcept {
|
||||
if (s_upload_owner == this) {
|
||||
EXIT("memory tracker re-entered from upload callback\n");
|
||||
}
|
||||
}
|
||||
@@ -167,7 +173,9 @@ private:
|
||||
void RequireMapped(uint64_t vaddr, uint64_t size) const {
|
||||
ValidateRange(vaddr, size);
|
||||
if (!m_page_manager.IsMapped(vaddr, size)) {
|
||||
EXIT("memory tracker range is not mapped\n");
|
||||
EXIT("memory tracker range [0x%llx, 0x%llx) is not mapped\n",
|
||||
static_cast<unsigned long long>(vaddr),
|
||||
static_cast<unsigned long long>(vaddr + size));
|
||||
}
|
||||
}
|
||||
RegionManager* GetOrCreateRegion(uint64_t index);
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
#include "graphics/host_gpu/objects/label.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum LabelStatus {
|
||||
New,
|
||||
Active,
|
||||
ActiveDeleted,
|
||||
NotActive,
|
||||
};
|
||||
|
||||
struct LabelCallbacks {
|
||||
LabelCallback callback_1 = nullptr;
|
||||
LabelCallback callback_2 = nullptr;
|
||||
uint64_t args[LABEL_ARGS_MAX] = {};
|
||||
};
|
||||
|
||||
struct LabelEvent final {
|
||||
vk::Device device = nullptr;
|
||||
vk::Event event = nullptr;
|
||||
|
||||
~LabelEvent() {
|
||||
if (event != nullptr) {
|
||||
device.destroyEvent(event, nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct LabelSubmission {
|
||||
std::shared_ptr<LabelEvent> completion;
|
||||
LabelCallbacks callbacks;
|
||||
};
|
||||
|
||||
struct Label {
|
||||
vk::Device device = nullptr;
|
||||
LabelStatus status = LabelStatus::New;
|
||||
LabelCallbacks callbacks;
|
||||
std::vector<LabelSubmission> submissions;
|
||||
};
|
||||
|
||||
class LabelManager {
|
||||
public:
|
||||
LabelManager() {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
Common::Thread t(ThreadRun, this);
|
||||
t.Detach();
|
||||
}
|
||||
~LabelManager() { KYTY_NOT_IMPLEMENTED; }
|
||||
KYTY_CLASS_NO_COPY(LabelManager);
|
||||
|
||||
Label* Create(LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
|
||||
void Delete(Label& label);
|
||||
void Set(CommandBuffer& buffer, Label& label);
|
||||
void Drain();
|
||||
|
||||
private:
|
||||
static void ThreadRun(void* data);
|
||||
|
||||
bool Remove(Label& label);
|
||||
static void Destroy(Label& label);
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_cond_var;
|
||||
std::vector<Label*> m_labels;
|
||||
uint64_t m_callbacks_in_flight = 0;
|
||||
};
|
||||
|
||||
static LabelManager* g_label_manager = nullptr;
|
||||
static thread_local bool g_in_label_callback = false;
|
||||
|
||||
class LabelCallbackScope final {
|
||||
public:
|
||||
LabelCallbackScope() {
|
||||
if (g_in_label_callback) {
|
||||
EXIT("recursive GPU label callback\n");
|
||||
}
|
||||
g_in_label_callback = true;
|
||||
}
|
||||
~LabelCallbackScope() {
|
||||
if (!g_in_label_callback) {
|
||||
EXIT("GPU label callback scope is not active\n");
|
||||
}
|
||||
g_in_label_callback = false;
|
||||
}
|
||||
};
|
||||
|
||||
void LabelManager::ThreadRun(void* data) {
|
||||
auto* manager = static_cast<LabelManager*>(data);
|
||||
|
||||
for (;;) {
|
||||
manager->m_mutex.Lock();
|
||||
|
||||
uint64_t active_count = 0;
|
||||
|
||||
std::vector<Label*> deleted_labels;
|
||||
std::vector<LabelCallbacks> fired_labels;
|
||||
std::vector<LabelSubmission> finished_submissions;
|
||||
deleted_labels.reserve(manager->m_labels.size());
|
||||
|
||||
for (auto& label: manager->m_labels) {
|
||||
for (auto it = label->submissions.begin(); it != label->submissions.end();) {
|
||||
active_count++;
|
||||
|
||||
if (it->completion == nullptr || it->completion->device == nullptr ||
|
||||
it->completion->event == nullptr) {
|
||||
EXIT("GPU label submission has no completion event\n");
|
||||
}
|
||||
const auto status = it->completion->device.getEventStatus(it->completion->event);
|
||||
switch (status) {
|
||||
case vk::Result::eEventSet:
|
||||
fired_labels.push_back(it->callbacks);
|
||||
finished_submissions.push_back(*it);
|
||||
it = label->submissions.erase(it);
|
||||
break;
|
||||
case vk::Result::eEventReset: ++it; break;
|
||||
default: EXIT("vkGetEventStatus returned an unexpected result\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (label->submissions.empty()) {
|
||||
switch (label->status) {
|
||||
case LabelStatus::ActiveDeleted: deleted_labels.push_back(label); break;
|
||||
case LabelStatus::Active: label->status = LabelStatus::NotActive; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (active_count == 0) {
|
||||
manager->m_cond_var.Wait(&manager->m_mutex);
|
||||
}
|
||||
if (fired_labels.size() > UINT64_MAX - manager->m_callbacks_in_flight) {
|
||||
EXIT("GPU label callback count overflow\n");
|
||||
}
|
||||
manager->m_callbacks_in_flight += fired_labels.size();
|
||||
|
||||
for (auto& label: deleted_labels) {
|
||||
bool removed = manager->Remove(*label);
|
||||
EXIT_NOT_IMPLEMENTED(!removed);
|
||||
}
|
||||
|
||||
manager->m_mutex.Unlock();
|
||||
|
||||
// Each completion event is shared with the recording command buffer's fence retainer.
|
||||
// The event is destroyed only after both the label thread observed it and that command
|
||||
// buffer completed, even when either side wins the race.
|
||||
(void)finished_submissions;
|
||||
|
||||
for (auto& label: deleted_labels) {
|
||||
Destroy(*label);
|
||||
}
|
||||
|
||||
for (auto& label: fired_labels) {
|
||||
LabelCallbackScope callback_scope;
|
||||
|
||||
if (label.callback_1 != nullptr) {
|
||||
(void)label.callback_1(label.args);
|
||||
}
|
||||
|
||||
if (label.callback_2 != nullptr) {
|
||||
label.callback_2(label.args);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fired_labels.empty()) {
|
||||
Common::LockGuard lock(manager->m_mutex);
|
||||
if (manager->m_callbacks_in_flight < fired_labels.size()) {
|
||||
EXIT("GPU label callback count underflow\n");
|
||||
}
|
||||
manager->m_callbacks_in_flight -= fired_labels.size();
|
||||
manager->m_cond_var.SignalAll();
|
||||
}
|
||||
|
||||
Common::Thread::SleepMicro(100);
|
||||
}
|
||||
}
|
||||
|
||||
Label* LabelManager::Create(LabelCallback callback_1, LabelCallback callback_2,
|
||||
const uint64_t* args) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto* label = new Label;
|
||||
|
||||
label->status = LabelStatus::New;
|
||||
label->device = GetRenderContext().GetGraphics().device;
|
||||
label->callbacks.callback_1 = callback_1;
|
||||
label->callbacks.callback_2 = callback_2;
|
||||
|
||||
if (args != nullptr) {
|
||||
for (int i = 0; i < LABEL_ARGS_MAX; i++) {
|
||||
label->callbacks.args[i] = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
m_labels.push_back(label);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
bool LabelManager::Remove(Label& label) {
|
||||
EXIT_IF(label.device == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
const auto it = std::find(m_labels.begin(), m_labels.end(), &label);
|
||||
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(label.status != LabelStatus::NotActive &&
|
||||
label.status != LabelStatus::Active &&
|
||||
label.status != LabelStatus::ActiveDeleted);
|
||||
|
||||
if (!label.submissions.empty()) {
|
||||
label.status = LabelStatus::ActiveDeleted;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_labels.erase(it);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LabelManager::Destroy(Label& label) {
|
||||
EXIT_IF(label.device == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!label.submissions.empty());
|
||||
|
||||
delete &label;
|
||||
}
|
||||
|
||||
void LabelManager::Delete(Label& label) {
|
||||
if (Remove(label)) {
|
||||
Destroy(label);
|
||||
}
|
||||
}
|
||||
|
||||
void LabelManager::Set(CommandBuffer& buffer, Label& label) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
EXIT_IF(label.device == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
const auto it = std::find(m_labels.begin(), m_labels.end(), &label);
|
||||
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(label.status != LabelStatus::New &&
|
||||
label.status != LabelStatus::NotActive &&
|
||||
label.status != LabelStatus::Active);
|
||||
|
||||
label.status = LabelStatus::Active;
|
||||
|
||||
LabelSubmission submission {};
|
||||
submission.callbacks = label.callbacks;
|
||||
submission.completion = std::make_shared<LabelEvent>();
|
||||
submission.completion->device = label.device;
|
||||
|
||||
auto vk_buffer = buffer.Handle();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(vk_buffer == nullptr);
|
||||
|
||||
vk::EventCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eEventCreateInfo;
|
||||
create_info.pNext = nullptr;
|
||||
create_info.flags = {};
|
||||
|
||||
const auto create_result =
|
||||
label.device.createEvent(&create_info, nullptr, &submission.completion->event);
|
||||
if (create_result != vk::Result::eSuccess || submission.completion->event == nullptr) {
|
||||
EXIT("failed to create label event: %s (%d)\n", VulkanToString(create_result).c_str(),
|
||||
static_cast<int>(create_result));
|
||||
}
|
||||
buffer.RetainResourceUntilFence(submission.completion);
|
||||
|
||||
// Labels can be reused before an earlier end-of-pipe event has been
|
||||
// observed by the polling thread. Capture a separate Vulkan event and
|
||||
// callback snapshot for each set so older writes are not lost.
|
||||
const auto reset_result = label.device.resetEvent(submission.completion->event);
|
||||
if (reset_result != vk::Result::eSuccess) {
|
||||
EXIT("failed to reset label event: %s (%d)\n", VulkanToString(reset_result).c_str(),
|
||||
static_cast<int>(reset_result));
|
||||
}
|
||||
vk_buffer.setEvent(submission.completion->event, vk::PipelineStageFlagBits::eBottomOfPipe);
|
||||
|
||||
label.submissions.push_back(submission);
|
||||
|
||||
m_cond_var.SignalAll();
|
||||
}
|
||||
|
||||
void LabelManager::Drain() {
|
||||
m_mutex.Lock();
|
||||
for (;;) {
|
||||
const bool pending = std::any_of(m_labels.begin(), m_labels.end(), [](const Label* label) {
|
||||
return !label->submissions.empty();
|
||||
});
|
||||
if (!pending && m_callbacks_in_flight == 0) {
|
||||
m_mutex.Unlock();
|
||||
return;
|
||||
}
|
||||
m_cond_var.SignalAll();
|
||||
m_cond_var.Wait(&m_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void LabelInit() {
|
||||
EXIT_IF(g_label_manager != nullptr);
|
||||
|
||||
g_label_manager = new LabelManager;
|
||||
}
|
||||
|
||||
Label* LabelCreate(LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args) {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
|
||||
return g_label_manager->Create(callback_1, callback_2, args);
|
||||
}
|
||||
|
||||
void LabelDelete(Label& label) {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
|
||||
g_label_manager->Delete(label);
|
||||
}
|
||||
|
||||
void LabelSet(CommandBuffer& buffer, Label& label) {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
|
||||
g_label_manager->Set(buffer, label);
|
||||
}
|
||||
|
||||
void LabelDrain() {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
g_label_manager->Drain();
|
||||
}
|
||||
|
||||
bool LabelInCallback() noexcept {
|
||||
return g_in_label_callback;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct Label;
|
||||
class CommandBuffer;
|
||||
struct GraphicContext;
|
||||
|
||||
void LabelInit();
|
||||
|
||||
constexpr int LABEL_ARGS_MAX = 5;
|
||||
using LabelCallback = bool (*)(const uint64_t* args);
|
||||
|
||||
Label* LabelCreate(LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
|
||||
void LabelDelete(Label& label);
|
||||
void LabelSet(CommandBuffer& buffer, Label& label);
|
||||
void LabelDrain();
|
||||
[[nodiscard]] bool LabelInCallback() noexcept;
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_ */
|
||||
@@ -1,17 +1,12 @@
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/renderer/tiler.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
@@ -159,6 +154,8 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_
|
||||
raw_type, raw_order);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static uint64_t GetLevelSrcOffset(const TileSizeOffset& level_size) {
|
||||
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
|
||||
}
|
||||
@@ -193,28 +190,6 @@ uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint3
|
||||
return level_offset + static_cast<uint64_t>(slice) * slice_stride;
|
||||
}
|
||||
|
||||
uint64_t TextureCalcUploadSize(const TextureUploadLayout& layout,
|
||||
const std::vector<BufferImageCopy>& regions, uint64_t levels,
|
||||
uint32_t depth) {
|
||||
uint64_t size = 0;
|
||||
|
||||
for (const auto& r: regions) {
|
||||
size = std::max<uint64_t>(size, static_cast<uint64_t>(r.offset) +
|
||||
layout.level_sizes[r.dst_level].size);
|
||||
}
|
||||
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
const auto src_size = GetLevelSrcSize(layout.level_sizes[level]);
|
||||
const auto mip_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
|
||||
for (uint32_t z = 0; z < mip_depth; z++) {
|
||||
size = std::max<uint64_t>(size,
|
||||
TextureUploadSliceSourceOffset(layout, level, z) + src_size);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
|
||||
switch (static_cast<Prospero::CompSwizzle>(s)) {
|
||||
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
|
||||
@@ -232,6 +207,8 @@ static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
|
||||
return (swizzle >> (channel * 3u)) & 0x7u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
|
||||
vk::ComponentMapping components {};
|
||||
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
|
||||
@@ -241,94 +218,6 @@ vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
|
||||
return components;
|
||||
}
|
||||
|
||||
bool TextureCheckFormat(vk::ImageCreateInfo& image_info) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
vk::ImageFormatProperties props {};
|
||||
if (graphics.GetImageFormatProperties(image_info.format, image_info.imageType,
|
||||
image_info.tiling, image_info.usage, image_info.flags,
|
||||
&props) == vk::Result::eErrorFormatNotSupported) {
|
||||
auto apply_fallback = [&](vk::Format replacement, const char* message) {
|
||||
image_info.format = replacement;
|
||||
const bool result = TextureCheckFormat(image_info);
|
||||
LOGF("%s [%s]\n", message, (!result ? "FAIL" : "SUCCESS"));
|
||||
return result;
|
||||
};
|
||||
|
||||
if (image_info.format == vk::Format::eR8G8B8A8Srgb) {
|
||||
// TODO() convert SRGB -> LINEAR in shader
|
||||
return apply_fallback(
|
||||
vk::Format::eR8G8B8A8Unorm,
|
||||
"replace vk::Format::eR8G8B8A8Srgb => vk::Format::eR8G8B8A8Unorm");
|
||||
}
|
||||
if (image_info.format == vk::Format::eB8G8R8A8Srgb) {
|
||||
// TODO() convert SRGB -> LINEAR in shader
|
||||
return apply_fallback(
|
||||
vk::Format::eB8G8R8A8Unorm,
|
||||
"replace vk::Format::eB8G8R8A8Srgb => vk::Format::eB8G8R8A8Unorm");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TextureCheckFormatExact(const vk::ImageCreateInfo& image_info) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
vk::ImageFormatProperties props {};
|
||||
return graphics.GetImageFormatProperties(image_info.format, image_info.imageType,
|
||||
image_info.tiling, image_info.usage, image_info.flags,
|
||||
&props) != vk::Result::eErrorFormatNotSupported;
|
||||
}
|
||||
|
||||
bool TextureCheckStorageSwizzle(vk::ImageCreateInfo& image_info, vk::ComponentMapping& components) {
|
||||
if (image_info.usage & vk::ImageUsageFlagBits::eStorage) {
|
||||
if (components.r == vk::ComponentSwizzle::eR && components.g == vk::ComponentSwizzle::eG &&
|
||||
components.b == vk::ComponentSwizzle::eB && components.a == vk::ComponentSwizzle::eA) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (components.r == vk::ComponentSwizzle::eB && components.g == vk::ComponentSwizzle::eG &&
|
||||
components.b == vk::ComponentSwizzle::eR && components.a == vk::ComponentSwizzle::eA &&
|
||||
image_info.format == vk::Format::eR8G8B8A8Srgb) {
|
||||
LOGF("replace vk::Format::eR8G8B8A8Srgb => vk::Format::eB8G8R8A8Srgb\n");
|
||||
|
||||
components.r = vk::ComponentSwizzle::eR;
|
||||
components.g = vk::ComponentSwizzle::eG;
|
||||
components.b = vk::ComponentSwizzle::eB;
|
||||
components.a = vk::ComponentSwizzle::eA;
|
||||
image_info.format = vk::Format::eB8G8R8A8Srgb;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO() swizzle channels in shader
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
vk::ImageUsageFlags TextureGetUsage(TextureFormatUsage usage) {
|
||||
vk::ImageUsageFlags vk_usage =
|
||||
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc;
|
||||
if (TextureHasFormatUsage(usage, TextureFormatUsage::Sampled)) {
|
||||
vk_usage |= vk::ImageUsageFlagBits::eSampled;
|
||||
}
|
||||
if (TextureHasFormatUsage(usage, TextureFormatUsage::Storage)) {
|
||||
vk_usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
return vk_usage;
|
||||
}
|
||||
|
||||
vk::ImageUsageFlags TextureGetViewUsage(TextureFormatUsage usage) {
|
||||
vk::ImageUsageFlags vk_usage = {};
|
||||
if (TextureHasFormatUsage(usage, TextureFormatUsage::Sampled)) {
|
||||
vk_usage |= vk::ImageUsageFlagBits::eSampled;
|
||||
}
|
||||
if (TextureHasFormatUsage(usage, TextureFormatUsage::Storage)) {
|
||||
vk_usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
return vk_usage;
|
||||
}
|
||||
|
||||
vk::Format TextureGetFormat(uint32_t fmt) {
|
||||
const auto vk_format = VulkanFormat(fmt);
|
||||
if (vk_format != vk::Format::eUndefined) {
|
||||
@@ -338,261 +227,10 @@ vk::Format TextureGetFormat(uint32_t fmt) {
|
||||
return vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
static uint32_t AlignUpU32(uint32_t value, uint32_t alignment) {
|
||||
return (value + alignment - 1u) & ~(alignment - 1u);
|
||||
}
|
||||
namespace {
|
||||
|
||||
uint32_t TextureGetAtlasSliceYStride(vk::Format format, uint32_t mip_height, uint32_t depth,
|
||||
uint64_t levels) {
|
||||
return (depth > 1 && levels > 1 && Transfer::IsBlockCompressedFormat(format)
|
||||
? AlignUpU32(mip_height, 4u)
|
||||
: mip_height);
|
||||
}
|
||||
|
||||
uint32_t TextureCalcStackedImageHeight(vk::Format format, uint32_t height, uint32_t depth,
|
||||
uint64_t levels) {
|
||||
auto image_height = height * depth;
|
||||
if (depth <= 1 || levels <= 1 || !Transfer::IsBlockCompressedFormat(format)) {
|
||||
return image_height;
|
||||
}
|
||||
|
||||
uint32_t mip_height = height;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
const auto mip_image_height =
|
||||
TextureGetAtlasSliceYStride(format, mip_height, depth, levels) * depth;
|
||||
image_height = std::max<uint32_t>(image_height, mip_image_height << level);
|
||||
if (mip_height > 1) {
|
||||
mip_height /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return image_height;
|
||||
}
|
||||
|
||||
uint32_t TextureCalcMipmapAtlasImageHeight(vk::Format format, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels) {
|
||||
auto image_height = height * depth;
|
||||
if (levels <= 1) {
|
||||
return image_height;
|
||||
}
|
||||
|
||||
uint32_t mip_height = height;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
const auto mipmap_offset = Transfer::MipmapAtlasOffset(level, width, height);
|
||||
const auto mip_bottom =
|
||||
static_cast<uint32_t>(mipmap_offset.second) +
|
||||
TextureGetAtlasSliceYStride(format, mip_height, depth, levels) * depth;
|
||||
image_height = std::max<uint32_t>(image_height, mip_bottom);
|
||||
if (mip_height > 1) {
|
||||
mip_height /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return image_height;
|
||||
}
|
||||
|
||||
bool TextureIs3DTexture(uint64_t type) {
|
||||
return static_cast<Prospero::ImageType>(type) == Prospero::ImageType::kColor3D;
|
||||
}
|
||||
|
||||
bool TextureIsCubeTexture(uint64_t type) {
|
||||
return static_cast<Prospero::ImageType>(type) == Prospero::ImageType::kCube;
|
||||
}
|
||||
|
||||
bool TextureIsLayeredTexture(uint64_t type) {
|
||||
const auto image_type = static_cast<Prospero::ImageType>(type);
|
||||
return image_type == Prospero::ImageType::kCube ||
|
||||
image_type == Prospero::ImageType::kColor1DArray ||
|
||||
image_type == Prospero::ImageType::kColor2DArray ||
|
||||
image_type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
}
|
||||
|
||||
bool TextureCanCreateCubeView(uint64_t type, uint32_t base_array, uint32_t layer_count) {
|
||||
return TextureIsCubeTexture(type) && base_array % 6u == 0 && layer_count >= 6 &&
|
||||
layer_count % 6u == 0;
|
||||
}
|
||||
|
||||
vk::ComponentMapping TextureCreateImage(VulkanImage& vk_obj,
|
||||
const TextureImageCreateParams& params) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
EXIT_IF(params.owner == nullptr);
|
||||
|
||||
const bool array_texture = TextureIsLayeredTexture(params.type);
|
||||
const bool volume_texture = TextureIs3DTexture(params.type);
|
||||
|
||||
auto pixel_format = TextureGetFormat(params.fmt);
|
||||
EXIT_NOT_IMPLEMENTED(pixel_format == vk::Format::eUndefined);
|
||||
EXIT_NOT_IMPLEMENTED(params.width == 0);
|
||||
EXIT_NOT_IMPLEMENTED(params.height == 0);
|
||||
EXIT_NOT_IMPLEMENTED(params.depth == 0);
|
||||
EXIT_NOT_IMPLEMENTED(params.levels == 0 || params.levels > 16);
|
||||
|
||||
uint32_t image_height = 0;
|
||||
uint32_t image_mips = 0;
|
||||
if (params.image_layout == TextureUploadDestination::MipAtlas) {
|
||||
EXIT_NOT_IMPLEMENTED(params.base_level != 0);
|
||||
const auto atlas_depth = (array_texture || volume_texture ? 1u : params.depth);
|
||||
image_height = TextureCalcMipmapAtlasImageHeight(
|
||||
pixel_format, static_cast<uint32_t>(params.width), static_cast<uint32_t>(params.height),
|
||||
atlas_depth, params.levels);
|
||||
image_mips = 1;
|
||||
} else {
|
||||
image_height =
|
||||
(array_texture || volume_texture
|
||||
? static_cast<uint32_t>(params.height)
|
||||
: TextureCalcStackedImageHeight(pixel_format, static_cast<uint32_t>(params.height),
|
||||
params.depth, params.levels));
|
||||
image_mips = static_cast<uint32_t>(params.levels);
|
||||
}
|
||||
|
||||
vk::ComponentMapping components =
|
||||
TextureGetComponentMapping(static_cast<uint32_t>(params.swizzle));
|
||||
|
||||
vk::ImageCreateInfo image_info {};
|
||||
image_info.sType = vk::StructureType::eImageCreateInfo;
|
||||
image_info.pNext = nullptr;
|
||||
image_info.flags =
|
||||
(params.allow_cube_view && TextureCanCreateCubeView(params.type, 0, params.depth)
|
||||
? vk::ImageCreateFlagBits::eCubeCompatible
|
||||
: vk::ImageCreateFlags {}) |
|
||||
(volume_texture ? vk::ImageCreateFlagBits::e2DArrayCompatible : vk::ImageCreateFlags {}) |
|
||||
(params.compatible_format_views ? vk::ImageCreateFlagBits::eMutableFormat
|
||||
: vk::ImageCreateFlags {});
|
||||
image_info.imageType = (volume_texture ? vk::ImageType::e3D : vk::ImageType::e2D);
|
||||
image_info.extent.width = static_cast<uint32_t>(params.width);
|
||||
image_info.extent.height = image_height;
|
||||
image_info.extent.depth = (volume_texture ? params.depth : 1);
|
||||
image_info.mipLevels = image_mips;
|
||||
image_info.arrayLayers = (array_texture ? params.depth : 1);
|
||||
image_info.format = pixel_format;
|
||||
image_info.tiling = vk::ImageTiling::eOptimal;
|
||||
image_info.initialLayout = vk::ImageLayout::eUndefined;
|
||||
image_info.usage = TextureGetUsage(params.format_usage);
|
||||
image_info.sharingMode = vk::SharingMode::eExclusive;
|
||||
image_info.samples = vk::SampleCountFlagBits::e1;
|
||||
|
||||
const bool storage_view = TextureHasFormatUsage(params.view_usage, TextureFormatUsage::Storage);
|
||||
if (storage_view && !TextureCheckStorageSwizzle(image_info, components)) {
|
||||
if (!params.storage_swizzle_fallback) {
|
||||
EXIT("swizzle is not supported");
|
||||
}
|
||||
|
||||
static std::atomic_uint log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("\t %s swizzle 0x%08" PRIx64 " is not supported, using identity mapping\n",
|
||||
params.owner, params.swizzle);
|
||||
}
|
||||
components.r = vk::ComponentSwizzle::eR;
|
||||
components.g = vk::ComponentSwizzle::eG;
|
||||
components.b = vk::ComponentSwizzle::eB;
|
||||
components.a = vk::ComponentSwizzle::eA;
|
||||
}
|
||||
|
||||
const auto view_checked_format = image_info.format;
|
||||
const auto requested_usage = params.format_usage;
|
||||
const auto required_usage = params.required_format_usage;
|
||||
const bool has_optional_usage = static_cast<uint32_t>(requested_usage & ~required_usage) != 0;
|
||||
if (!TextureCheckFormatExact(image_info) && has_optional_usage) {
|
||||
auto required_info = image_info;
|
||||
required_info.usage = TextureGetUsage(required_usage);
|
||||
required_info.format = view_checked_format;
|
||||
if (TextureCheckFormatExact(required_info)) {
|
||||
static std::atomic_uint log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("\t %s usage 0x%08x is not supported for format %d, using required "
|
||||
"usage 0x%08x\n",
|
||||
params.owner, static_cast<uint32_t>(TextureGetUsage(requested_usage)),
|
||||
static_cast<int>(image_info.format),
|
||||
static_cast<uint32_t>(TextureGetUsage(required_usage)));
|
||||
}
|
||||
image_info = required_info;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextureCheckFormat(image_info)) {
|
||||
if (has_optional_usage) {
|
||||
image_info.format = view_checked_format;
|
||||
image_info.usage = TextureGetUsage(required_usage);
|
||||
}
|
||||
if (!has_optional_usage || !TextureCheckFormat(image_info)) {
|
||||
EXIT("format is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
vk_obj.extent.width = image_info.extent.width;
|
||||
vk_obj.extent.height = image_info.extent.height;
|
||||
vk_obj.layers = image_info.arrayLayers;
|
||||
vk_obj.mip_levels = image_info.mipLevels;
|
||||
vk_obj.format = image_info.format;
|
||||
vk_obj.image = nullptr;
|
||||
vk_obj.layout = image_info.initialLayout;
|
||||
|
||||
vk_obj.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
|
||||
bool created = graphics.CreateImage(image_info, vk_obj);
|
||||
EXIT_NOT_IMPLEMENTED(!created);
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
void TextureCreateImageViews(VulkanImage& vk_obj,
|
||||
vk::ComponentMapping components, uint64_t type, uint32_t base_array,
|
||||
uint32_t base_level, uint32_t level_count, uint32_t depth,
|
||||
bool allow_cube_view, TextureFormatUsage view_usage) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
EXIT_IF(level_count == 0 || base_level + level_count > vk_obj.mip_levels);
|
||||
|
||||
const bool layered_texture = TextureIsLayeredTexture(type);
|
||||
const bool volume_texture = TextureIs3DTexture(type);
|
||||
const auto layer_count = (layered_texture && base_array < depth ? depth - base_array : 1u);
|
||||
const auto volume_slices = std::max(depth >> base_level, 1u);
|
||||
|
||||
vk::ImageViewUsageCreateInfo usage_info {};
|
||||
usage_info.sType = vk::StructureType::eImageViewUsageCreateInfo;
|
||||
usage_info.pNext = nullptr;
|
||||
usage_info.usage = TextureGetViewUsage(view_usage);
|
||||
|
||||
vk::ImageViewCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eImageViewCreateInfo;
|
||||
create_info.pNext = (usage_info.usage ? &usage_info : nullptr);
|
||||
create_info.flags = {};
|
||||
create_info.image = vk_obj.image;
|
||||
create_info.viewType = vk::ImageViewType::e2D;
|
||||
create_info.format = vk_obj.format;
|
||||
create_info.components = components;
|
||||
create_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
create_info.subresourceRange.baseArrayLayer = (layered_texture ? base_array : 0);
|
||||
create_info.subresourceRange.baseMipLevel = base_level;
|
||||
create_info.subresourceRange.layerCount = layer_count;
|
||||
create_info.subresourceRange.levelCount = level_count;
|
||||
if (volume_texture) {
|
||||
create_info.viewType = vk::ImageViewType::e3D;
|
||||
create_info.subresourceRange.baseArrayLayer = 0;
|
||||
create_info.subresourceRange.layerCount = 1;
|
||||
} else if (allow_cube_view && TextureCanCreateCubeView(type, base_array, layer_count)) {
|
||||
create_info.viewType =
|
||||
(layer_count > 6 ? vk::ImageViewType::eCubeArray : vk::ImageViewType::eCube);
|
||||
} else if (layered_texture) {
|
||||
create_info.viewType = vk::ImageViewType::e2DArray;
|
||||
}
|
||||
|
||||
auto create_view = [&](int index) {
|
||||
const auto result =
|
||||
graphics.device.createImageView(&create_info, nullptr, &vk_obj.image_view[index]);
|
||||
if (result != vk::Result::eSuccess || vk_obj.image_view[index] == nullptr) {
|
||||
EXIT("failed to create texture image view: result=%d format=%d index=%d\n",
|
||||
static_cast<int>(result), static_cast<int>(create_info.format), index);
|
||||
}
|
||||
};
|
||||
create_view(VulkanImage::VIEW_DEFAULT);
|
||||
|
||||
create_info.viewType = vk::ImageViewType::e2DArray;
|
||||
create_info.subresourceRange.layerCount = volume_texture ? volume_slices : layer_count;
|
||||
create_view(VulkanImage::VIEW_DEFAULT_ARRAY);
|
||||
}
|
||||
|
||||
static uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
|
||||
uint64_t total_size, uint32_t depth) {
|
||||
uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
|
||||
uint64_t total_size, uint32_t depth) {
|
||||
uint64_t stride = 0;
|
||||
for (uint32_t i = 0; i < levels; i++) {
|
||||
stride =
|
||||
@@ -609,7 +247,7 @@ static uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64
|
||||
return stride;
|
||||
}
|
||||
|
||||
static uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
|
||||
uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
|
||||
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt);
|
||||
bytes_per_element != 0) {
|
||||
return static_cast<uint64_t>(pitch) * height * bytes_per_element;
|
||||
@@ -625,8 +263,8 @@ static uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height,
|
||||
uint64_t levels, uint32_t base_pitch) {
|
||||
uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height,
|
||||
uint64_t levels, uint32_t base_pitch) {
|
||||
uint64_t offset = 0;
|
||||
auto pitch = base_pitch;
|
||||
auto h = static_cast<uint32_t>(height);
|
||||
@@ -655,6 +293,8 @@ static uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt,
|
||||
return offset;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
|
||||
uint64_t levels, uint32_t depth, uint64_t pitch,
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
@@ -663,6 +303,7 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64
|
||||
TextureUploadLayout layout {};
|
||||
layout.tile = static_cast<uint32_t>(tile);
|
||||
layout.pitch = static_cast<uint32_t>(pitch);
|
||||
layout.texel_block = Prospero::BlockCompressedBytesPerBlock(fmt) != 0 ? 4u : 1u;
|
||||
layout.volume_texture = volume_texture;
|
||||
|
||||
if (fmt != 0) {
|
||||
@@ -752,12 +393,10 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64
|
||||
return layout;
|
||||
}
|
||||
|
||||
std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout& layout,
|
||||
vk::Format image_format, uint32_t width,
|
||||
uint32_t height, uint32_t depth,
|
||||
uint64_t levels, bool array_texture,
|
||||
bool volume_texture,
|
||||
TextureUploadDestination destination) {
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture) {
|
||||
uint32_t mip_width = width;
|
||||
uint32_t mip_height = height;
|
||||
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
|
||||
@@ -765,53 +404,34 @@ std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout
|
||||
? width
|
||||
: layout.pitch;
|
||||
|
||||
std::vector<BufferImageCopy> regions;
|
||||
std::vector<vk::BufferImageCopy> regions;
|
||||
regions.reserve(GetTextureRegionCount(depth, levels, volume_texture));
|
||||
for (uint32_t i = 0; i < levels; i++) {
|
||||
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0);
|
||||
|
||||
const auto mipmap_offset = Transfer::MipmapAtlasOffset(i, width, height);
|
||||
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
|
||||
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
|
||||
|
||||
for (uint32_t z = 0; z < mip_depth; z++) {
|
||||
const auto slice_offset = z * layout.slice_stride;
|
||||
BufferImageCopy region {};
|
||||
|
||||
region.offset = static_cast<uint32_t>(layout.level_sizes[i].offset + slice_offset);
|
||||
region.width = mip_width;
|
||||
region.height = mip_height;
|
||||
region.copy_height =
|
||||
(!array_texture && !volume_texture && depth > 1 && levels > 1 &&
|
||||
Transfer::IsBlockCompressedFormat(image_format)
|
||||
? TextureGetAtlasSliceYStride(image_format, mip_height, depth, levels)
|
||||
: 0);
|
||||
region.dst_layer = (array_texture ? z : 0);
|
||||
region.dst_z = (volume_texture ? static_cast<int>(z) : 0);
|
||||
if (!layout.volume_texture &&
|
||||
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear &&
|
||||
layout.padded_sizes[i].width != 0) {
|
||||
region.pitch = layout.padded_sizes[i].width;
|
||||
const auto slice_offset = z * layout.slice_stride;
|
||||
vk::BufferImageCopy region {};
|
||||
region.bufferOffset =
|
||||
layout.level_sizes[i].offset + slice_offset;
|
||||
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i,
|
||||
array_texture ? z : 0, 1};
|
||||
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
|
||||
region.imageExtent = {mip_width, mip_height, 1};
|
||||
const bool linear =
|
||||
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear;
|
||||
if (linear) {
|
||||
region.bufferRowLength = layout.padded_sizes[i].width;
|
||||
region.bufferImageHeight = layout.padded_sizes[i].height;
|
||||
} else {
|
||||
region.pitch = mip_pitch;
|
||||
}
|
||||
|
||||
if (destination == TextureUploadDestination::MipLevels) {
|
||||
region.dst_level = i;
|
||||
region.dst_x = 0;
|
||||
region.dst_y =
|
||||
(array_texture || volume_texture
|
||||
? 0
|
||||
: static_cast<int>(z * TextureGetAtlasSliceYStride(
|
||||
image_format, mip_height, depth, levels)));
|
||||
} else {
|
||||
region.dst_level = 0;
|
||||
region.dst_x = mipmap_offset.first;
|
||||
region.dst_y =
|
||||
(array_texture || volume_texture
|
||||
? mipmap_offset.second
|
||||
: mipmap_offset.second +
|
||||
static_cast<int>(z * TextureGetAtlasSliceYStride(
|
||||
image_format, mip_height, depth, levels)));
|
||||
const auto align = [](uint32_t value, uint32_t block) {
|
||||
return ((value + block - 1u) / block) * block;
|
||||
};
|
||||
const auto pitch = align(mip_pitch, layout.texel_block);
|
||||
region.bufferRowLength =
|
||||
pitch > align(mip_width, layout.texel_block) ? pitch : 0;
|
||||
}
|
||||
regions.push_back(region);
|
||||
}
|
||||
@@ -830,55 +450,6 @@ std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout
|
||||
return regions;
|
||||
}
|
||||
|
||||
std::vector<ImageBufferCopy>
|
||||
TextureBuildDownloadRegions(const std::vector<BufferImageCopy>& upload_regions) {
|
||||
std::vector<ImageBufferCopy> regions;
|
||||
regions.reserve(upload_regions.size());
|
||||
for (const auto& region: upload_regions) {
|
||||
regions.push_back({region.offset, region.pitch, region.dst_level, region.width,
|
||||
region.height, region.copy_height, region.dst_layer, region.dst_x,
|
||||
region.dst_y, region.dst_z, region.aspect});
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
static uint64_t FmaskRegionCopySize(const BufferImageCopy& region) {
|
||||
const uint64_t row_length = (region.pitch != 0 ? region.pitch : region.width);
|
||||
if (region.height == 0 || region.width == 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((static_cast<uint64_t>(region.height - 1u) * row_length) + region.width) *
|
||||
sizeof(uint32_t);
|
||||
}
|
||||
|
||||
static void UploadFmaskIdentity(VulkanImage& vk_obj,
|
||||
const std::vector<BufferImageCopy>& regions,
|
||||
vk::ImageLayout dst_layout, const char* owner) {
|
||||
constexpr uint32_t kIdentityFmaskPattern = 0x76543210u;
|
||||
|
||||
std::vector<BufferImageCopy> upload_regions = regions;
|
||||
uint64_t upload_size = 0;
|
||||
for (auto& region: upload_regions) {
|
||||
upload_size = (upload_size + 255u) & ~uint64_t {255};
|
||||
EXIT_NOT_IMPLEMENTED(upload_size > UINT32_MAX);
|
||||
region.offset = static_cast<uint32_t>(upload_size);
|
||||
upload_size += FmaskRegionCopySize(region);
|
||||
}
|
||||
|
||||
LOGF("%s: temporary: decoding PS5 FMASK8_S4_F4 as identity pattern 0x%08" PRIx32
|
||||
", upload_size=%" PRIu64 " regions=%zu\n",
|
||||
owner, kIdentityFmaskPattern, upload_size, upload_regions.size());
|
||||
|
||||
Transfer::ScratchBuffer temp_buf(upload_size);
|
||||
auto* words = static_cast<uint32_t*>(temp_buf.Data());
|
||||
for (uint64_t i = 0; i < upload_size / sizeof(uint32_t); i++) {
|
||||
words[i] = kIdentityFmaskPattern;
|
||||
}
|
||||
|
||||
Transfer::UploadImage(vk_obj, temp_buf.Data(), upload_size, upload_regions,
|
||||
dst_layout);
|
||||
}
|
||||
|
||||
struct GpuTileElementLayout {
|
||||
uint32_t bytes = 0;
|
||||
uint32_t wide = 1;
|
||||
@@ -905,7 +476,8 @@ static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>& regions,
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
|
||||
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
|
||||
@@ -929,7 +501,8 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
|
||||
infos.reserve(regions.size());
|
||||
if (layout.volume_texture) {
|
||||
TileVolumeLayout volume {};
|
||||
if (!TileGetTextureVolumeLayout(fmt, regions[0].width, regions[0].height, depth,
|
||||
if (!TileGetTextureVolumeLayout(fmt, regions[0].imageExtent.width,
|
||||
regions[0].imageExtent.height, depth,
|
||||
static_cast<uint32_t>(levels), layout.tile, volume)) {
|
||||
return false;
|
||||
}
|
||||
@@ -945,10 +518,16 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
|
||||
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
|
||||
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
|
||||
const auto& region = regions[region_base + z];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
GpuTileInfo info {};
|
||||
info.family = block.family;
|
||||
info.bytes_per_element = block.bytes_per_element;
|
||||
info.linear_offset = region.offset;
|
||||
info.linear_offset = region.bufferOffset;
|
||||
info.tiled_offset =
|
||||
static_cast<uint64_t>(z / block.block_depth) * volume.block_slice_size +
|
||||
volume.level_offsets[level];
|
||||
@@ -961,11 +540,16 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
|
||||
return false;
|
||||
}
|
||||
info.linear_slice_stride = linear_stride;
|
||||
info.width = std::max((region.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max((region.height + element.tall - 1u) / element.tall, 1u);
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.depth = copy_depth;
|
||||
info.surface_z = block.block_depth == 1 ? static_cast<uint32_t>(region.dst_z) : 0;
|
||||
info.pitch = std::max((region.pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.surface_z = block.block_depth == 1
|
||||
? static_cast<uint32_t>(region.imageOffset.z)
|
||||
: 0;
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail_x = tail ? volume.tail_x[level] : 0;
|
||||
info.tail_y = tail ? volume.tail_y[level] : 0;
|
||||
info.tail = tail;
|
||||
@@ -993,23 +577,32 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
|
||||
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
|
||||
for (uint32_t z = 0; z < level_depth; z++) {
|
||||
const auto& region = regions[region_index++];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
GpuTileInfo info {};
|
||||
info.family = block.family;
|
||||
info.bytes_per_element = block.bytes_per_element;
|
||||
info.linear_offset = region.offset;
|
||||
info.linear_offset = region.bufferOffset;
|
||||
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z);
|
||||
if (!SetGpuTileSize(info.linear_offset, level_size.size, size, info.linear_size) ||
|
||||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), size,
|
||||
info.tiled_size)) {
|
||||
return false;
|
||||
}
|
||||
info.width = std::max((region.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max((region.height + element.tall - 1u) / element.tall, 1u);
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
|
||||
base_family == TileBlockFamily::Depth64KB
|
||||
? region.dst_layer
|
||||
? region.imageSubresource.baseArrayLayer
|
||||
: 0;
|
||||
info.pitch = std::max((region.pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail = tail;
|
||||
info.tail_x = tail ? level_size.x : 0;
|
||||
info.tail_y = tail ? level_size.y : 0;
|
||||
@@ -1037,32 +630,4 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureUploadGuestImage(VulkanImage& vk_obj, const void* src_data,
|
||||
uint64_t size, const std::vector<BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint64_t width,
|
||||
uint64_t height, uint32_t depth, uint64_t levels, const char* owner,
|
||||
vk::ImageLayout dst_layout) {
|
||||
if (fmt == 0) {
|
||||
EXIT("%s: texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64 " extent=%" PRIu64
|
||||
"x%" PRIu64 " depth=%u pitch=%u levels=%" PRIu64 "\n",
|
||||
owner, layout.tile, size, width, height, depth, layout.pitch, levels);
|
||||
}
|
||||
if (static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear) {
|
||||
Transfer::UploadImage(vk_obj, src_data, size, regions, dst_layout);
|
||||
return;
|
||||
}
|
||||
if (layout.tile_family == TileBlockFamily::Depth64KB && Prospero::IsFmaskTextureFormat(fmt)) {
|
||||
UploadFmaskIdentity(vk_obj, regions, dst_layout, owner);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<GpuTileInfo> infos;
|
||||
if (!TextureBuildGpuTileInfos(size, regions, layout, fmt, depth, levels, infos)) {
|
||||
EXIT("%s: GPU tiled upload unsupported: fmt=%u tile=%u size=%" PRIu64 " extent=%" PRIu64
|
||||
"x%" PRIu64 " depth=%u pitch=%u levels=%" PRIu64 "\n",
|
||||
owner, fmt, layout.tile, size, width, height, depth, layout.pitch, levels);
|
||||
}
|
||||
Transfer::UploadTiledImage(vk_obj, src_data, size, size, infos, regions, dst_layout);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -5,44 +5,13 @@
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
struct VulkanImage;
|
||||
|
||||
enum class TextureFormatUsage : uint32_t {
|
||||
None = 0,
|
||||
Sampled = 1u << 0u,
|
||||
Storage = 1u << 1u,
|
||||
};
|
||||
|
||||
constexpr TextureFormatUsage operator|(TextureFormatUsage lhs, TextureFormatUsage rhs) {
|
||||
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
constexpr TextureFormatUsage operator&(TextureFormatUsage lhs, TextureFormatUsage rhs) {
|
||||
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
constexpr TextureFormatUsage operator~(TextureFormatUsage usage) {
|
||||
return static_cast<TextureFormatUsage>(~static_cast<uint32_t>(usage));
|
||||
}
|
||||
|
||||
constexpr TextureFormatUsage& operator|=(TextureFormatUsage& lhs, TextureFormatUsage rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
constexpr bool TextureHasFormatUsage(TextureFormatUsage usage, TextureFormatUsage flag) {
|
||||
return (static_cast<uint32_t>(usage & flag) == static_cast<uint32_t>(flag));
|
||||
}
|
||||
|
||||
enum class TextureUploadDestination { MipLevels, MipAtlas };
|
||||
struct GpuTileInfo;
|
||||
|
||||
struct RenderTargetFormatInfo {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
@@ -53,6 +22,7 @@ struct RenderTargetFormatInfo {
|
||||
struct TextureUploadLayout {
|
||||
uint32_t tile = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t texel_block = 1;
|
||||
uint64_t slice_stride = 0;
|
||||
uint64_t source_slice_stride = 0;
|
||||
uint32_t first_tail_level = 16;
|
||||
@@ -62,75 +32,22 @@ struct TextureUploadLayout {
|
||||
TilePaddedSize padded_sizes[16] = {};
|
||||
};
|
||||
|
||||
struct TextureImageCreateParams {
|
||||
uint32_t fmt = 0;
|
||||
uint64_t width = 0;
|
||||
uint64_t height = 0;
|
||||
uint32_t base_level = 0;
|
||||
uint64_t levels = 1;
|
||||
uint32_t depth = 1;
|
||||
uint64_t type = 0;
|
||||
uint64_t swizzle = 0;
|
||||
TextureFormatUsage format_usage = TextureFormatUsage::Sampled;
|
||||
TextureFormatUsage required_format_usage = TextureFormatUsage::Sampled;
|
||||
TextureFormatUsage view_usage = TextureFormatUsage::Sampled;
|
||||
TextureUploadDestination image_layout = TextureUploadDestination::MipLevels;
|
||||
bool allow_cube_view = false;
|
||||
bool compatible_format_views = false;
|
||||
bool storage_swizzle_fallback = false;
|
||||
const char* owner = nullptr;
|
||||
};
|
||||
|
||||
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s);
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
|
||||
bool TextureCheckFormat(vk::ImageCreateInfo& image_info);
|
||||
bool TextureCheckStorageSwizzle(vk::ImageCreateInfo& image_info, vk::ComponentMapping& components);
|
||||
vk::ImageUsageFlags TextureGetUsage(TextureFormatUsage usage);
|
||||
vk::ImageUsageFlags TextureGetViewUsage(TextureFormatUsage usage);
|
||||
vk::Format TextureGetFormat(uint32_t fmt);
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
|
||||
uint32_t TextureGetAtlasSliceYStride(vk::Format format, uint32_t mip_height, uint32_t depth,
|
||||
uint64_t levels);
|
||||
uint32_t TextureCalcStackedImageHeight(vk::Format format, uint32_t height, uint32_t depth,
|
||||
uint64_t levels);
|
||||
uint32_t TextureCalcMipmapAtlasImageHeight(vk::Format format, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels);
|
||||
bool TextureIs3DTexture(uint64_t type);
|
||||
bool TextureIsCubeTexture(uint64_t type);
|
||||
bool TextureIsLayeredTexture(uint64_t type);
|
||||
bool TextureCanCreateCubeView(uint64_t type, uint32_t base_array, uint32_t layer_count);
|
||||
vk::ComponentMapping TextureCreateImage(VulkanImage& image,
|
||||
const TextureImageCreateParams& params);
|
||||
void TextureCreateImageViews(VulkanImage& vk_obj,
|
||||
vk::ComponentMapping components, uint64_t type, uint32_t base_array,
|
||||
uint32_t base_level, uint32_t level_count, uint32_t depth,
|
||||
bool allow_cube_view, TextureFormatUsage view_usage);
|
||||
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
|
||||
uint64_t levels, uint32_t depth, uint64_t pitch,
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner);
|
||||
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
|
||||
uint32_t slice);
|
||||
uint64_t TextureCalcUploadSize(const TextureUploadLayout& layout,
|
||||
const std::vector<BufferImageCopy>& regions, uint64_t levels,
|
||||
uint32_t depth);
|
||||
std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout& layout,
|
||||
vk::Format image_format, uint32_t width,
|
||||
uint32_t height, uint32_t depth,
|
||||
uint64_t levels, bool array_texture,
|
||||
bool volume_texture,
|
||||
TextureUploadDestination destination);
|
||||
std::vector<ImageBufferCopy>
|
||||
TextureBuildDownloadRegions(const std::vector<BufferImageCopy>& upload_regions);
|
||||
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>& regions,
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture);
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& infos);
|
||||
void TextureUploadGuestImage(VulkanImage& vk_obj, const void* src_data,
|
||||
uint64_t size, const std::vector<BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint64_t width,
|
||||
uint64_t height, uint32_t depth, uint64_t levels, const char* owner,
|
||||
vk::ImageLayout dst_layout);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#elif defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -28,8 +34,57 @@ constexpr uint64_t PAGE_SIZE = TRACKER_PAGE_SIZE;
|
||||
constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE;
|
||||
constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE;
|
||||
constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE;
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// The tracker reuses Win32 memory-protection tags as internal page-state values (on
|
||||
// Windows they come from <windows.h> and are what VirtualQuery returns). Mirror the
|
||||
// canonical Win32 numeric values so the shared state-machine logic is identical.
|
||||
constexpr uint32_t PAGE_NOACCESS = 0x01;
|
||||
constexpr uint32_t PAGE_READONLY = 0x02;
|
||||
constexpr uint32_t PAGE_READWRITE = 0x04;
|
||||
#endif
|
||||
constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE;
|
||||
|
||||
constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
|
||||
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
|
||||
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// Map the tracker's Win32-style protection tags to POSIX mprotect flags.
|
||||
static int PageProtToPosix(uint32_t protection) {
|
||||
switch (protection) {
|
||||
case PAGE_NOACCESS: return PROT_NONE;
|
||||
case PAGE_READONLY: return PROT_READ;
|
||||
case PAGE_READWRITE: return PROT_READ | PROT_WRITE;
|
||||
default: return PROT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Query the current protection of the page containing vaddr via the Mach VM map and
|
||||
// collapse it to the tracker's read/write tags (execute is irrelevant to write tracking).
|
||||
static uint32_t MachQueryPageProt(uint64_t vaddr) {
|
||||
auto region_addr = static_cast<mach_vm_address_t>(vaddr);
|
||||
mach_vm_size_t region_size = 0;
|
||||
vm_region_basic_info_data_64_t info {};
|
||||
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
mach_port_t object_name = MACH_PORT_NULL;
|
||||
|
||||
kern_return_t kr =
|
||||
mach_vm_region(mach_task_self(), ®ion_addr, ®ion_size, VM_REGION_BASIC_INFO_64,
|
||||
reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
|
||||
if (kr != KERN_SUCCESS || region_addr > vaddr) {
|
||||
return PAGE_NOACCESS; // no region covering vaddr
|
||||
}
|
||||
if ((info.protection & VM_PROT_WRITE) != 0) {
|
||||
return PAGE_READWRITE;
|
||||
}
|
||||
if ((info.protection & VM_PROT_READ) != 0) {
|
||||
return PAGE_READONLY;
|
||||
}
|
||||
return PAGE_NOACCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
thread_local bool g_in_fault_resolution = false;
|
||||
|
||||
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
|
||||
@@ -68,6 +123,8 @@ thread_local bool g_in_fault_resolution = false;
|
||||
uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__APPLE__)
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
#else
|
||||
FailFast();
|
||||
#endif
|
||||
@@ -135,6 +192,12 @@ struct PageManager::Impl {
|
||||
Fatal("unsupported host page size 0x%08" PRIx32,
|
||||
static_cast<uint32_t>(info.dwPageSize));
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
// Under Rosetta the host page size is 4 KB, matching TRACKER_PAGE_SIZE.
|
||||
if (static_cast<uint64_t>(getpagesize()) != PAGE_SIZE) {
|
||||
Fatal("unsupported host page size 0x%08" PRIx32,
|
||||
static_cast<uint32_t>(getpagesize()));
|
||||
}
|
||||
#else
|
||||
Fatal("page-fault invalidation is not implemented on this platform");
|
||||
#endif
|
||||
@@ -184,21 +247,22 @@ struct PageManager::Impl {
|
||||
|
||||
static uint32_t WatcherProtection(const PageState& page) {
|
||||
if (page.access_watchers != 0) {
|
||||
return PAGE_NOACCESS;
|
||||
return NO_ACCESS_PROTECTION;
|
||||
}
|
||||
if (page.write_watchers != 0) {
|
||||
return PAGE_READONLY;
|
||||
return READ_ONLY_PROTECTION;
|
||||
}
|
||||
return page.original_protection;
|
||||
}
|
||||
|
||||
static void PublishDelayedFaults(PageState& page, uint32_t old_protection,
|
||||
uint32_t new_protection) {
|
||||
if (old_protection == PAGE_NOACCESS && new_protection != PAGE_NOACCESS) {
|
||||
if (old_protection == NO_ACCESS_PROTECTION && new_protection != NO_ACCESS_PROTECTION) {
|
||||
page.late_read_pending = true;
|
||||
}
|
||||
if ((old_protection == PAGE_NOACCESS || old_protection == PAGE_READONLY) &&
|
||||
new_protection == PAGE_READWRITE) {
|
||||
if ((old_protection == NO_ACCESS_PROTECTION ||
|
||||
old_protection == READ_ONLY_PROTECTION) &&
|
||||
new_protection == READ_WRITE_PROTECTION) {
|
||||
page.late_write_pending = true;
|
||||
}
|
||||
}
|
||||
@@ -214,6 +278,14 @@ struct PageManager::Impl {
|
||||
vaddr, static_cast<uint32_t>(info.State), static_cast<uint32_t>(info.Protect));
|
||||
}
|
||||
return info.Protect;
|
||||
#elif defined(__APPLE__)
|
||||
const uint32_t protection = MachQueryPageProt(vaddr);
|
||||
if (protection != PAGE_READWRITE) {
|
||||
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (protection=0x%08" PRIx32
|
||||
")",
|
||||
vaddr, protection);
|
||||
}
|
||||
return protection;
|
||||
#else
|
||||
(void)vaddr;
|
||||
Fatal("page query is unsupported on this platform");
|
||||
@@ -234,6 +306,14 @@ struct PageManager::Impl {
|
||||
case PageFaultAccess::Write: return info.Protect == PAGE_READWRITE;
|
||||
default: return false;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
const uint32_t protection = MachQueryPageProt(vaddr);
|
||||
switch (access) {
|
||||
case PageFaultAccess::Read:
|
||||
return protection == PAGE_READONLY || protection == PAGE_READWRITE;
|
||||
case PageFaultAccess::Write: return protection == PAGE_READWRITE;
|
||||
default: return false;
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
return false;
|
||||
@@ -254,6 +334,18 @@ struct PageManager::Impl {
|
||||
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
|
||||
vaddr, static_cast<uint32_t>(old_protection), expected_old, protection);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
// mprotect cannot report the previous protection, so the expected_old comparison
|
||||
// is dropped; the tracker is the sole mutator of these pages and drives the
|
||||
// transition from its own shadow state.
|
||||
(void)expected_old;
|
||||
if (mprotect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
|
||||
PageProtToPosix(protection)) != 0) {
|
||||
if (fault_path) {
|
||||
FailFast("mprotect fault transition failed");
|
||||
}
|
||||
Fatal("mprotect failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr, protection);
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
(void)protection;
|
||||
@@ -297,8 +389,7 @@ bool PageManager::IsTracked(uint64_t vaddr) const noexcept {
|
||||
}
|
||||
|
||||
bool PageManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
|
||||
if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE ||
|
||||
size > ADDRESS_SIZE - vaddr) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
|
||||
return false;
|
||||
}
|
||||
const auto end = PageStart(vaddr + size - 1) + PAGE_SIZE;
|
||||
@@ -363,9 +454,6 @@ bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access)
|
||||
|
||||
void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
PageWatchMode mode) {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("page watchers changed during fault resolution");
|
||||
}
|
||||
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
|
||||
Fatal("invalid watcher mode");
|
||||
}
|
||||
@@ -401,11 +489,11 @@ void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
switch (new_protection) {
|
||||
case PAGE_NOACCESS:
|
||||
case NO_ACCESS_PROTECTION:
|
||||
page.late_read_pending = false;
|
||||
page.late_write_pending = false;
|
||||
break;
|
||||
case PAGE_READONLY: page.late_write_pending = false; break;
|
||||
case READ_ONLY_PROTECTION: page.late_write_pending = false; break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
@@ -500,6 +588,37 @@ PageManager::BackingWrite::~BackingWrite() {
|
||||
m_manager.EndBackingWrite(m_vaddr, m_size);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<PageManager::BackingWrite>>
|
||||
PageManager::ReserveBackingWrites(std::span<const RangeSet::Range> ranges) {
|
||||
if (ranges.empty()) {
|
||||
Fatal("cannot reserve empty backing-write ranges");
|
||||
}
|
||||
std::vector<std::unique_ptr<BackingWrite>> writes;
|
||||
writes.reserve(ranges.size());
|
||||
uint64_t begin = 0;
|
||||
uint64_t end = 0;
|
||||
for (const auto& range: ranges) {
|
||||
if (range.address == 0 || range.size == 0 || range.size > UINT64_MAX - range.address ||
|
||||
range.address + range.size > UINT64_MAX - (PAGE_SIZE - 1)) {
|
||||
Fatal("invalid backing-write range");
|
||||
}
|
||||
const auto page_begin = PageStart(range.address);
|
||||
const auto page_end = PageStart(range.address + range.size + PAGE_SIZE - 1);
|
||||
if (begin != 0 && page_begin > end) {
|
||||
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
|
||||
begin = 0;
|
||||
}
|
||||
if (begin == 0) {
|
||||
begin = page_begin;
|
||||
end = page_end;
|
||||
} else {
|
||||
end = std::max(end, page_end);
|
||||
}
|
||||
}
|
||||
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
|
||||
return writes;
|
||||
}
|
||||
|
||||
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("backing write began during fault resolution");
|
||||
@@ -539,7 +658,7 @@ void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (!page.resolving || page.backing_writer != writer) {
|
||||
FailFast("backing write ended without matching owner and resolving state");
|
||||
}
|
||||
const auto old_protection = PAGE_NOACCESS;
|
||||
const auto old_protection = NO_ACCESS_PROTECTION;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(address, new_protection, old_protection, false);
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -48,6 +51,8 @@ public:
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
[[nodiscard]] bool HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept;
|
||||
[[nodiscard]] std::vector<std::unique_ptr<BackingWrite>>
|
||||
ReserveBackingWrites(std::span<const RangeSet::Range> ranges);
|
||||
|
||||
private:
|
||||
void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -49,6 +51,9 @@ private:
|
||||
static uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__APPLE__)
|
||||
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
#else
|
||||
EXIT("region tracking thread identity is unsupported on this platform\n");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
#include "graphics/host_gpu/renderer/blitHelper.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "gpu_blit_shaders/gpu_blit_color_to_ms_depth_spv.h"
|
||||
#include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
: m_graphics(graphics), m_scheduler(scheduler) {
|
||||
vk::DescriptorSetLayoutBinding texture_binding {};
|
||||
texture_binding.binding = 0;
|
||||
texture_binding.descriptorType = vk::DescriptorType::eSampledImage;
|
||||
texture_binding.descriptorCount = 1;
|
||||
texture_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = 1;
|
||||
descriptor_info.pBindings = &texture_binding;
|
||||
RequireVulkanSuccess(m_graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
|
||||
&m_descriptor_layout),
|
||||
"create BlitHelper descriptor layout");
|
||||
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_descriptor_layout;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
|
||||
"create BlitHelper pipeline layout");
|
||||
|
||||
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
|
||||
m_fragment_shader =
|
||||
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
|
||||
}
|
||||
|
||||
BlitHelper::~BlitHelper() {
|
||||
for (const auto& pipeline: m_pipelines) {
|
||||
m_graphics.device.destroyPipeline(pipeline.handle, nullptr);
|
||||
}
|
||||
if (m_fragment_shader != nullptr) {
|
||||
m_graphics.device.destroyShaderModule(m_fragment_shader, nullptr);
|
||||
}
|
||||
if (m_vertex_shader != nullptr) {
|
||||
m_graphics.device.destroyShaderModule(m_vertex_shader, nullptr);
|
||||
}
|
||||
if (m_pipeline_layout != nullptr) {
|
||||
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
|
||||
}
|
||||
if (m_descriptor_layout != nullptr) {
|
||||
m_graphics.device.destroyDescriptorSetLayout(m_descriptor_layout, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
|
||||
EXIT_IF(code == nullptr || words == 0);
|
||||
vk::ShaderModuleCreateInfo create {};
|
||||
create.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
create.codeSize = words * sizeof(uint32_t);
|
||||
create.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
|
||||
"create BlitHelper shader module");
|
||||
return module;
|
||||
}
|
||||
|
||||
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
|
||||
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
|
||||
if (cached != m_pipelines.end()) {
|
||||
return cached->handle;
|
||||
}
|
||||
|
||||
const auto samples = vulkan_sample_count(key.samples);
|
||||
EXIT_IF(samples == vk::SampleCountFlagBits {} || key.format == vk::Format::eUndefined);
|
||||
|
||||
std::array<vk::PipelineShaderStageCreateInfo, 2> stages {};
|
||||
stages[0].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
|
||||
stages[0].module = m_vertex_shader;
|
||||
stages[0].pName = "main";
|
||||
stages[1].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
|
||||
stages[1].module = m_fragment_shader;
|
||||
stages[1].pName = "main";
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input {};
|
||||
vertex_input.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
|
||||
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
|
||||
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
|
||||
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
|
||||
vk::PipelineViewportStateCreateInfo viewport {};
|
||||
viewport.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
|
||||
viewport.viewportCount = 1;
|
||||
viewport.scissorCount = 1;
|
||||
vk::PipelineRasterizationStateCreateInfo rasterization {};
|
||||
rasterization.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
rasterization.polygonMode = vk::PolygonMode::eFill;
|
||||
rasterization.cullMode = vk::CullModeFlagBits::eNone;
|
||||
rasterization.lineWidth = 1.0f;
|
||||
vk::PipelineMultisampleStateCreateInfo multisample {};
|
||||
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
|
||||
multisample.rasterizationSamples = samples;
|
||||
vk::PipelineDepthStencilStateCreateInfo depth {};
|
||||
depth.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
|
||||
depth.depthTestEnable = VK_TRUE;
|
||||
depth.depthWriteEnable = VK_TRUE;
|
||||
depth.depthCompareOp = vk::CompareOp::eAlways;
|
||||
vk::PipelineColorBlendStateCreateInfo color_blend {};
|
||||
color_blend.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
const std::array dynamic_states {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
|
||||
vk::PipelineDynamicStateCreateInfo dynamic {};
|
||||
dynamic.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
|
||||
dynamic.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
|
||||
dynamic.pDynamicStates = dynamic_states.data();
|
||||
|
||||
vk::PipelineRenderingCreateInfo rendering {};
|
||||
rendering.sType = vk::StructureType::ePipelineRenderingCreateInfo;
|
||||
rendering.depthAttachmentFormat = key.format;
|
||||
|
||||
vk::GraphicsPipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
||||
create.pNext = &rendering;
|
||||
create.stageCount = static_cast<uint32_t>(stages.size());
|
||||
create.pStages = stages.data();
|
||||
create.pVertexInputState = &vertex_input;
|
||||
create.pInputAssemblyState = &input_assembly;
|
||||
create.pViewportState = &viewport;
|
||||
create.pRasterizationState = &rasterization;
|
||||
create.pMultisampleState = &multisample;
|
||||
create.pDepthStencilState = &depth;
|
||||
create.pColorBlendState = &color_blend;
|
||||
create.pDynamicState = &dynamic;
|
||||
create.layout = m_pipeline_layout;
|
||||
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createGraphicsPipelines(nullptr, 1, &create, nullptr, &pipeline),
|
||||
"create color-to-MS-depth pipeline");
|
||||
m_pipelines.push_back({key, pipeline});
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
const auto& source_info = source.info;
|
||||
const auto& destination_info = destination.info;
|
||||
EXIT_IF(DepthAspectTransferFormat(source_info.pixel_format) != vk::Format::eUndefined ||
|
||||
DepthAspectTransferFormat(destination_info.pixel_format) == vk::Format::eUndefined ||
|
||||
source_info.samples != 1 || destination_info.samples <= 1 ||
|
||||
destination_info.samples > 4 || source.backing.image_type != vk::ImageType::e2D ||
|
||||
destination.backing.image_type != vk::ImageType::e2D ||
|
||||
source_info.extent.width != destination_info.extent.width ||
|
||||
source_info.extent.height != destination_info.extent.height ||
|
||||
source_info.extent.depth != 1 || destination_info.extent.depth != 1 ||
|
||||
source.backing.image == nullptr || destination.backing.image == nullptr);
|
||||
m_scheduler.EndRendering();
|
||||
|
||||
ImageViewInfo source_view_info {};
|
||||
source_view_info.format = source_info.pixel_format;
|
||||
source_view_info.type = vk::ImageViewType::e2D;
|
||||
source_view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
source_view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
||||
const auto source_view = source.FindView(source_view_info);
|
||||
|
||||
ImageViewInfo destination_view_info {};
|
||||
destination_view_info.format = destination_info.pixel_format;
|
||||
destination_view_info.type = vk::ImageViewType::e2D;
|
||||
destination_view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
destination_view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
const auto destination_view = destination.FindView(destination_view_info);
|
||||
|
||||
auto& command_buffer = m_scheduler.Current();
|
||||
auto command = command_buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
|
||||
command);
|
||||
destination.Transit(ColorToMsDepthLayout,
|
||||
vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}, command);
|
||||
|
||||
vk::RenderingAttachmentInfo depth_attachment {};
|
||||
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
depth_attachment.imageView = destination_view;
|
||||
depth_attachment.imageLayout = ColorToMsDepthLayout;
|
||||
depth_attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
depth_attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
depth_attachment.clearValue.depthStencil = {0.0f, 0};
|
||||
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
rendering.renderArea.extent = {destination_info.extent.width, destination_info.extent.height};
|
||||
rendering.layerCount = 1;
|
||||
rendering.pDepthAttachment = &depth_attachment;
|
||||
command.beginRendering(&rendering);
|
||||
|
||||
vk::DescriptorImageInfo descriptor_image {};
|
||||
descriptor_image.imageView = source_view;
|
||||
descriptor_image.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
vk::WriteDescriptorSet descriptor_write {};
|
||||
descriptor_write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
descriptor_write.dstBinding = 0;
|
||||
descriptor_write.descriptorCount = 1;
|
||||
descriptor_write.descriptorType = vk::DescriptorType::eSampledImage;
|
||||
descriptor_write.pImageInfo = &descriptor_image;
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_pipeline_layout, 0, 1,
|
||||
&descriptor_write);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eGraphics,
|
||||
GetPipeline({destination_info.samples, destination_info.pixel_format}));
|
||||
|
||||
const vk::Viewport viewport {0.0f,
|
||||
0.0f,
|
||||
static_cast<float>(destination_info.extent.width),
|
||||
static_cast<float>(destination_info.extent.height),
|
||||
0.0f,
|
||||
1.0f};
|
||||
const vk::Rect2D scissor {{0, 0},
|
||||
{destination_info.extent.width, destination_info.extent.height}};
|
||||
command.setViewport(0, 1, &viewport);
|
||||
command.setScissor(0, 1, &scissor);
|
||||
command.draw(3, 1, 0, 0);
|
||||
command.endRendering();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <compare>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandScheduler;
|
||||
class Image;
|
||||
struct GraphicContext;
|
||||
|
||||
class BlitHelper final {
|
||||
public:
|
||||
inline static constexpr auto ColorToMsDepthLayout =
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
|
||||
BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler);
|
||||
~BlitHelper();
|
||||
KYTY_CLASS_NO_COPY(BlitHelper);
|
||||
|
||||
void ReinterpretColorAsMsDepth(Image& source, Image& destination);
|
||||
|
||||
private:
|
||||
struct PipelineKey {
|
||||
uint32_t samples = 1;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
|
||||
auto operator<=>(const PipelineKey&) const = default;
|
||||
};
|
||||
|
||||
struct Pipeline {
|
||||
PipelineKey key;
|
||||
vk::Pipeline handle = nullptr;
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
vk::DescriptorSetLayout m_descriptor_layout = nullptr;
|
||||
vk::PipelineLayout m_pipeline_layout = nullptr;
|
||||
vk::ShaderModule m_vertex_shader = nullptr;
|
||||
vk::ShaderModule m_fragment_shader = nullptr;
|
||||
std::vector<Pipeline> m_pipelines;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,42 +6,34 @@
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/renderer/streamBuffer.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
struct VulkanBuffer;
|
||||
class CommandBuffer;
|
||||
class CommandScheduler;
|
||||
class TextureCache;
|
||||
class ResourceMutex;
|
||||
|
||||
struct BufferImageCopySource {
|
||||
VulkanBuffer* buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
bool cpu_current = false;
|
||||
// True when the guest range was CPU-dirty before coherence resolution. ObtainBufferForImage
|
||||
// may consume that tracker state while publishing the same bytes to a cached buffer.
|
||||
bool cpu_dirty = false;
|
||||
};
|
||||
|
||||
struct BufferCacheRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
|
||||
struct BufferBinding {
|
||||
VulkanBuffer& buffer;
|
||||
uint64_t offset;
|
||||
std::shared_ptr<void> owner;
|
||||
vk::Buffer buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange& merged,
|
||||
BufferCacheRange candidate) noexcept;
|
||||
struct ImageBufferSource {
|
||||
Buffer* buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
bool gpu_owned = false;
|
||||
};
|
||||
|
||||
class BufferCache {
|
||||
public:
|
||||
@@ -50,7 +42,8 @@ public:
|
||||
return vaddr & (CACHING_PAGE_SIZE - 1);
|
||||
}
|
||||
|
||||
BufferCache(GraphicContext& graphics, PageManager& page_manager, ResourceMutex& resource_mutex);
|
||||
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
TextureCache& texture_cache, ResourceMutex& resource_mutex);
|
||||
~BufferCache();
|
||||
KYTY_CLASS_NO_COPY(BufferCache);
|
||||
|
||||
@@ -60,39 +53,91 @@ public:
|
||||
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
|
||||
bool is_written = false, bool is_read = true,
|
||||
bool is_formatted = false);
|
||||
// Emulator-owned, CPU-current scratch only. Guest ranges must use ObtainBuffer so page
|
||||
// ownership is resolved before any CPU access.
|
||||
[[nodiscard]] bool UploadHostData(CommandBuffer& command, const void* src, uint64_t size,
|
||||
uint64_t alignment, VulkanBuffer*& out_buffer,
|
||||
uint64_t& out_offset, uint64_t& out_range);
|
||||
[[nodiscard]] VulkanBuffer& ObtainNullBuffer(CommandBuffer& command);
|
||||
[[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
|
||||
void FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size, uint32_t value);
|
||||
void CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
|
||||
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept;
|
||||
[[nodiscard]] Buffer& GetGdsBuffer() noexcept { return m_gds_buffer; }
|
||||
[[nodiscard]] const Buffer& GetGdsBuffer() const noexcept { return m_gds_buffer; }
|
||||
[[nodiscard]] BufferBinding UploadTransient(const void* data, uint64_t size,
|
||||
uint64_t alignment);
|
||||
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
|
||||
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] std::pair<std::shared_ptr<Buffer>, uint64_t>
|
||||
ObtainBufferForImageWrite(uint64_t vaddr, uint64_t size);
|
||||
void DiscardGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
|
||||
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
|
||||
bool src_gds = false);
|
||||
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void PublishImageBacking(uint64_t vaddr, uint64_t size);
|
||||
void InvalidateImageAliases(uint64_t vaddr, uint64_t size);
|
||||
void BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
|
||||
void CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
|
||||
[[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size);
|
||||
void PublishImageBuffer(uint64_t vaddr, uint64_t size);
|
||||
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
|
||||
void SetTextureCache(TextureCache& texture_cache);
|
||||
|
||||
void ResetNullBuffer();
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
struct CachedBuffer;
|
||||
struct ReadbackWorker;
|
||||
friend struct BufferCacheTestAccess;
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
std::shared_ptr<VulkanBuffer> m_null_buffer;
|
||||
// TODO: add LRU cache
|
||||
struct CacheRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct CachedBuffer;
|
||||
struct DownloadCopy;
|
||||
struct DownloadRange;
|
||||
struct RetiredBuffer;
|
||||
struct FaultReadback;
|
||||
struct PendingBackingPublication;
|
||||
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
|
||||
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
|
||||
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
|
||||
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
|
||||
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
|
||||
}
|
||||
[[nodiscard]] static bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
|
||||
uint64_t right_size) noexcept;
|
||||
[[nodiscard]] static std::pair<uint64_t, uint64_t>
|
||||
DownloadEnvelope(const DownloadCopy& copy);
|
||||
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
|
||||
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
|
||||
const void* source, uint64_t size);
|
||||
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
|
||||
uint64_t size);
|
||||
[[nodiscard]] std::vector<DownloadRange>
|
||||
RecordDownloads(std::span<const DownloadCopy> copies);
|
||||
void PublishDownloads(std::span<const DownloadRange> downloads);
|
||||
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
|
||||
void RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr,
|
||||
uint64_t size, bool upload);
|
||||
void DiscardGpuDirtyBytesLocked(uint64_t vaddr, uint64_t size, const char* operation);
|
||||
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
Buffer m_gds_buffer;
|
||||
Common::Mutex m_mutex;
|
||||
std::shared_ptr<Buffer> m_null_buffer;
|
||||
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
|
||||
std::unique_ptr<ReadbackWorker> m_readback;
|
||||
std::unique_ptr<FaultReadback> m_fault_readback;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
RangeSet m_image_invalidated_ranges;
|
||||
std::mutex m_publication_mutex;
|
||||
std::vector<PendingBackingPublication> m_pending_backing_publications;
|
||||
MemoryTracker m_memory_tracker;
|
||||
StreamBuffer m_staging_buffer;
|
||||
StreamBuffer m_stream_buffer;
|
||||
StreamBuffer m_download_buffer;
|
||||
StreamBuffer m_device_buffer;
|
||||
PageManager& m_page_manager;
|
||||
TextureCache* m_texture_cache = nullptr;
|
||||
TextureCache& m_texture_cache;
|
||||
ResourceMutex& m_resource_mutex;
|
||||
uint64_t m_total_used_memory = 0;
|
||||
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_gc_tick = 0;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -10,12 +10,9 @@
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@@ -25,9 +22,11 @@ namespace Libs::Graphics {
|
||||
static std::atomic<uint32_t> g_render_color_log_count = 0;
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderColorInfo& r,
|
||||
uint32_t render_target_slice_offset, uint32_t render_target_slot,
|
||||
bool ignore_target_mask, bool reuse_existing_render_texture) {
|
||||
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderColorInfo& r,
|
||||
uint32_t render_target_slice_offset,
|
||||
uint32_t render_target_slot, bool ignore_target_mask,
|
||||
bool exact_format) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
|
||||
@@ -57,14 +56,17 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
|
||||
// No color output
|
||||
r.type = RenderColorType::NoColorOutput;
|
||||
r.desc = {};
|
||||
r.base_addr = 0;
|
||||
r.vulkan_buffer = nullptr;
|
||||
r.vulkan_view = nullptr;
|
||||
r.image_id = {};
|
||||
r.image_view = nullptr;
|
||||
r.format = vk::Format::eUndefined;
|
||||
r.extent = {};
|
||||
r.base_mip_level = 0;
|
||||
r.base_array_layer = 0;
|
||||
r.buffer_size = 0;
|
||||
r.samples = 1;
|
||||
r.export_mapping = {};
|
||||
r.color_clear_enable = false;
|
||||
r.color_clear_value = {};
|
||||
return;
|
||||
@@ -176,26 +178,33 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
pitch = width;
|
||||
}
|
||||
|
||||
TileSizeOffset mip_sizes[16] {};
|
||||
TilePaddedSize mip_padded[16] {};
|
||||
if (tile) {
|
||||
TileSizeAlign layout {};
|
||||
bool valid_layout = false;
|
||||
if (standard64) {
|
||||
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
|
||||
height, pitch, levels, rt.attrib3.tile_mode, &layout, nullptr,
|
||||
nullptr);
|
||||
height, pitch, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
|
||||
mip_padded);
|
||||
valid_layout = layout.size != 0 && layout.align == 65536;
|
||||
} else {
|
||||
valid_layout =
|
||||
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
|
||||
layout, rt.attrib.num_fragments)
|
||||
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
|
||||
levels, layout, nullptr, nullptr);
|
||||
levels, layout, mip_sizes, mip_padded);
|
||||
}
|
||||
if (!valid_layout) {
|
||||
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
|
||||
height, pitch, bytes_per_element, levels);
|
||||
}
|
||||
size = layout.size;
|
||||
EXIT_IF(size > UINT32_MAX);
|
||||
if (levels == 1) {
|
||||
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (rt.slice.slice_div64_minus1 != 0 &&
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
|
||||
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
|
||||
@@ -204,6 +213,11 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
}
|
||||
} else {
|
||||
size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
|
||||
if (size > UINT32_MAX) {
|
||||
EXIT("linear render-target slice exceeds the supported layout size\n");
|
||||
}
|
||||
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (size == 0 || size > UINT64_MAX / view.image_layers) {
|
||||
EXIT("render-target memory footprint is invalid\n");
|
||||
@@ -213,119 +227,69 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
EXIT("render-target backing range is invalid\n");
|
||||
}
|
||||
|
||||
auto video_image = Presentation::DisplayBufferFind(rt.base.addr, true);
|
||||
if (video_image.image != nullptr &&
|
||||
!IsSupportedDisplayRenderTargetTileMode(rt.attrib3.tile_mode)) {
|
||||
EXIT("unsupported display render-target tile mode: tile=%u expected=%u addr=0x%010" PRIx64
|
||||
" backing_size=0x%016" PRIx64 " video_size=0x%016" PRIx64 "\n",
|
||||
rt.attrib3.tile_mode, Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget),
|
||||
rt.base.addr, backing_size, video_image.size);
|
||||
}
|
||||
bool render_to_texture = view.base_layer != 0 || video_image.image == nullptr;
|
||||
if (!render_to_texture && (levels != 1 || rt.view.current_mip_level != 0)) {
|
||||
EXIT("mipmapped display render targets are unsupported\n");
|
||||
}
|
||||
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
|
||||
std::max(height >> rt.view.current_mip_level, 1u)};
|
||||
|
||||
auto decision_log_id = g_render_color_log_count.fetch_add(1);
|
||||
if (decision_log_id < 128 || !render_to_texture) {
|
||||
if (decision_log_id < 128) {
|
||||
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
|
||||
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
|
||||
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32
|
||||
" samples=%u tile=%s target=%s video_size=0x%016" PRIx64 " video_pitch=%" PRIu64 "\n",
|
||||
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
|
||||
rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level,
|
||||
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
|
||||
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear",
|
||||
render_to_texture ? "RenderTexture" : "DisplayBuffer", video_image.size,
|
||||
video_image.pitch);
|
||||
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
|
||||
}
|
||||
|
||||
if (render_to_texture) {
|
||||
(void)reuse_existing_render_texture;
|
||||
RenderTargetInfo target {};
|
||||
target.address = rt.base.addr;
|
||||
target.size = backing_size;
|
||||
target.format = target_format.format;
|
||||
target.width = width;
|
||||
target.height = height;
|
||||
target.pitch = pitch;
|
||||
target.bytes_per_element = target_format.bytes_per_element;
|
||||
target.tile_mode = rt.attrib3.tile_mode;
|
||||
target.levels = levels;
|
||||
target.layers = view.image_layers;
|
||||
target.samples = samples;
|
||||
auto& texture_cache = GetRenderContext().GetTextureCache();
|
||||
auto& buffer_vulkan = texture_cache.FindRenderTarget(buffer, target);
|
||||
r.type = RenderColorType::RenderTexture;
|
||||
r.base_addr = rt.base.addr;
|
||||
r.vulkan_buffer = &buffer_vulkan;
|
||||
r.vulkan_view = texture_cache.GetRenderTargetAttachmentView(
|
||||
buffer_vulkan, target.format, rt.view.current_mip_level, view.base_layer,
|
||||
view.layer_count);
|
||||
r.format = target.format;
|
||||
r.extent = view_extent;
|
||||
r.base_mip_level = rt.view.current_mip_level;
|
||||
r.buffer_size = backing_size;
|
||||
r.samples = samples;
|
||||
r.export_mapping = target_format.export_mapping;
|
||||
r.color_clear_enable = buffer_vulkan.initial_clear_pending;
|
||||
r.color_clear_value = {};
|
||||
} else {
|
||||
if (samples != 1) {
|
||||
EXIT("multisampled display render targets are unsupported\n");
|
||||
}
|
||||
const auto layout = static_cast<Prospero::ChannelLayout>(rt.info.format);
|
||||
const auto type = static_cast<Prospero::ChannelType>(rt.info.channel_type);
|
||||
const auto order = static_cast<Prospero::ChannelOrder>(rt.info.channel_order);
|
||||
|
||||
bool supported_display_format =
|
||||
(layout == Prospero::ChannelLayout::k8_8_8_8 &&
|
||||
(type == Prospero::ChannelType::kSrgb || type == Prospero::ChannelType::kUNorm) &&
|
||||
(order == Prospero::ChannelOrder::kStandard ||
|
||||
order == Prospero::ChannelOrder::kAlt)) ||
|
||||
(layout == Prospero::ChannelLayout::k10_10_10_2 &&
|
||||
type == Prospero::ChannelType::kUNorm &&
|
||||
(order == Prospero::ChannelOrder::kStandard ||
|
||||
order == Prospero::ChannelOrder::kAlt)) ||
|
||||
(layout == Prospero::ChannelLayout::k16_16_16_16 &&
|
||||
type == Prospero::ChannelType::kFloat &&
|
||||
(order == Prospero::ChannelOrder::kStandard || order == Prospero::ChannelOrder::kAlt));
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!supported_display_format);
|
||||
|
||||
// Display buffer
|
||||
if (video_image.size != size) {
|
||||
LOGF("RenderColorTarget: display buffer size differs from render target span, "
|
||||
"video_size=0x%016" PRIx64 " render_size=0x%016" PRIx64 "\n",
|
||||
video_image.size, size);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(video_image.size < size);
|
||||
EXIT_NOT_IMPLEMENTED(video_image.pitch != pitch);
|
||||
r.type = RenderColorType::DisplayBuffer;
|
||||
r.base_addr = rt.base.addr;
|
||||
r.vulkan_buffer = video_image.image;
|
||||
r.vulkan_view = video_image.image->image_view[VulkanImage::VIEW_DEFAULT];
|
||||
r.format = video_image.image->format;
|
||||
r.extent = video_image.image->extent;
|
||||
r.base_mip_level = 0;
|
||||
r.buffer_size = video_image.size;
|
||||
r.samples = 1;
|
||||
r.export_mapping = target_format.export_mapping;
|
||||
}
|
||||
}
|
||||
|
||||
void MarkRenderTargetGpuWritten(const RenderColorInfo& target) {
|
||||
const bool with_color = target.vulkan_buffer != nullptr;
|
||||
|
||||
if (with_color) {
|
||||
if (target.type == RenderColorType::RenderTexture ||
|
||||
target.type == RenderColorType::DisplayBuffer) {
|
||||
GetRenderContext().GetTextureCache().MarkGpuWritten(*target.vulkan_buffer);
|
||||
} else {
|
||||
EXIT("unknown writable render-color resource type\n");
|
||||
}
|
||||
TextureCache::ImageDesc desc {};
|
||||
desc.type = TextureCache::BindingType::RenderTarget;
|
||||
desc.info.data = {rt.base.addr, backing_size};
|
||||
desc.info.pixel_format = target_format.format;
|
||||
desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
|
||||
desc.info.type = Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {width, height, 1};
|
||||
desc.info.resources = {levels, view.image_layers};
|
||||
desc.info.pitch = pitch;
|
||||
desc.info.bytes_per_block = bytes_per_element;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
const auto level_offset =
|
||||
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
|
||||
const auto level_size =
|
||||
static_cast<uint64_t>(mip_sizes[level].src_size != 0 ? mip_sizes[level].src_size
|
||||
: mip_sizes[level].size) *
|
||||
view.image_layers;
|
||||
desc.info.mip_layout[level] = {
|
||||
level_offset,
|
||||
level_size,
|
||||
mip_padded[level].width,
|
||||
mip_padded[level].height,
|
||||
};
|
||||
}
|
||||
desc.view_info.format = target_format.format;
|
||||
desc.view_info.type =
|
||||
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
desc.view_info.base_level = rt.view.current_mip_level;
|
||||
desc.view_info.level_count = 1;
|
||||
desc.view_info.base_layer = view.base_layer;
|
||||
desc.view_info.layer_count = view.layer_count;
|
||||
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
r.desc = std::move(desc);
|
||||
r.image_id = texture_cache.FindImage(r.desc, exact_format);
|
||||
r.type = RenderColorType::RenderTexture;
|
||||
r.base_addr = rt.base.addr;
|
||||
r.image_view = nullptr;
|
||||
r.format = r.desc.view_info.format;
|
||||
r.extent = view_extent;
|
||||
r.base_mip_level = rt.view.current_mip_level;
|
||||
r.buffer_size = backing_size;
|
||||
r.samples = samples;
|
||||
r.export_mapping = target_format.export_mapping;
|
||||
r.color_clear_enable = false;
|
||||
r.color_clear_value = {};
|
||||
BindRenderTarget(r.image_id);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/textureCache.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -10,18 +11,17 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
struct VulkanImage;
|
||||
|
||||
enum class RenderColorType {
|
||||
NoColorOutput,
|
||||
DisplayBuffer,
|
||||
RenderTexture,
|
||||
};
|
||||
|
||||
struct RenderColorInfo {
|
||||
RenderColorType type = RenderColorType::NoColorOutput;
|
||||
VulkanImage* vulkan_buffer = nullptr;
|
||||
vk::ImageView vulkan_view = nullptr;
|
||||
RenderColorType type = RenderColorType::NoColorOutput;
|
||||
TextureCache::ImageDesc desc;
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::Extent2D extent = {};
|
||||
uint32_t base_mip_level = 0;
|
||||
@@ -35,13 +35,6 @@ struct RenderColorInfo {
|
||||
vk::ClearColorValue color_clear_value {};
|
||||
};
|
||||
|
||||
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderColorInfo& r,
|
||||
uint32_t render_target_slice_offset = 0,
|
||||
uint32_t render_target_slot = UINT32_MAX,
|
||||
bool ignore_target_mask = false,
|
||||
bool reuse_existing_render_texture = false);
|
||||
void MarkRenderTargetGpuWritten(const RenderColorInfo& target);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
static thread_local CommandScheduler* g_deferred_callback_scheduler = nullptr;
|
||||
|
||||
void CommandSlot::Reset() {
|
||||
EXIT_IF(buffer == nullptr);
|
||||
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
}
|
||||
|
||||
CommandScheduler::CommandPool::~CommandPool() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CommandScheduler::CommandPool::Create(GraphicContext& graphics) {
|
||||
EXIT_IF(m_pool != nullptr || m_graphics != nullptr ||
|
||||
graphics.queue_family == static_cast<uint32_t>(-1));
|
||||
m_graphics = &graphics;
|
||||
|
||||
vk::CommandPoolCreateInfo create {};
|
||||
create.sType = vk::StructureType::eCommandPoolCreateInfo;
|
||||
create.queueFamilyIndex = graphics.queue_family;
|
||||
create.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
||||
const auto result = graphics.device.createCommandPool(&create, nullptr, &m_pool);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
|
||||
}
|
||||
|
||||
CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
|
||||
EXIT_IF(m_graphics == nullptr);
|
||||
auto& graphics = *m_graphics;
|
||||
|
||||
vk::CommandBufferAllocateInfo allocate {};
|
||||
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
|
||||
allocate.commandPool = m_pool;
|
||||
allocate.level = vk::CommandBufferLevel::ePrimary;
|
||||
allocate.commandBufferCount = 1;
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
EXIT_IF(graphics.device.allocateCommandBuffers(&allocate, &buffer) != vk::Result::eSuccess);
|
||||
|
||||
vk::FenceCreateInfo fence_create {};
|
||||
fence_create.sType = vk::StructureType::eFenceCreateInfo;
|
||||
fence_create.flags = vk::FenceCreateFlagBits::eSignaled;
|
||||
vk::Fence fence = nullptr;
|
||||
if (graphics.device.createFence(&fence_create, nullptr, &fence) != vk::Result::eSuccess) {
|
||||
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
|
||||
EXIT("failed to create command-buffer fence\n");
|
||||
}
|
||||
|
||||
auto& slot = m_slots.emplace_back();
|
||||
slot.pool_mutex = &m_mutex;
|
||||
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
|
||||
slot.buffer = buffer;
|
||||
slot.fence = fence;
|
||||
return &slot;
|
||||
}
|
||||
|
||||
CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_pool == nullptr) {
|
||||
Create(graphics);
|
||||
}
|
||||
EXIT_IF(m_graphics != &graphics);
|
||||
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
|
||||
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
|
||||
slot->busy = true;
|
||||
slot->Reset();
|
||||
return slot;
|
||||
}
|
||||
|
||||
void CommandScheduler::CommandPool::Destroy() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_pool == nullptr) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
|
||||
EXIT_IF(m_graphics == nullptr);
|
||||
for (const auto& slot: m_slots) {
|
||||
m_graphics->device.destroyFence(slot.fence, nullptr);
|
||||
}
|
||||
m_graphics->device.destroyCommandPool(m_pool, nullptr);
|
||||
m_slots.clear();
|
||||
m_pool = nullptr;
|
||||
m_graphics = nullptr;
|
||||
}
|
||||
|
||||
bool CommandScheduler::InDeferredOperation() noexcept {
|
||||
return g_deferred_callback_scheduler != nullptr;
|
||||
}
|
||||
|
||||
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
|
||||
: m_master(graphics), m_context(context), m_graphics(graphics),
|
||||
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
|
||||
|
||||
CommandScheduler::~CommandScheduler() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void CommandScheduler::Shutdown() {
|
||||
{
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
if (m_operation_state == OperationState::Closed) {
|
||||
return;
|
||||
}
|
||||
if (g_deferred_callback_scheduler == this) {
|
||||
EXIT_IF(m_operation_state == OperationState::Open);
|
||||
// A priority callback cannot join its own runner, while a normal callback can be
|
||||
// executing inside the shutdown owner's final PopPendingOperations. The owning
|
||||
// thread will finish shutdown after this callback returns.
|
||||
return;
|
||||
}
|
||||
if (m_operation_state == OperationState::Draining) {
|
||||
m_operation_available.wait(
|
||||
lock, [this] { return m_operation_state == OperationState::Closed; });
|
||||
return;
|
||||
}
|
||||
m_operation_state = OperationState::Draining;
|
||||
}
|
||||
if (Active() && m_recording) {
|
||||
Finish();
|
||||
}
|
||||
DrainPriorityOperations();
|
||||
m_priority_thread.request_stop();
|
||||
m_operation_available.notify_all();
|
||||
if (m_priority_thread.joinable()) {
|
||||
m_priority_thread.join();
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(m_operation_mutex);
|
||||
EXIT_IF(!m_pending_operations.empty() || !m_priority_operations.empty() ||
|
||||
m_priority_active);
|
||||
m_operation_state = OperationState::Closed;
|
||||
}
|
||||
m_operation_available.notify_all();
|
||||
}
|
||||
|
||||
void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config,
|
||||
HW::Shader& shaders) {
|
||||
{
|
||||
std::lock_guard lock(m_operation_mutex);
|
||||
EXIT_IF(m_operation_state != OperationState::Open);
|
||||
}
|
||||
m_registers = ®isters;
|
||||
m_user_config = &user_config;
|
||||
m_shaders = &shaders;
|
||||
|
||||
if (!Active()) {
|
||||
for (auto& buffer: m_buffers) {
|
||||
buffer = std::make_unique<RenderCommandBuffer>(*this);
|
||||
}
|
||||
m_current = 0;
|
||||
}
|
||||
|
||||
BindCurrent();
|
||||
if (!m_recording) {
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScheduler::BeginRendering(const RenderState& state) {
|
||||
Current().BeginRendering(state);
|
||||
}
|
||||
|
||||
void CommandScheduler::EndRendering() {
|
||||
if (Active() && m_recording) {
|
||||
Current().EndRendering();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScheduler::Flush() {
|
||||
SubmitInfo submit;
|
||||
Flush(submit);
|
||||
}
|
||||
|
||||
void CommandScheduler::Flush(SubmitInfo& submit) {
|
||||
SubmitCurrent(submit);
|
||||
BeginNext();
|
||||
}
|
||||
|
||||
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
|
||||
SubmitInfo submit;
|
||||
auto& submitted = SubmitCurrent(submit);
|
||||
BeginNext();
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CommandScheduler::Finish() {
|
||||
CheckActive();
|
||||
const auto tick = CurrentTick();
|
||||
if (m_recording) {
|
||||
SubmitInfo submit;
|
||||
SubmitCurrent(submit);
|
||||
}
|
||||
for (auto& buffer: m_buffers) {
|
||||
buffer->WaitForFenceAndReset();
|
||||
}
|
||||
m_master.Wait(tick);
|
||||
PopPendingOperations();
|
||||
BindCurrent();
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
|
||||
void CommandScheduler::FinishCurrent() {
|
||||
SubmitInfo submit;
|
||||
auto& submitted = SubmitCurrent(submit);
|
||||
submitted.WaitForFenceAndReset();
|
||||
m_master.Refresh();
|
||||
PopPendingOperations();
|
||||
submitted.Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
|
||||
void CommandScheduler::Wait(uint64_t tick) {
|
||||
CheckActive();
|
||||
EXIT_IF(tick > CurrentTick());
|
||||
if (tick >= CurrentTick()) {
|
||||
// A stream-buffer wrap can wait while a draw is being prepared through a reference to
|
||||
// Current(). Recycle the same command object so that reference remains valid.
|
||||
FinishCurrent();
|
||||
return;
|
||||
}
|
||||
m_master.Wait(tick);
|
||||
PopPendingOperations();
|
||||
}
|
||||
|
||||
void CommandScheduler::PopPendingOperations() {
|
||||
m_master.Refresh();
|
||||
for (;;) {
|
||||
Common::UniqueFunction<void> callback;
|
||||
{
|
||||
std::lock_guard lock(m_operation_mutex);
|
||||
if (m_pending_operations.empty() ||
|
||||
!m_master.IsFree(m_pending_operations.front().tick)) {
|
||||
return;
|
||||
}
|
||||
callback = std::move(m_pending_operations.front().callback);
|
||||
m_pending_operations.pop();
|
||||
}
|
||||
RunOperation(std::move(callback));
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScheduler::DeferOperation(Common::UniqueFunction<void>&& operation) {
|
||||
CheckActive();
|
||||
EXIT_IF(!operation);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
if (m_operation_state == OperationState::Open) {
|
||||
m_pending_operations.push({std::move(operation), CurrentTick()});
|
||||
return;
|
||||
}
|
||||
if (g_deferred_callback_scheduler == this) {
|
||||
lock.unlock();
|
||||
operation();
|
||||
return;
|
||||
}
|
||||
m_operation_available.wait(lock,
|
||||
[this] { return m_operation_state == OperationState::Closed; });
|
||||
lock.unlock();
|
||||
operation();
|
||||
}
|
||||
|
||||
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
|
||||
CheckActive();
|
||||
EXIT_IF(!operation);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
if (m_operation_state == OperationState::Open) {
|
||||
m_priority_operations.push({std::move(operation), CurrentTick()});
|
||||
lock.unlock();
|
||||
m_operation_available.notify_one();
|
||||
return;
|
||||
}
|
||||
if (g_deferred_callback_scheduler == this) {
|
||||
lock.unlock();
|
||||
operation();
|
||||
return;
|
||||
}
|
||||
m_operation_available.wait(lock,
|
||||
[this] { return m_operation_state == OperationState::Closed; });
|
||||
lock.unlock();
|
||||
operation();
|
||||
}
|
||||
|
||||
void CommandScheduler::PriorityOperationsThread(std::stop_token stop) {
|
||||
while (!stop.stop_requested()) {
|
||||
PendingOperation operation;
|
||||
{
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
m_operation_available.wait(lock, [this, &stop] {
|
||||
return stop.stop_requested() || !m_priority_operations.empty();
|
||||
});
|
||||
if (stop.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
operation = std::move(m_priority_operations.front());
|
||||
m_priority_operations.pop();
|
||||
m_priority_active = true;
|
||||
m_priority_active_tick = operation.tick;
|
||||
}
|
||||
m_master.Wait(operation.tick);
|
||||
if (!stop.stop_requested()) {
|
||||
RunOperation(std::move(operation.callback));
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(m_operation_mutex);
|
||||
m_priority_active = false;
|
||||
m_priority_active_tick = 0;
|
||||
}
|
||||
m_operation_available.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScheduler::DrainPriorityOperations() {
|
||||
EXIT_IF(g_deferred_callback_scheduler == this);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
m_operation_available.wait(
|
||||
lock, [this] { return m_priority_operations.empty() && !m_priority_active; });
|
||||
}
|
||||
|
||||
void CommandScheduler::WaitPriorityOperations(uint64_t tick) {
|
||||
EXIT_IF(g_deferred_callback_scheduler == this);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
m_operation_available.wait(lock, [this, tick] {
|
||||
const bool active_before_or_at =
|
||||
m_priority_active && m_priority_active_tick <= tick;
|
||||
const bool queued_before_or_at =
|
||||
!m_priority_operations.empty() && m_priority_operations.front().tick <= tick;
|
||||
return !active_before_or_at && !queued_before_or_at;
|
||||
});
|
||||
}
|
||||
|
||||
void CommandScheduler::RunOperation(Common::UniqueFunction<void>&& operation) {
|
||||
auto* previous = g_deferred_callback_scheduler;
|
||||
g_deferred_callback_scheduler = this;
|
||||
operation();
|
||||
g_deferred_callback_scheduler = previous;
|
||||
}
|
||||
|
||||
bool CommandScheduler::IsFree(uint64_t tick) {
|
||||
if (m_master.IsFree(tick)) {
|
||||
return true;
|
||||
}
|
||||
m_master.Refresh();
|
||||
return m_master.IsFree(tick);
|
||||
}
|
||||
|
||||
CommandSlot* CommandScheduler::AllocateCommandBuffer() {
|
||||
return m_command_pool.Allocate(m_graphics);
|
||||
}
|
||||
|
||||
uint64_t CommandScheduler::NextSubmitSequence() noexcept {
|
||||
return m_submit_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
}
|
||||
|
||||
void CommandScheduler::CheckActive() const {
|
||||
EXIT_IF(!Active() || m_current >= BufferCount);
|
||||
}
|
||||
|
||||
RenderCommandBuffer& CommandScheduler::Current() const {
|
||||
CheckActive();
|
||||
EXIT_IF(m_buffers[m_current] == nullptr);
|
||||
return *m_buffers[m_current];
|
||||
}
|
||||
|
||||
void CommandScheduler::BindCurrent() const {
|
||||
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
|
||||
Current().Bind(*m_registers, *m_user_config, *m_shaders);
|
||||
}
|
||||
|
||||
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
|
||||
CheckActive();
|
||||
EXIT_IF(!m_recording);
|
||||
auto& submitted = Current();
|
||||
submitted.End();
|
||||
const auto signal_tick = m_master.NextTick();
|
||||
submit.AddSignal(m_master.Handle(), signal_tick);
|
||||
submitted.Execute(submit);
|
||||
m_recording = false;
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CommandScheduler::BeginNext() {
|
||||
EXIT_IF(m_recording);
|
||||
m_current = (m_current + 1) % BufferCount;
|
||||
Current().WaitForFenceAndReset();
|
||||
PopPendingOperations();
|
||||
BindCurrent();
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,127 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/uniqueFunction.h"
|
||||
#include "graphics/host_gpu/renderer/masterSemaphore.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct CommandSlot {
|
||||
Common::Mutex* pool_mutex = nullptr;
|
||||
uint32_t id = 0;
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
vk::Fence fence = nullptr;
|
||||
bool busy = false;
|
||||
|
||||
void Reset();
|
||||
};
|
||||
|
||||
class CommandScheduler {
|
||||
public:
|
||||
static constexpr int BufferCount = 8;
|
||||
|
||||
CommandScheduler(RenderContext& context, GraphicContext& graphics);
|
||||
~CommandScheduler();
|
||||
KYTY_CLASS_NO_COPY(CommandScheduler);
|
||||
|
||||
void Begin(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders);
|
||||
void BeginRendering(const RenderState& state);
|
||||
void EndRendering();
|
||||
void Flush();
|
||||
void Flush(SubmitInfo& submit);
|
||||
CommandBuffer& FlushAndGetSubmitted();
|
||||
void Finish();
|
||||
void FinishCurrent();
|
||||
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
|
||||
// the priority runner cannot join itself.
|
||||
void Shutdown();
|
||||
void Wait(uint64_t tick);
|
||||
void PopPendingOperations();
|
||||
void DrainPriorityOperations();
|
||||
void WaitPriorityOperations(uint64_t tick);
|
||||
void DeferOperation(Common::UniqueFunction<void>&& operation);
|
||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] static bool InDeferredOperation() noexcept;
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
|
||||
void CheckActive() const;
|
||||
RenderCommandBuffer& Current() const;
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
|
||||
[[nodiscard]] bool IsFree(uint64_t tick);
|
||||
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
|
||||
|
||||
private:
|
||||
class CommandPool {
|
||||
public:
|
||||
CommandPool() = default;
|
||||
~CommandPool();
|
||||
KYTY_CLASS_NO_COPY(CommandPool);
|
||||
|
||||
CommandSlot* Allocate(GraphicContext& graphics);
|
||||
|
||||
private:
|
||||
void Create(GraphicContext& graphics);
|
||||
CommandSlot* CreateSlot();
|
||||
void Destroy();
|
||||
|
||||
GraphicContext* m_graphics = nullptr;
|
||||
Common::Mutex m_mutex;
|
||||
vk::CommandPool m_pool = nullptr;
|
||||
std::deque<CommandSlot> m_slots;
|
||||
};
|
||||
|
||||
enum class OperationState { Open, Draining, Closed };
|
||||
|
||||
struct PendingOperation {
|
||||
Common::UniqueFunction<void> callback;
|
||||
uint64_t tick = 0;
|
||||
};
|
||||
|
||||
void BindCurrent() const;
|
||||
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
|
||||
void BeginNext();
|
||||
void PriorityOperationsThread(std::stop_token stop);
|
||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
|
||||
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
|
||||
|
||||
MasterSemaphore m_master;
|
||||
RenderContext& m_context;
|
||||
GraphicContext& m_graphics;
|
||||
CommandPool m_command_pool;
|
||||
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
|
||||
std::queue<PendingOperation> m_pending_operations;
|
||||
std::queue<PendingOperation> m_priority_operations;
|
||||
std::mutex m_operation_mutex;
|
||||
std::condition_variable m_operation_available;
|
||||
std::jthread m_priority_thread;
|
||||
bool m_priority_active = false;
|
||||
uint64_t m_priority_active_tick = 0;
|
||||
OperationState m_operation_state = OperationState::Open;
|
||||
int m_current = -1;
|
||||
bool m_recording = false;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
std::atomic<uint64_t> m_submit_sequence = 0;
|
||||
|
||||
friend class CommandBuffer;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
|
||||
@@ -9,63 +9,17 @@
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
namespace Libs::Graphics {
|
||||
static std::atomic<uint64_t> g_command_buffer_submit_seq = 0;
|
||||
|
||||
static void ResetNativeCommandBuffer(vk::CommandBuffer buffer) {
|
||||
EXIT_IF(buffer == nullptr);
|
||||
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
}
|
||||
|
||||
struct CommandSlot {
|
||||
Common::Mutex* pool_mutex = nullptr;
|
||||
uint32_t id = 0;
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
vk::Fence fence = nullptr;
|
||||
bool busy = false;
|
||||
};
|
||||
|
||||
class ThreadCommandPool {
|
||||
public:
|
||||
ThreadCommandPool() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(ThreadCommandPool);
|
||||
|
||||
CommandSlot* Allocate();
|
||||
void Destroy();
|
||||
|
||||
private:
|
||||
void Create();
|
||||
CommandSlot* CreateSlot();
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
vk::CommandPool m_pool = nullptr;
|
||||
std::deque<CommandSlot> m_slots;
|
||||
};
|
||||
|
||||
static RenderContext* g_render_ctx = nullptr;
|
||||
static thread_local ThreadCommandPool g_command_pool;
|
||||
|
||||
RenderContext& GetRenderContext() noexcept {
|
||||
return *g_render_ctx;
|
||||
}
|
||||
|
||||
FenceResourceRetainer::~FenceResourceRetainer() {
|
||||
if (!m_resources.empty()) {
|
||||
@@ -88,87 +42,12 @@ void FenceResourceRetainer::ReleaseAfterFence() noexcept {
|
||||
m_resources.clear();
|
||||
}
|
||||
|
||||
void GraphicsRenderInit(GraphicContext& graphics) {
|
||||
g_render_ctx = new RenderContext(graphics);
|
||||
}
|
||||
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
|
||||
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
|
||||
m_slot(scheduler.AllocateCommandBuffer()) {}
|
||||
|
||||
void GraphicsRenderReleaseThreadCommandPool() {
|
||||
g_command_pool.Destroy();
|
||||
}
|
||||
|
||||
CommandBuffer::CommandBuffer()
|
||||
: m_graphics(GetRenderContext().GetGraphics()), m_slot(g_command_pool.Allocate()),
|
||||
m_host_stream(m_graphics) {}
|
||||
|
||||
void ThreadCommandPool::Create() {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
EXIT_IF(m_pool != nullptr || graphics.queue_family == static_cast<uint32_t>(-1));
|
||||
|
||||
vk::CommandPoolCreateInfo pool_info {};
|
||||
pool_info.sType = vk::StructureType::eCommandPoolCreateInfo;
|
||||
pool_info.pNext = nullptr;
|
||||
pool_info.queueFamilyIndex = graphics.queue_family;
|
||||
pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
||||
|
||||
const auto result = graphics.device.createCommandPool(&pool_info, nullptr, &m_pool);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
|
||||
}
|
||||
|
||||
CommandSlot* ThreadCommandPool::CreateSlot() {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
vk::CommandBufferAllocateInfo alloc_info {};
|
||||
alloc_info.sType = vk::StructureType::eCommandBufferAllocateInfo;
|
||||
alloc_info.commandPool = m_pool;
|
||||
alloc_info.level = vk::CommandBufferLevel::ePrimary;
|
||||
alloc_info.commandBufferCount = 1;
|
||||
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
if (graphics.device.allocateCommandBuffers(&alloc_info, &buffer) != vk::Result::eSuccess) {
|
||||
EXIT("Can't allocate command buffers");
|
||||
}
|
||||
|
||||
vk::FenceCreateInfo fence_info {};
|
||||
fence_info.sType = vk::StructureType::eFenceCreateInfo;
|
||||
fence_info.flags = vk::FenceCreateFlagBits::eSignaled;
|
||||
|
||||
vk::Fence fence = nullptr;
|
||||
if (graphics.device.createFence(&fence_info, nullptr, &fence) != vk::Result::eSuccess) {
|
||||
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
|
||||
EXIT("Can't create fence");
|
||||
}
|
||||
auto& slot = m_slots.emplace_back();
|
||||
slot.pool_mutex = &m_mutex;
|
||||
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
|
||||
slot.buffer = buffer;
|
||||
slot.fence = fence;
|
||||
return &slot;
|
||||
}
|
||||
|
||||
CommandSlot* ThreadCommandPool::Allocate() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_pool == nullptr) {
|
||||
Create();
|
||||
}
|
||||
auto it = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
|
||||
auto* slot = it != m_slots.end() ? &*it : CreateSlot();
|
||||
slot->busy = true;
|
||||
ResetNativeCommandBuffer(slot->buffer);
|
||||
return slot;
|
||||
}
|
||||
|
||||
void ThreadCommandPool::Destroy() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_pool == nullptr) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
for (const auto& slot: m_slots) {
|
||||
graphics.device.destroyFence(slot.fence, nullptr);
|
||||
}
|
||||
graphics.device.destroyCommandPool(m_pool, nullptr);
|
||||
m_slots.clear();
|
||||
m_pool = nullptr;
|
||||
CommandBuffer::~CommandBuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
bool CommandBuffer::IsInvalid() const {
|
||||
@@ -190,18 +69,19 @@ void CommandBuffer::Release() {
|
||||
|
||||
WaitForFence();
|
||||
|
||||
m_host_stream.Release();
|
||||
|
||||
m_slot->busy = false;
|
||||
ResetNativeCommandBuffer(m_slot->buffer);
|
||||
m_slot->Reset();
|
||||
ReleaseResourcesAfterFence();
|
||||
m_slot = nullptr;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsInvalid());
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteAfterFence(VulkanBuffer& buffer) {
|
||||
m_delete_after_fence.push_back(&buffer);
|
||||
void CommandBuffer::RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer) {
|
||||
if (IsInvalid() || m_execute || buffer == nullptr || buffer->buffer == nullptr) {
|
||||
EXIT("cannot retire a buffer on an invalid or submitted command buffer\n");
|
||||
}
|
||||
m_retired_buffers.push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
|
||||
@@ -217,12 +97,13 @@ void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
|
||||
|
||||
void CommandBuffer::RecycleDescriptorsAfterFence() {
|
||||
for (auto* set: m_descriptor_sets_after_fence) {
|
||||
GetRenderContext().GetDescriptorCache().Recycle(*set);
|
||||
m_context.GetDescriptorCache().Recycle(*set);
|
||||
}
|
||||
m_descriptor_sets_after_fence.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::Begin() const {
|
||||
EXIT_IF(m_rendering);
|
||||
auto buffer = Handle();
|
||||
|
||||
vk::CommandBufferBeginInfo begin_info {};
|
||||
@@ -237,6 +118,7 @@ void CommandBuffer::Begin() const {
|
||||
}
|
||||
|
||||
void CommandBuffer::End() const {
|
||||
EndRendering();
|
||||
auto buffer = Handle();
|
||||
|
||||
auto result = buffer.end();
|
||||
@@ -255,39 +137,34 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
|
||||
m_debug_arg4 = arg4;
|
||||
}
|
||||
|
||||
void CommandBuffer::Execute() {
|
||||
Submit(nullptr, {}, nullptr);
|
||||
}
|
||||
|
||||
void CommandBuffer::ExecuteWithSemaphore(vk::Semaphore wait_semaphore,
|
||||
vk::PipelineStageFlags wait_stage,
|
||||
vk::Semaphore signal_semaphore) {
|
||||
EXIT_IF(wait_semaphore == nullptr || signal_semaphore == nullptr);
|
||||
Submit(wait_semaphore, wait_stage, signal_semaphore);
|
||||
}
|
||||
|
||||
void CommandBuffer::Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
|
||||
vk::Semaphore signal_semaphore) {
|
||||
void CommandBuffer::Execute(const SubmitInfo& submit) {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(m_execute);
|
||||
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
|
||||
submit.num_signal_semaphores > SubmitInfo::MaxSemaphores);
|
||||
|
||||
const bool has_wait = wait_semaphore != nullptr;
|
||||
const bool has_signal = signal_semaphore != nullptr;
|
||||
auto buffer = Handle();
|
||||
auto fence = m_slot->fence;
|
||||
auto buffer = Handle();
|
||||
auto fence = m_slot->fence;
|
||||
|
||||
vk::TimelineSemaphoreSubmitInfo timeline_info {};
|
||||
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
|
||||
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
|
||||
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
|
||||
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
|
||||
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
|
||||
|
||||
vk::SubmitInfo submit_info {};
|
||||
submit_info.sType = vk::StructureType::eSubmitInfo;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = has_wait ? 1u : 0u;
|
||||
submit_info.pWaitSemaphores = has_wait ? &wait_semaphore : nullptr;
|
||||
submit_info.pWaitDstStageMask = has_wait ? &wait_stage : nullptr;
|
||||
submit_info.pNext = &timeline_info;
|
||||
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
|
||||
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
|
||||
submit_info.pWaitDstStageMask = submit.wait_stages.data();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = has_signal ? 1u : 0u;
|
||||
submit_info.pSignalSemaphores = has_signal ? &signal_semaphore : nullptr;
|
||||
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
|
||||
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
|
||||
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
auto& graphics = m_graphics;
|
||||
EXIT_IF(graphics.queue == nullptr);
|
||||
|
||||
auto result = graphics.device.resetFences(1, &fence);
|
||||
@@ -298,16 +175,16 @@ void CommandBuffer::Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled()) {
|
||||
LOGF("vkQueueSubmit begin: slot=%u wait_semaphore=%p signal_semaphore=%p"
|
||||
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_slot->id, static_cast<void*>(wait_semaphore), static_cast<void*>(signal_semaphore),
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
|
||||
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
|
||||
{
|
||||
Common::LockGuard lock(graphics.queue_mutex);
|
||||
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
m_submit_seq = m_scheduler.NextSubmitSequence();
|
||||
result = graphics.queue.submit(1, &submit_info, fence);
|
||||
}
|
||||
|
||||
@@ -333,7 +210,7 @@ void CommandBuffer::WaitForFenceOnly() {
|
||||
if (!m_execute || m_fence_waited) {
|
||||
return;
|
||||
}
|
||||
auto device = GetRenderContext().GetGraphics().device;
|
||||
auto device = m_graphics.device;
|
||||
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
LOGF("vkWaitForFences failed: %s (%d), slot=%u submit_seq=%" PRIu64
|
||||
@@ -357,13 +234,10 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
|
||||
m_execute = false;
|
||||
m_fence_waited = false;
|
||||
if (reset_recording) {
|
||||
ResetNativeCommandBuffer(m_slot->buffer);
|
||||
m_recording_generation++;
|
||||
Common::LockGuard lock(*m_slot->pool_mutex);
|
||||
m_slot->Reset();
|
||||
}
|
||||
}
|
||||
if (reset_recording) {
|
||||
m_host_stream.Reset();
|
||||
}
|
||||
if (was_executed) {
|
||||
ReleaseResourcesAfterFence();
|
||||
}
|
||||
@@ -376,144 +250,71 @@ void CommandBuffer::ReleaseResourcesAfterFence() {
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteBuffersAfterFence() {
|
||||
for (auto* buffer: m_delete_after_fence) {
|
||||
GetRenderContext().GetGraphics().DeleteBuffer(*buffer);
|
||||
delete buffer;
|
||||
for (const auto& buffer: m_retired_buffers) {
|
||||
m_graphics.DeleteBuffer(*buffer);
|
||||
}
|
||||
m_delete_after_fence.clear();
|
||||
m_retired_buffers.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::BeginRenderPass(VulkanFramebuffer& framebuffer, RenderColorInfo* colors,
|
||||
uint32_t requested_color_count, RenderDepthInfo& depth) const {
|
||||
auto buffer = Handle();
|
||||
|
||||
EXIT_IF(colors == nullptr);
|
||||
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
|
||||
bool with_depth = (depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
|
||||
uint32_t color_count = 0;
|
||||
for (uint32_t i = 0; i < requested_color_count; i++) {
|
||||
if (colors[i].vulkan_buffer == nullptr) {
|
||||
break;
|
||||
}
|
||||
color_count++;
|
||||
void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
|
||||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
if (m_rendering && m_render_state == state) {
|
||||
return;
|
||||
}
|
||||
bool with_color = (color_count != 0);
|
||||
EndRendering();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!with_depth && !with_color);
|
||||
|
||||
vk::ClearValue clears[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
clears[i].color = colors[i].color_clear_value;
|
||||
}
|
||||
clears[color_count].depthStencil = {depth.depth_clear_value, depth.stencil_clear_value};
|
||||
|
||||
vk::Extent2D extent = (with_color ? colors[0].extent : depth.vulkan_buffer->extent);
|
||||
|
||||
vk::RenderPassBeginInfo render_pass_info {};
|
||||
render_pass_info.sType = vk::StructureType::eRenderPassBeginInfo;
|
||||
render_pass_info.pNext = nullptr;
|
||||
render_pass_info.renderPass = framebuffer.render_pass;
|
||||
render_pass_info.framebuffer = framebuffer.framebuffer;
|
||||
render_pass_info.renderArea.offset = {0, 0};
|
||||
render_pass_info.renderArea.extent = extent;
|
||||
render_pass_info.clearValueCount = color_count + (with_depth ? 1u : 0u);
|
||||
render_pass_info.pClearValues = clears;
|
||||
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
const auto color_initial_layout = framebuffer.color_layout[i];
|
||||
if (colors[i].vulkan_buffer->layout != color_initial_layout) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("BeginRenderPass: color%u initial barrier image=%p mem=%" PRIu64 " %s -> %s\n",
|
||||
i, VulkanHandleToPointer(colors[i].vulkan_buffer->image),
|
||||
colors[i].vulkan_buffer->memory.unique_id,
|
||||
VulkanToString(colors[i].vulkan_buffer->layout).c_str(),
|
||||
VulkanToString(color_initial_layout).c_str());
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask = {};
|
||||
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
image_memory_barrier.oldLayout = colors[i].vulkan_buffer->layout;
|
||||
image_memory_barrier.newLayout = color_initial_layout;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.image = colors[i].vulkan_buffer->image;
|
||||
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
image_memory_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_memory_barrier.subresourceRange.layerCount = colors[i].vulkan_buffer->layers;
|
||||
|
||||
buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
|
||||
&image_memory_barrier);
|
||||
|
||||
colors[i].vulkan_buffer->layout = image_memory_barrier.newLayout;
|
||||
} else if (graphics_debug_dump_enabled()) {
|
||||
LOGF("BeginRenderPass: color%u initial image=%p mem=%" PRIu64 " layout=%s\n", i,
|
||||
VulkanHandleToPointer(colors[i].vulkan_buffer->image),
|
||||
colors[i].vulkan_buffer->memory.unique_id,
|
||||
VulkanToString(colors[i].vulkan_buffer->layout).c_str());
|
||||
}
|
||||
std::array<vk::RenderingAttachmentInfo, RENDER_COLOR_ATTACHMENTS_MAX> colors {};
|
||||
for (uint32_t i = 0; i < state.num_color_attachments; i++) {
|
||||
const auto& attachment = state.color_attachments[i];
|
||||
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
colors[i].imageView = attachment.image_view;
|
||||
colors[i].imageLayout = attachment.image_layout;
|
||||
colors[i].loadOp = attachment.is_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
colors[i].clearValue.color.uint32 = attachment.clear_value;
|
||||
}
|
||||
|
||||
const auto depth_layout =
|
||||
with_depth ? framebuffer.depth_layout : vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
const auto& depth_stencil = state.depth_stencil_attachment;
|
||||
vk::RenderingAttachmentInfo depth {};
|
||||
depth.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
depth.imageView = depth_stencil.image_view;
|
||||
depth.imageLayout = depth_stencil.image_layout;
|
||||
depth.loadOp = depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
depth.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
|
||||
|
||||
if (with_depth && depth.vulkan_buffer->layout != depth_layout) {
|
||||
vk::ImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
image_memory_barrier.dstAccessMask =
|
||||
(depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
? vk::AccessFlagBits::eMemoryRead
|
||||
: vk::AccessFlagBits::eMemoryWrite);
|
||||
image_memory_barrier.oldLayout = depth.vulkan_buffer->layout;
|
||||
image_memory_barrier.newLayout = depth_layout;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.image = depth.vulkan_buffer->image;
|
||||
image_memory_barrier.subresourceRange.aspectMask =
|
||||
ImageViewOps::DepthAspectMask(depth.vulkan_buffer->format);
|
||||
image_memory_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_memory_barrier.subresourceRange.levelCount = 1;
|
||||
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_memory_barrier.subresourceRange.layerCount = depth.vulkan_buffer->layers;
|
||||
vk::RenderingAttachmentInfo stencil {};
|
||||
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
stencil.imageView = depth_stencil.image_view;
|
||||
stencil.imageLayout = depth_stencil.image_layout;
|
||||
stencil.loadOp = depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
stencil.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
|
||||
|
||||
buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
|
||||
|
||||
depth.vulkan_buffer->layout = image_memory_barrier.newLayout;
|
||||
}
|
||||
|
||||
buffer.beginRenderPass(&render_pass_info, vk::SubpassContents::eInline);
|
||||
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
colors[i].vulkan_buffer->layout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
if (colors[i].vulkan_buffer->type == VulkanImageType::RenderTexture) {
|
||||
static_cast<RenderTextureVulkanImage*>(colors[i].vulkan_buffer)->initial_clear_pending =
|
||||
false;
|
||||
}
|
||||
}
|
||||
if (with_depth) {
|
||||
depth.vulkan_buffer->initial_depth_clear_pending = false;
|
||||
depth.vulkan_buffer->initial_stencil_clear_pending = false;
|
||||
}
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
rendering.renderArea.extent = {state.width, state.height};
|
||||
rendering.layerCount = state.num_layers;
|
||||
rendering.colorAttachmentCount = state.num_color_attachments;
|
||||
rendering.pColorAttachments = colors.data();
|
||||
rendering.pDepthAttachment = depth_stencil.has_depth ? &depth : nullptr;
|
||||
rendering.pStencilAttachment = depth_stencil.has_stencil ? &stencil : nullptr;
|
||||
Handle().beginRendering(rendering);
|
||||
m_render_state = state;
|
||||
m_rendering = true;
|
||||
}
|
||||
|
||||
void CommandBuffer::EndRenderPass() const {
|
||||
auto buffer = Handle();
|
||||
|
||||
buffer.endRenderPass();
|
||||
void CommandBuffer::EndRendering() const {
|
||||
if (!m_rendering) {
|
||||
return;
|
||||
}
|
||||
Handle().endRendering();
|
||||
m_rendering = false;
|
||||
m_render_state = {};
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <fmt/format.h>
|
||||
@@ -100,8 +101,6 @@ void sh_print(const char* func, const HW::Shader& /*uc*/) {
|
||||
LOGF("%s\n", func);
|
||||
}
|
||||
|
||||
void sh_check(const HW::Shader& /*uc*/) {}
|
||||
|
||||
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) {
|
||||
std::vector<std::string> dst;
|
||||
dst.reserve(53);
|
||||
@@ -431,21 +430,17 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
LOGF("\t z_info.format = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.tile_mode_index = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.num_samples = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.tile_surface_enable = %s\n"
|
||||
"\t z_info.texture_compatibility = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.htile_acceleration = %s\n"
|
||||
"\t z_info.expclear_enabled = %s\n"
|
||||
"\t z_info.zrange_precision = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.embedded_sample_locations = %s\n"
|
||||
"\t z_info.z_compare_base = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.partially_resident = %s\n"
|
||||
"\t z_info.num_mip_levels = 0x%02" PRIx8 "\n"
|
||||
"\t z_info.plane_compression = 0x%02" PRIx8 "\n"
|
||||
"\t z_info.max_mip_level = 0x%02" PRIx8 "\n"
|
||||
"\t stencil_info.format = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.tile_stencil_disable = %s\n"
|
||||
"\t stencil_info.texture_compatibility = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.htile_stencil_disabled = %s\n"
|
||||
"\t stencil_info.expclear_enabled = %s\n"
|
||||
"\t stencil_info.tile_mode_index = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.tile_split = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.texture_compatible_stencil = %s\n"
|
||||
"\t stencil_info.partially_resident = %s\n"
|
||||
"\t depth_info.addr5_swizzle_mask = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.array_mode = 0x%08" PRIx32 "\n"
|
||||
@@ -478,15 +473,15 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
"\t height = 0x%08" PRIx32 "\n"
|
||||
"\t size.x_max = 0x%04" PRIx16 "\n"
|
||||
"\t size.y_max = 0x%04" PRIx16 "\n",
|
||||
z.z_info.format, z.z_info.tile_mode_index, z.z_info.num_samples,
|
||||
z.z_info.tile_surface_enable ? "true" : "false",
|
||||
z.z_info.expclear_enabled ? "true" : "false", z.z_info.zrange_precision,
|
||||
z.z_info.embedded_sample_locations ? "true" : "false",
|
||||
z.z_info.partially_resident ? "true" : "false", z.z_info.num_mip_levels,
|
||||
z.z_info.plane_compression, z.stencil_info.format,
|
||||
z.stencil_info.tile_stencil_disable ? "true" : "false",
|
||||
z.stencil_info.expclear_enabled ? "true" : "false", z.stencil_info.tile_mode_index,
|
||||
z.stencil_info.tile_split, z.stencil_info.texture_compatible_stencil ? "true" : "false",
|
||||
z.z_info.format, z.z_info.num_samples,
|
||||
Prospero::GpuEnumValue(z.z_info.texture_compatibility),
|
||||
z.z_info.htile_acceleration ? "true" : "false",
|
||||
z.z_info.expclear_enabled ? "true" : "false",
|
||||
Prospero::GpuEnumValue(z.z_info.z_compare_base),
|
||||
z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level,
|
||||
z.stencil_info.format, Prospero::GpuEnumValue(z.stencil_info.texture_compatibility),
|
||||
z.stencil_info.htile_stencil_disabled ? "true" : "false",
|
||||
z.stencil_info.expclear_enabled ? "true" : "false",
|
||||
z.stencil_info.partially_resident ? "true" : "false", z.depth_info.addr5_swizzle_mask,
|
||||
z.depth_info.array_mode, z.depth_info.pipe_config, z.depth_info.bank_width,
|
||||
z.depth_info.bank_height, z.depth_info.macro_tile_aspect, z.depth_info.num_banks,
|
||||
@@ -503,36 +498,17 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static void ZCheck(const HW::DepthRenderTarget& z) {
|
||||
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
|
||||
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
|
||||
if (z.z_info.format == 0) {
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.tile_mode_index != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.tile_surface_enable != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
|
||||
if (z.z_info.zrange_precision != 0) {
|
||||
LOGF("Warning: zrange_precision != 0\n");
|
||||
// z.z_info.zrange_precision = 0;
|
||||
}
|
||||
if (z.z_info.embedded_sample_locations) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring embedded sample locations\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.num_mip_levels != 0);
|
||||
if (z.z_info.plane_compression != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 plane_compression=0x%02" PRIx8 "\n",
|
||||
z.z_info.plane_compression);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
|
||||
} else {
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0x00000001 && z.z_info.format != 0x00000003);
|
||||
// EXIT_NOT_IMPLEMENTED(z.z_info.tile_mode_index != 0x00000002);
|
||||
if (z.z_info.num_samples != 0x00000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -541,56 +517,17 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
// EXIT_NOT_IMPLEMENTED(z.z_info.tile_surface_enable != true);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
|
||||
if (z.z_info.zrange_precision != 0x00000001) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring zrange_precision=0x%08" PRIx32 "\n",
|
||||
z.z_info.zrange_precision);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (z.z_info.embedded_sample_locations) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring embedded sample locations\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.num_mip_levels != 0);
|
||||
if (z.z_info.plane_compression != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 plane_compression=0x%02" PRIx8 "\n",
|
||||
z.z_info.plane_compression);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
|
||||
}
|
||||
|
||||
if (z.stencil_info.format == 0) {
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_stencil_disable != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_mode_index != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_split != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.texture_compatible_stencil != true);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
|
||||
} else {
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001);
|
||||
if (z.stencil_info.tile_stencil_disable != true) {
|
||||
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 16) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 HTILE stencil acceleration\n");
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_mode_index != 0x00000002);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_split != 0x00000002);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.texture_compatible_stencil != true);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
|
||||
}
|
||||
|
||||
@@ -1135,10 +1072,10 @@ static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t heig
|
||||
int max_right = static_cast<int>(width);
|
||||
int max_bottom = static_cast<int>(height);
|
||||
|
||||
r.left = (r.left < 0 ? 0 : (r.left > max_right ? max_right : r.left));
|
||||
r.right = (r.right < 0 ? 0 : (r.right > max_right ? max_right : r.right));
|
||||
r.top = (r.top < 0 ? 0 : (r.top > max_bottom ? max_bottom : r.top));
|
||||
r.bottom = (r.bottom < 0 ? 0 : (r.bottom > max_bottom ? max_bottom : r.bottom));
|
||||
r.left = std::clamp(r.left, 0, max_right);
|
||||
r.right = std::clamp(r.right, 0, max_right);
|
||||
r.top = std::clamp(r.top, 0, max_bottom);
|
||||
r.bottom = std::clamp(r.bottom, 0, max_bottom);
|
||||
|
||||
if (!ScissorRectValid(r)) {
|
||||
r.right = r.left;
|
||||
|
||||
@@ -33,7 +33,6 @@ bool graphics_debug_dump_enabled();
|
||||
void uc_print(const char* func, const HW::UserConfig& uc);
|
||||
void uc_check(const HW::UserConfig& uc);
|
||||
void sh_print(const char* func, const HW::Shader& uc);
|
||||
void sh_check(const HW::Shader& uc);
|
||||
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
|
||||
bool RenderIsColorTileModeLinear(uint32_t tile_mode);
|
||||
void hw_print(const RenderCommandBuffer& buffer);
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@@ -88,7 +86,8 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderDepthInfo& r) {
|
||||
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderDepthInfo& r) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
(void)submit_id;
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
@@ -103,31 +102,34 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
if (!z.z_info.HasValidTextureCompatibility() ||
|
||||
!z.stencil_info.HasValidTextureCompatibility()) {
|
||||
DepthFatal("invalid PS5 depth texture-compatibility encoding");
|
||||
}
|
||||
const bool attachment_unbound =
|
||||
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) &&
|
||||
z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) &&
|
||||
z.z_info.tile_mode_index == 0 && z.z_info.num_samples == 0 &&
|
||||
z.z_info.zrange_precision <= 1 && !z.z_info.expclear_enabled &&
|
||||
!z.z_info.embedded_sample_locations && !z.z_info.partially_resident &&
|
||||
z.z_info.num_mip_levels == 0 && z.z_info.plane_compression == 0 &&
|
||||
z.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.texture_compatible_stencil &&
|
||||
!z.stencil_info.partially_resident && z.depth_view.slice_start == 0 &&
|
||||
z.depth_view.slice_max == 0 && z.depth_view.current_mip_level == 0 &&
|
||||
!z.depth_view.depth_write_disable && !z.depth_view.stencil_write_disable &&
|
||||
z.depth_info.addr5_swizzle_mask == 0 && z.depth_info.array_mode == 0 &&
|
||||
z.depth_info.pipe_config == 0 && z.depth_info.bank_width == 0 &&
|
||||
z.depth_info.bank_height == 0 && z.depth_info.macro_tile_aspect == 0 &&
|
||||
z.depth_info.num_banks == 0 && z.htile_surface.linear == 0 &&
|
||||
z.htile_surface.full_cache == 0 && z.htile_surface.htile_uses_preload_win == 0 &&
|
||||
z.htile_surface.preload == 0 && z.htile_surface.prefetch_width == 0 &&
|
||||
z.htile_surface.prefetch_height == 0 && z.htile_surface.dst_outside_zero_to_one == 0 &&
|
||||
z.z_read_base_addr == 0 && z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
|
||||
z.z_info.num_samples == 0 &&
|
||||
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
|
||||
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
|
||||
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
|
||||
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
|
||||
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
|
||||
!z.depth_view.stencil_write_disable && z.depth_info.addr5_swizzle_mask == 0 &&
|
||||
z.depth_info.array_mode == 0 && z.depth_info.pipe_config == 0 &&
|
||||
z.depth_info.bank_width == 0 && z.depth_info.bank_height == 0 &&
|
||||
z.depth_info.macro_tile_aspect == 0 && z.depth_info.num_banks == 0 &&
|
||||
z.htile_surface.linear == 0 && z.htile_surface.full_cache == 0 &&
|
||||
z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.preload == 0 &&
|
||||
z.htile_surface.prefetch_width == 0 && z.htile_surface.prefetch_height == 0 &&
|
||||
z.htile_surface.dst_outside_zero_to_one == 0 && z.z_read_base_addr == 0 &&
|
||||
z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
|
||||
z.stencil_write_base_addr == 0 && z.htile_data_base_addr == 0 &&
|
||||
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
|
||||
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
|
||||
// presence alone must not manufacture a depth attachment.
|
||||
!z.z_info.tile_surface_enable && !z.width_height_valid && !z.pitch_height_valid &&
|
||||
!z.z_info.htile_acceleration && !z.width_height_valid && !z.pitch_height_valid &&
|
||||
z.size.x_max == 0 && z.size.y_max == 0 && z.pitch_div8_minus1 == 0 &&
|
||||
z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 && z.width == 0 && z.height == 0;
|
||||
if (attachment_unbound) {
|
||||
@@ -139,13 +141,13 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
}
|
||||
const bool has_stencil =
|
||||
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
|
||||
const bool has_htile = z.z_info.tile_surface_enable;
|
||||
const bool has_htile = z.z_info.htile_acceleration;
|
||||
const auto samples = render_sample_count(z.z_info.num_samples);
|
||||
if (samples == 0) {
|
||||
DepthFatal("unsupported depth fragment count: %u", z.z_info.num_samples);
|
||||
}
|
||||
const bool htile_stencil_compat = depth_htile_stencil_acceleration_compatible(
|
||||
has_stencil, has_htile, z.stencil_info.tile_stencil_disable);
|
||||
has_stencil, has_htile, z.stencil_info.htile_stencil_disabled);
|
||||
const auto view = ResolveTargetViewInfo(z.depth_view.slice_start, z.depth_view.slice_max);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D: break;
|
||||
@@ -156,14 +158,10 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
|
||||
z.depth_view.slice_max);
|
||||
}
|
||||
// Prospero defines the compression-disable bits as tile writeback policy. Vulkan attachments
|
||||
// expose the same logical depth/stencil values regardless of the driver's backing compression.
|
||||
if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid ||
|
||||
rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
|
||||
z.z_info.embedded_sample_locations || z.z_info.partially_resident ||
|
||||
z.stencil_info.partially_resident || z.z_info.plane_compression != 0 ||
|
||||
z.z_info.num_mip_levels != 0 || z.z_info.tile_mode_index != 0 ||
|
||||
z.z_info.zrange_precision > 1 || z.depth_view.current_mip_level != 0 ||
|
||||
z.z_info.partially_resident || z.stencil_info.partially_resident ||
|
||||
z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
|
||||
z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.array_mode != 0 ||
|
||||
z.depth_info.pipe_config != 0 || z.depth_info.bank_width != 0 ||
|
||||
z.depth_info.bank_height != 0 || z.depth_info.macro_tile_aspect != 0 ||
|
||||
@@ -177,27 +175,16 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
DepthFatal("unsupported depth register state");
|
||||
}
|
||||
if (has_stencil) {
|
||||
// Prospero defines Hi-Stencil as HTile-backed acceleration of the logical stencil plane.
|
||||
// Keep the plane native in Vulkan while tracking HTile separately.
|
||||
if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil ||
|
||||
z.stencil_read_base_addr == 0 ||
|
||||
!htile_stencil_compat || z.stencil_read_base_addr == 0 ||
|
||||
z.stencil_write_base_addr != z.stencil_read_base_addr ||
|
||||
(z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) {
|
||||
DepthFatal("unsupported stencil attachment state");
|
||||
}
|
||||
if (!z.stencil_info.tile_stencil_disable) {
|
||||
static std::atomic_bool logged = false;
|
||||
if (!logged.load(std::memory_order_relaxed) &&
|
||||
!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("DepthTarget: compatibility: using native stencil with PS5 HTILE "
|
||||
"acceleration\n");
|
||||
}
|
||||
}
|
||||
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil) {
|
||||
!htile_stencil_compat ||
|
||||
z.stencil_info.texture_compatibility !=
|
||||
Prospero::TextureCompatibleStencil::kDisable) {
|
||||
DepthFatal("stencil state without an active stencil attachment");
|
||||
}
|
||||
if (has_htile) {
|
||||
@@ -272,28 +259,19 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
(has_htile && htile_backing_size > TRACKER_ADDRESS_SIZE - z.htile_data_base_addr)) {
|
||||
DepthFatal("layered depth backing range is invalid");
|
||||
}
|
||||
r.htile = has_htile;
|
||||
r.width = width;
|
||||
r.height = height;
|
||||
r.samples = samples;
|
||||
r.depth_buffer_size = depth_backing_size;
|
||||
r.depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
|
||||
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
|
||||
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
auto& cache = GetRenderContext().GetTextureCache();
|
||||
if (has_htile) {
|
||||
cache.RegisterMeta(r.htile_buffer_vaddr, r.htile_buffer_size, view.image_layers);
|
||||
}
|
||||
if (has_htile && rc.depth_clear_enable && !cache.ClearMeta(z.htile_data_base_addr)) {
|
||||
DepthFatal("failed to acquire HTile metadata for a depth clear");
|
||||
}
|
||||
const bool meta_clear =
|
||||
has_htile && cache.IsMetaCleared(z.htile_data_base_addr, z.depth_view.slice_start);
|
||||
r.htile = has_htile;
|
||||
r.width = width;
|
||||
r.height = height;
|
||||
r.samples = samples;
|
||||
r.depth_buffer_size = depth_backing_size;
|
||||
r.depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
|
||||
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
|
||||
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
r.depth_clear_enable = rc.depth_clear_enable;
|
||||
r.depth_meta_clear_enable = meta_clear;
|
||||
r.depth_load_clear_enable = r.depth_clear_enable || r.depth_meta_clear_enable;
|
||||
r.depth_meta_clear_enable = false;
|
||||
r.depth_load_clear_enable = r.depth_clear_enable;
|
||||
r.depth_clear_value = hw.GetDepthClearValue();
|
||||
r.depth_test_enable = dc.z_enable;
|
||||
r.depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
|
||||
@@ -343,55 +321,93 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
r.vaddr[1] = r.stencil_buffer_vaddr;
|
||||
r.size[1] = r.stencil_buffer_size;
|
||||
}
|
||||
DepthTargetInfo info {};
|
||||
info.address = r.depth_buffer_vaddr;
|
||||
info.size = r.depth_buffer_size;
|
||||
info.stencil_address = r.stencil_buffer_vaddr;
|
||||
info.stencil_size = r.stencil_buffer_size;
|
||||
info.htile_address = r.htile_buffer_vaddr;
|
||||
info.htile_size = r.htile_buffer_size;
|
||||
info.format = r.format;
|
||||
info.guest_format = guest_format;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.pitch = pitch;
|
||||
info.bytes_per_element = bytes;
|
||||
info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
|
||||
info.layers = view.image_layers;
|
||||
info.samples = samples;
|
||||
info.depth_load_clear = r.depth_load_clear_enable;
|
||||
info.depth_access = depth_active;
|
||||
info.stencil_load_clear = rc.stencil_clear_enable;
|
||||
info.stencil_access =
|
||||
r.stencil_clear_enable ||
|
||||
(r.stencil_test_enable &&
|
||||
(stencil_face_accesses_attachment(r.stencil_static_front, r.stencil_dynamic_front) ||
|
||||
stencil_face_accesses_attachment(r.stencil_static_back, r.stencil_dynamic_back)));
|
||||
info.stencil_htile_compressed =
|
||||
has_stencil && has_htile && !z.stencil_info.tile_stencil_disable;
|
||||
r.vulkan_buffer = &cache.FindDepthTarget(buffer, info);
|
||||
r.vulkan_view =
|
||||
cache.GetDepthTargetAttachmentView(*r.vulkan_buffer, view.base_layer, view.layer_count);
|
||||
if (r.vulkan_buffer->initial_depth_clear_pending) {
|
||||
r.depth_load_clear_enable = true;
|
||||
r.depth_clear_value = 0.0f;
|
||||
}
|
||||
if (r.vulkan_buffer->initial_stencil_clear_pending) {
|
||||
r.stencil_clear_enable = true;
|
||||
r.stencil_clear_value = 0;
|
||||
}
|
||||
if (meta_clear && !cache.TouchMeta(z.htile_data_base_addr, z.depth_view.slice_start, false)) {
|
||||
DepthFatal("failed to consume HTile clear state");
|
||||
}
|
||||
TextureCache::ImageDesc desc {};
|
||||
desc.type = TextureCache::BindingType::DepthTarget;
|
||||
desc.info.data = {r.depth_buffer_vaddr, r.depth_buffer_size};
|
||||
desc.info.stencil = {r.stencil_buffer_vaddr, r.stencil_buffer_size};
|
||||
desc.info.pixel_format = r.format;
|
||||
desc.info.guest_format = guest_format;
|
||||
desc.info.type = Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {width, height, 1};
|
||||
desc.info.resources = {1, view.image_layers};
|
||||
desc.info.pitch = pitch;
|
||||
desc.info.bytes_per_block = bytes;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
|
||||
desc.info.mip_layout[0] = {0, r.depth_buffer_size, pitch, height};
|
||||
desc.info.metadata.range = {r.htile_buffer_vaddr, r.htile_buffer_size};
|
||||
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
|
||||
desc.info.metadata.stencil_compressed =
|
||||
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
|
||||
desc.view_info.format = r.format;
|
||||
desc.view_info.type =
|
||||
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
desc.view_info.aspect = ImageViewOps::DepthAspectMask(r.format);
|
||||
desc.view_info.base_level = 0;
|
||||
desc.view_info.level_count = 1;
|
||||
desc.view_info.base_layer = view.base_layer;
|
||||
desc.view_info.layer_count = view.layer_count;
|
||||
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
r.desc = std::move(desc);
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
r.image_id = cache.FindImage(r.desc);
|
||||
r.image_view = nullptr;
|
||||
BindRenderTarget(r.image_id);
|
||||
}
|
||||
|
||||
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target) {
|
||||
const bool with_depth =
|
||||
target.format != vk::Format::eUndefined && target.vulkan_buffer != nullptr;
|
||||
|
||||
if (with_depth && !depth_attachment_read_only(target)) {
|
||||
GetRenderContext().GetTextureCache().MarkGpuWritten(*target.vulkan_buffer);
|
||||
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
if (format == vk::Format::eUndefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto available = ImageViewOps::DepthAspectMask(format);
|
||||
vk::ImageAspectFlags writes {};
|
||||
if ((available & vk::ImageAspectFlagBits::eDepth) &&
|
||||
(depth_load_clear_enable || (depth_test_enable && depth_write_enable))) {
|
||||
writes |= vk::ImageAspectFlagBits::eDepth;
|
||||
}
|
||||
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
|
||||
return writes;
|
||||
}
|
||||
|
||||
const auto face_writes = [&](const PipelineStencilStaticState& state,
|
||||
const PipelineStencilDynamicState& dynamic) {
|
||||
if (dynamic.writeMask == 0) {
|
||||
return false;
|
||||
}
|
||||
bool can_pass = state.compareOp != vk::CompareOp::eNever;
|
||||
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
|
||||
if (dynamic.compareMask == 0) {
|
||||
switch (state.compareOp) {
|
||||
case vk::CompareOp::eEqual:
|
||||
case vk::CompareOp::eLessOrEqual:
|
||||
case vk::CompareOp::eGreaterOrEqual:
|
||||
case vk::CompareOp::eAlways:
|
||||
can_pass = true;
|
||||
can_fail = false;
|
||||
break;
|
||||
case vk::CompareOp::eNever:
|
||||
case vk::CompareOp::eLess:
|
||||
case vk::CompareOp::eGreater:
|
||||
case vk::CompareOp::eNotEqual:
|
||||
can_pass = false;
|
||||
can_fail = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
const bool depth_pass = !depth_test_enable || depth_compare_op != vk::CompareOp::eNever;
|
||||
const bool depth_fail = depth_test_enable && depth_compare_op != vk::CompareOp::eAlways;
|
||||
return (can_fail && state.failOp != vk::StencilOp::eKeep) ||
|
||||
(can_pass && depth_pass && state.passOp != vk::StencilOp::eKeep) ||
|
||||
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
|
||||
};
|
||||
if (stencil_clear_enable ||
|
||||
(stencil_test_enable && (face_writes(stencil_static_front, stencil_dynamic_front) ||
|
||||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
|
||||
writes |= vk::ImageAspectFlagBits::eStencil;
|
||||
}
|
||||
return writes;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/textureCache.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -10,14 +12,14 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
struct DepthStencilVulkanImage;
|
||||
|
||||
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
|
||||
bool acceleration_disabled) {
|
||||
return acceleration_disabled || (has_stencil && has_htile);
|
||||
bool htile_stencil_disabled) {
|
||||
return htile_stencil_disabled || (has_stencil && has_htile);
|
||||
}
|
||||
|
||||
struct RenderDepthInfo {
|
||||
TextureCache::ImageDesc desc;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
@@ -49,29 +51,46 @@ struct RenderDepthInfo {
|
||||
PipelineStencilStaticState stencil_static_back;
|
||||
PipelineStencilDynamicState stencil_dynamic_front;
|
||||
PipelineStencilDynamicState stencil_dynamic_back;
|
||||
DepthStencilVulkanImage* vulkan_buffer = nullptr;
|
||||
vk::ImageView vulkan_view = nullptr;
|
||||
uint64_t vaddr[3] = {};
|
||||
uint64_t size[3] = {};
|
||||
int vaddr_num = 0;
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
uint64_t vaddr[3] = {};
|
||||
uint64_t size[3] = {};
|
||||
int vaddr_num = 0;
|
||||
|
||||
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
|
||||
};
|
||||
|
||||
inline bool depth_attachment_read_only(const RenderDepthInfo& depth) {
|
||||
const bool stencil_write =
|
||||
depth.stencil_test_enable &&
|
||||
(depth.stencil_dynamic_front.writeMask != 0 || depth.stencil_dynamic_back.writeMask != 0);
|
||||
return !depth.depth_load_clear_enable && !depth.stencil_clear_enable &&
|
||||
!depth.depth_write_enable && !stencil_write;
|
||||
return !depth.AttachmentWriteAspects();
|
||||
}
|
||||
|
||||
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
|
||||
return depth_attachment_read_only(depth) ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
const auto available = ImageViewOps::DepthAspectMask(depth.format);
|
||||
const auto writes = depth.AttachmentWriteAspects();
|
||||
const bool has_depth = static_cast<bool>(available & vk::ImageAspectFlagBits::eDepth);
|
||||
const bool has_stencil = static_cast<bool>(available & vk::ImageAspectFlagBits::eStencil);
|
||||
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
|
||||
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
|
||||
if (!has_stencil) {
|
||||
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
|
||||
: vk::ImageLayout::eDepthReadOnlyOptimal;
|
||||
}
|
||||
if (!has_depth) {
|
||||
return stencil_write ? vk::ImageLayout::eStencilAttachmentOptimal
|
||||
: vk::ImageLayout::eStencilReadOnlyOptimal;
|
||||
}
|
||||
if (depth_write && stencil_write) {
|
||||
return vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
}
|
||||
if (depth_write) {
|
||||
return vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal;
|
||||
}
|
||||
if (stencil_write) {
|
||||
return vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal;
|
||||
}
|
||||
return vk::ImageLayout::eDepthStencilReadOnlyOptimal;
|
||||
}
|
||||
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderDepthInfo& r);
|
||||
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
#include <array>
|
||||
@@ -23,9 +22,13 @@ vk::ShaderStageFlags StageFlags(DescriptorCache::Stage stage) {
|
||||
|
||||
bool IsSampledImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Sampled1D:
|
||||
case BindingKind::Sampled1DArray:
|
||||
case BindingKind::Sampled2D:
|
||||
case BindingKind::Sampled2DArray:
|
||||
case BindingKind::Sampled3D:
|
||||
case BindingKind::SampledUint1D:
|
||||
case BindingKind::SampledUint1DArray:
|
||||
case BindingKind::SampledUint2D:
|
||||
case BindingKind::SampledUint2DArray:
|
||||
case BindingKind::SampledUint3D: return true;
|
||||
@@ -35,9 +38,13 @@ bool IsSampledImage(BindingKind kind) {
|
||||
|
||||
bool IsStorageImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Storage1D:
|
||||
case BindingKind::Storage1DArray:
|
||||
case BindingKind::Storage2D:
|
||||
case BindingKind::Storage2DArray:
|
||||
case BindingKind::Storage3D:
|
||||
case BindingKind::StorageUint1D:
|
||||
case BindingKind::StorageUint1DArray:
|
||||
case BindingKind::StorageUint2D:
|
||||
case BindingKind::StorageUint2DArray:
|
||||
case BindingKind::StorageUint3D: return true;
|
||||
@@ -76,31 +83,35 @@ std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
|
||||
return key;
|
||||
}
|
||||
|
||||
vk::ImageLayout SampledLayout(const VulkanImage& image) {
|
||||
switch (image.layout) {
|
||||
case vk::ImageLayout::eTransferDstOptimal:
|
||||
case vk::ImageLayout::eTransferSrcOptimal:
|
||||
case vk::ImageLayout::eColorAttachmentOptimal: return vk::ImageLayout::eGeneral;
|
||||
default: return image.layout;
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
|
||||
EXIT_IF(view.buffer == nullptr || view.buffer->buffer == nullptr);
|
||||
return {view.buffer->buffer, view.offset, view.range};
|
||||
}
|
||||
|
||||
vk::DescriptorImageInfo MakeDescriptorImageInfo(const DescriptorCache::TextureBinding& texture,
|
||||
vk::ImageLayout layout) {
|
||||
EXIT_IF(texture.image == nullptr || texture.view < 0 || texture.view >= VulkanImage::VIEW_MAX);
|
||||
const auto view = texture.image_view != nullptr ? texture.image_view
|
||||
: texture.image->image_view[texture.view];
|
||||
EXIT_IF(view == nullptr);
|
||||
return {nullptr, view, layout};
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
return {view.buffer, view.offset, view.range};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture) {
|
||||
EXIT_IF(!texture.image_id || texture.image_view == nullptr ||
|
||||
texture.layout == vk::ImageLayout::eUndefined);
|
||||
return {nullptr, texture.image_view, texture.layout};
|
||||
}
|
||||
|
||||
DescriptorCache::~DescriptorCache() {
|
||||
for (auto& [layout, sets]: m_free_sets_by_layout) {
|
||||
(void)layout;
|
||||
for (auto* set: sets) {
|
||||
delete set;
|
||||
}
|
||||
}
|
||||
for (const auto& [key, layout]: m_descriptor_set_layouts) {
|
||||
(void)key;
|
||||
m_graphics.device.destroyDescriptorSetLayout(layout, nullptr);
|
||||
}
|
||||
for (const auto& pool: m_pools) {
|
||||
m_graphics.device.destroyDescriptorPool(pool.pool, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout
|
||||
DescriptorCache::GetDescriptorSetLayoutInternal(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
@@ -256,13 +267,9 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
const auto layout = IsStorageImage(binding.kind) ? vk::ImageLayout::eGeneral
|
||||
: vk::ImageLayout::eUndefined;
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto& texture = data.images.at(resource);
|
||||
image_infos.push_back(MakeDescriptorImageInfo(
|
||||
texture,
|
||||
IsSampledImage(binding.kind) ? SampledLayout(*texture.image) : layout));
|
||||
image_infos.push_back(MakeImageInfo(texture));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6,20 +6,27 @@
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/textureCache.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
struct DescriptorValue;
|
||||
struct ImageResource;
|
||||
struct Program;
|
||||
}
|
||||
struct ResourceSnapshot;
|
||||
} // namespace ShaderRecompiler::IR
|
||||
|
||||
class CommandBuffer;
|
||||
struct DescriptorCacheTestAccess;
|
||||
struct ShaderStageRuntime;
|
||||
|
||||
struct VulkanDescriptorSet {
|
||||
@@ -29,9 +36,10 @@ struct VulkanDescriptorSet {
|
||||
};
|
||||
|
||||
struct BufferView {
|
||||
VulkanBuffer* buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
std::shared_ptr<void> owner;
|
||||
vk::Buffer buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
};
|
||||
|
||||
class DescriptorCache {
|
||||
@@ -39,18 +47,10 @@ public:
|
||||
enum class Stage { Unknown, Vertex, Pixel, Compute };
|
||||
|
||||
struct TextureBinding {
|
||||
VulkanImage* image = nullptr;
|
||||
int view = VulkanImage::VIEW_DEFAULT;
|
||||
vk::ImageView image_view = nullptr;
|
||||
};
|
||||
|
||||
enum class TextureVariant : int {
|
||||
Float2D = 0,
|
||||
Uint2D,
|
||||
FloatArray,
|
||||
UintArray,
|
||||
Float3D,
|
||||
Uint3D,
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
TextureCache::ImageDesc desc;
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
};
|
||||
|
||||
struct NativeDescriptors {
|
||||
@@ -63,10 +63,21 @@ public:
|
||||
BufferView user_data;
|
||||
};
|
||||
|
||||
struct PreparedBindings {
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
|
||||
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
|
||||
NativeDescriptors resources;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
vk::ShaderStageFlags shader_stage;
|
||||
Stage stage = Stage::Unknown;
|
||||
bool committed = false;
|
||||
};
|
||||
|
||||
explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
~DescriptorCache() { KYTY_NOT_IMPLEMENTED; }
|
||||
~DescriptorCache();
|
||||
KYTY_CLASS_NO_COPY(DescriptorCache);
|
||||
|
||||
vk::DescriptorSetLayout GetDescriptorSetLayout(Stage stage,
|
||||
@@ -76,11 +87,14 @@ public:
|
||||
const NativeDescriptors& descriptors);
|
||||
|
||||
private:
|
||||
friend struct DescriptorCacheTestAccess;
|
||||
|
||||
struct Pool {
|
||||
vk::DescriptorPool pool = nullptr;
|
||||
int next_free_pool = -1;
|
||||
};
|
||||
|
||||
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture);
|
||||
void CreatePool();
|
||||
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
|
||||
vk::DescriptorSetLayout
|
||||
@@ -95,11 +109,6 @@ private:
|
||||
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
|
||||
};
|
||||
|
||||
void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer,
|
||||
vk::PipelineBindPoint pipeline_bind_point, vk::PipelineLayout layout,
|
||||
const ShaderStageRuntime& runtime, vk::ShaderStageFlags vk_stage,
|
||||
DescriptorCache::Stage stage);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
@@ -12,8 +13,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct VulkanImage;
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
@@ -35,13 +34,12 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
|
||||
|
||||
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
|
||||
const VulkanImage& image);
|
||||
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor);
|
||||
const Image& image);
|
||||
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
|
||||
const Image& image);
|
||||
[[nodiscard]] bool
|
||||
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, const VulkanImage& image);
|
||||
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, uint64_t size);
|
||||
const ShaderTextureResource& descriptor, const Image& image);
|
||||
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, uint64_t size);
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#include "graphics/host_gpu/renderer/dummyTextureCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] constexpr size_t DummyTextureIndex(bool uint_format, bool image_3d) noexcept {
|
||||
return (image_3d ? 2u : 0u) + (uint_format ? 1u : 0u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DummyTextureCache::~DummyTextureCache() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
const auto populated = [](const auto& slots) {
|
||||
return std::ranges::any_of(slots, [](const auto& slot) { return slot.image != nullptr; });
|
||||
};
|
||||
if (!populated(m_sampled) && !populated(m_storage)) {
|
||||
return;
|
||||
}
|
||||
Transfer::WaitForQueueIdle();
|
||||
const auto destroy = [](auto& slots) {
|
||||
for (auto& slot: slots) {
|
||||
if (slot.image != nullptr) {
|
||||
ImageOps::Destroy(*slot.image);
|
||||
slot.image = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
destroy(m_sampled);
|
||||
destroy(m_storage);
|
||||
}
|
||||
|
||||
VulkanImage& DummyTextureCache::Get(Usage usage, bool uint_format, bool image_3d) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto& slots = usage == Usage::Storage ? m_storage : m_sampled;
|
||||
auto& slot = slots[DummyTextureIndex(uint_format, image_3d)];
|
||||
if (slot.image == nullptr) {
|
||||
slot.image = ImageOps::CreateDummyTexture(uint_format, image_3d, usage == Usage::Storage);
|
||||
}
|
||||
return *slot.image;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class DummyTextureCache final {
|
||||
public:
|
||||
enum class Usage : uint8_t { Sampled, Storage };
|
||||
|
||||
DummyTextureCache() = default;
|
||||
~DummyTextureCache();
|
||||
KYTY_CLASS_NO_COPY(DummyTextureCache);
|
||||
|
||||
[[nodiscard]] VulkanImage& Get(Usage usage, bool uint_format, bool image_3d);
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
GpuTextureVulkanImage* image = nullptr;
|
||||
};
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
std::array<Slot, 4> m_sampled {};
|
||||
std::array<Slot, 4> m_storage {};
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
|
||||
@@ -1,357 +0,0 @@
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
VulkanFramebuffer* FramebufferCache::CreateFramebuffer(RenderColorInfo* colors,
|
||||
uint32_t requested_color_count,
|
||||
RenderDepthInfo& depth) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
EXIT_IF(colors == nullptr);
|
||||
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
|
||||
bool with_depth = (depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
|
||||
bool with_color[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
VulkanImage* first_color = nullptr;
|
||||
vk::Extent2D first_color_extent = {};
|
||||
uint32_t attachment_samples = 0;
|
||||
for (uint32_t i = 0; i < requested_color_count; i++) {
|
||||
with_color[i] = (colors[i].vulkan_buffer != nullptr);
|
||||
if (!with_color[i]) {
|
||||
break;
|
||||
}
|
||||
if (first_color == nullptr) {
|
||||
first_color = colors[i].vulkan_buffer;
|
||||
first_color_extent = colors[i].extent;
|
||||
attachment_samples = colors[i].samples;
|
||||
} else if (colors[i].extent.width != first_color_extent.width ||
|
||||
colors[i].extent.height != first_color_extent.height) {
|
||||
LOGF("Framebuffer: temporary: dropping mismatched MRT%u attachment color0=%ux%u "
|
||||
"color%u=%ux%u\n",
|
||||
i, first_color_extent.width, first_color_extent.height, i, colors[i].extent.width,
|
||||
colors[i].extent.height);
|
||||
with_color[i] = false;
|
||||
break;
|
||||
}
|
||||
if (colors[i].samples != attachment_samples ||
|
||||
colors[i].vulkan_buffer->samples != colors[i].samples) {
|
||||
EXIT("Framebuffer: mismatched color attachment samples at slot %u, expected=%u "
|
||||
"requested=%u image=%u\n",
|
||||
i, attachment_samples, colors[i].samples, colors[i].vulkan_buffer->samples);
|
||||
}
|
||||
color_count++;
|
||||
}
|
||||
if (with_depth) {
|
||||
if (depth.samples != depth.vulkan_buffer->samples) {
|
||||
EXIT("Framebuffer: depth attachment sample identity mismatch, requested=%u image=%u\n",
|
||||
depth.samples, depth.vulkan_buffer->samples);
|
||||
}
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.samples;
|
||||
} else if (attachment_samples != depth.samples) {
|
||||
EXIT(
|
||||
"Framebuffer: mixed color/depth sample counts are unsupported, color=%u depth=%u\n",
|
||||
attachment_samples, depth.samples);
|
||||
}
|
||||
}
|
||||
if (!with_depth && color_count == 0) {
|
||||
LOGF("Framebuffer: warning: no color or depth attachment\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
|
||||
EXIT("Framebuffer: invalid attachment sample count %u\n", attachment_samples);
|
||||
}
|
||||
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (auto& layout: color_layout) {
|
||||
layout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
auto depth_layout = (with_depth ? depth_attachment_layout(depth)
|
||||
: vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
||||
auto depth_read_only =
|
||||
(with_depth && depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal);
|
||||
|
||||
if (with_depth && first_color != nullptr &&
|
||||
(first_color_extent.width != depth.vulkan_buffer->extent.width ||
|
||||
first_color_extent.height != depth.vulkan_buffer->extent.height)) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("Framebuffer: temporary: dropping mismatched PS5 depth attachment color=%ux%u "
|
||||
"depth=%ux%u format=%s\n",
|
||||
first_color_extent.width, first_color_extent.height,
|
||||
depth.vulkan_buffer->extent.width, depth.vulkan_buffer->extent.height,
|
||||
VulkanToString(depth.format).c_str());
|
||||
}
|
||||
depth.format = vk::Format::eUndefined;
|
||||
depth.vulkan_buffer = nullptr;
|
||||
depth.vulkan_view = nullptr;
|
||||
depth.depth_test_enable = false;
|
||||
depth.depth_write_enable = false;
|
||||
depth.depth_bounds_test_enable = false;
|
||||
depth.stencil_test_enable = false;
|
||||
depth.depth_clear_enable = false;
|
||||
depth.depth_load_clear_enable = false;
|
||||
depth.stencil_clear_enable = false;
|
||||
with_depth = false;
|
||||
depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
depth_read_only = false;
|
||||
}
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
bool color_match = (f.framebuffer != nullptr);
|
||||
for (uint32_t i = 0; color_match && i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
const uint64_t image_id =
|
||||
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
|
||||
color_match =
|
||||
color_match && f.image_id[i] == image_id &&
|
||||
f.color_view[i] ==
|
||||
(i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr) &&
|
||||
f.color_clear_enable[i] ==
|
||||
(i < color_count && with_color[i] && colors[i].color_clear_enable) &&
|
||||
f.color_layout[i] == color_layout[i];
|
||||
}
|
||||
if (color_match && f.depth_id == (with_depth ? depth.vulkan_buffer->memory.unique_id : 0) &&
|
||||
f.depth_view == (with_depth ? depth.vulkan_view : nullptr) &&
|
||||
f.depth_clear_enable == depth.depth_load_clear_enable &&
|
||||
f.stencil_clear_enable == depth.stencil_clear_enable &&
|
||||
f.depth_read_only == depth_read_only) {
|
||||
return f.framebuffer;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(with_depth && first_color != nullptr &&
|
||||
(first_color_extent.width != depth.vulkan_buffer->extent.width ||
|
||||
first_color_extent.height != depth.vulkan_buffer->extent.height));
|
||||
|
||||
if (first_color == nullptr) {
|
||||
first_color_extent = depth.vulkan_buffer->extent;
|
||||
}
|
||||
|
||||
auto* framebuffer = new VulkanFramebuffer;
|
||||
framebuffer->render_pass = nullptr;
|
||||
framebuffer->framebuffer = nullptr;
|
||||
framebuffer->samples = attachment_samples;
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
framebuffer->color_layout[i] = color_layout[i];
|
||||
}
|
||||
framebuffer->depth_layout = depth_layout;
|
||||
|
||||
vk::AttachmentDescription attachments[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
attachments[i].flags = {};
|
||||
attachments[i].format = colors[i].format;
|
||||
attachments[i].samples = vulkan_sample_count(attachment_samples);
|
||||
attachments[i].loadOp = (colors[i].color_clear_enable ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad);
|
||||
attachments[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[i].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
||||
attachments[i].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
||||
attachments[i].initialLayout = color_layout[i];
|
||||
attachments[i].finalLayout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
|
||||
const uint32_t depth_attachment = color_count;
|
||||
attachments[depth_attachment].flags = {};
|
||||
attachments[depth_attachment].format = depth.format;
|
||||
attachments[depth_attachment].samples = vulkan_sample_count(attachment_samples);
|
||||
attachments[depth_attachment].loadOp =
|
||||
(depth.depth_load_clear_enable ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad);
|
||||
attachments[depth_attachment].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[depth_attachment].stencilLoadOp =
|
||||
(depth.stencil_clear_enable ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad);
|
||||
attachments[depth_attachment].stencilStoreOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[depth_attachment].initialLayout = depth_layout;
|
||||
attachments[depth_attachment].finalLayout = depth_layout;
|
||||
|
||||
vk::AttachmentReference color_attachment_ref[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
color_attachment_ref[i].attachment = (i < color_count ? i : VK_ATTACHMENT_UNUSED);
|
||||
color_attachment_ref[i].layout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
|
||||
vk::AttachmentReference depth_attachment_ref {};
|
||||
depth_attachment_ref.attachment = depth_attachment;
|
||||
depth_attachment_ref.layout = depth_layout;
|
||||
|
||||
vk::SubpassDescription subpass {};
|
||||
subpass.flags = {};
|
||||
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
|
||||
subpass.inputAttachmentCount = 0;
|
||||
subpass.pInputAttachments = nullptr;
|
||||
subpass.colorAttachmentCount = color_count;
|
||||
subpass.pColorAttachments = (color_count > 0 ? color_attachment_ref : nullptr);
|
||||
subpass.pResolveAttachments = nullptr;
|
||||
subpass.pDepthStencilAttachment = (with_depth ? &depth_attachment_ref : nullptr);
|
||||
subpass.preserveAttachmentCount = 0;
|
||||
subpass.pPreserveAttachments = nullptr;
|
||||
|
||||
const auto attachment_stage_mask =
|
||||
static_cast<vk::PipelineStageFlags>(vk::PipelineStageFlagBits::eColorAttachmentOutput |
|
||||
vk::PipelineStageFlagBits::eEarlyFragmentTests |
|
||||
vk::PipelineStageFlagBits::eLateFragmentTests);
|
||||
const auto attachment_access_mask = static_cast<vk::AccessFlags>(
|
||||
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentWrite);
|
||||
|
||||
vk::SubpassDependency dependencies[2] = {};
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eAllCommands;
|
||||
dependencies[0].dstStageMask = attachment_stage_mask;
|
||||
dependencies[0].srcAccessMask =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
dependencies[0].dstAccessMask = attachment_access_mask;
|
||||
dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].srcStageMask = attachment_stage_mask;
|
||||
dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eAllCommands;
|
||||
dependencies[1].srcAccessMask = attachment_access_mask;
|
||||
dependencies[1].dstAccessMask =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
|
||||
vk::RenderPassCreateInfo render_pass_info {};
|
||||
render_pass_info.sType = vk::StructureType::eRenderPassCreateInfo;
|
||||
render_pass_info.pNext = nullptr;
|
||||
render_pass_info.flags = {};
|
||||
render_pass_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
|
||||
render_pass_info.pAttachments = attachments;
|
||||
render_pass_info.subpassCount = 1;
|
||||
render_pass_info.pSubpasses = &subpass;
|
||||
render_pass_info.dependencyCount = 2;
|
||||
render_pass_info.pDependencies = dependencies;
|
||||
|
||||
auto result =
|
||||
m_graphics.device.createRenderPass(&render_pass_info, nullptr, &framebuffer->render_pass);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
vk::Format color_formats[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
color_formats[i] = colors[i].format;
|
||||
}
|
||||
framebuffer->render_pass_id = render_pass_compat_id(
|
||||
color_count, color_formats, with_depth, depth.format, depth_layout, attachment_samples);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(framebuffer->render_pass == nullptr);
|
||||
|
||||
vk::ImageView views[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
if (colors[i].vulkan_view == nullptr) {
|
||||
EXIT("Framebuffer: color attachment view is missing at slot %u\n", i);
|
||||
}
|
||||
views[i] = colors[i].vulkan_view;
|
||||
}
|
||||
if (with_depth) {
|
||||
if (depth.vulkan_view == nullptr) {
|
||||
EXIT("Framebuffer: depth attachment view is missing\n");
|
||||
}
|
||||
views[color_count] = depth.vulkan_view;
|
||||
}
|
||||
|
||||
vk::FramebufferCreateInfo framebuffer_info {};
|
||||
framebuffer_info.sType = vk::StructureType::eFramebufferCreateInfo;
|
||||
framebuffer_info.pNext = nullptr;
|
||||
framebuffer_info.flags = {};
|
||||
framebuffer_info.renderPass = framebuffer->render_pass;
|
||||
framebuffer_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
|
||||
framebuffer_info.pAttachments = views;
|
||||
framebuffer_info.width = first_color_extent.width;
|
||||
framebuffer_info.height = first_color_extent.height;
|
||||
framebuffer_info.layers = 1;
|
||||
|
||||
result =
|
||||
m_graphics.device.createFramebuffer(&framebuffer_info, nullptr, &framebuffer->framebuffer);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(framebuffer->framebuffer == nullptr);
|
||||
|
||||
Framebuffer fnew;
|
||||
fnew.framebuffer = framebuffer;
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
fnew.image_id[i] =
|
||||
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
|
||||
fnew.color_view[i] = (i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr);
|
||||
fnew.color_clear_enable[i] =
|
||||
(i < color_count && with_color[i] && colors[i].color_clear_enable);
|
||||
fnew.color_layout[i] = color_layout[i];
|
||||
}
|
||||
fnew.depth_id = (with_depth ? depth.vulkan_buffer->memory.unique_id : 0);
|
||||
fnew.depth_view = (with_depth ? depth.vulkan_view : nullptr);
|
||||
fnew.depth_clear_enable = depth.depth_load_clear_enable;
|
||||
fnew.stencil_clear_enable = depth.stencil_clear_enable;
|
||||
fnew.depth_read_only = depth_read_only;
|
||||
|
||||
bool updated = false;
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
if (f.framebuffer == nullptr) {
|
||||
f = fnew;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!updated) {
|
||||
m_framebuffers.push_back(fnew);
|
||||
}
|
||||
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
void FramebufferCache::FreeFramebufferByColor(VulkanImage& image) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
bool uses_image = false;
|
||||
for (auto image_id: f.image_id) {
|
||||
if (image_id == image.memory.unique_id) {
|
||||
uses_image = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (f.framebuffer != nullptr && uses_image) {
|
||||
m_graphics.device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
|
||||
|
||||
m_graphics.device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
|
||||
|
||||
delete f.framebuffer;
|
||||
|
||||
f.framebuffer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FramebufferCache::FreeFramebufferByDepth(DepthStencilVulkanImage& image) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
if (f.framebuffer != nullptr && f.depth_id == image.memory.unique_id) {
|
||||
m_graphics.device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
|
||||
|
||||
m_graphics.device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
|
||||
|
||||
delete f.framebuffer;
|
||||
|
||||
f.framebuffer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,85 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct RenderColorInfo;
|
||||
struct RenderDepthInfo;
|
||||
|
||||
static constexpr vk::ImageLayout RENDER_COLOR_IMAGE_LAYOUT = vk::ImageLayout::eGeneral;
|
||||
|
||||
struct VulkanFramebuffer {
|
||||
vk::RenderPass render_pass = nullptr;
|
||||
uint64_t render_pass_id = 0;
|
||||
vk::Framebuffer framebuffer = nullptr;
|
||||
uint32_t samples = 1;
|
||||
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
vk::ImageLayout depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
};
|
||||
|
||||
inline uint64_t render_pass_compat_id(uint32_t color_count, const vk::Format* color_formats,
|
||||
bool with_depth, vk::Format depth_format,
|
||||
vk::ImageLayout depth_layout, uint32_t samples) {
|
||||
uint64_t id = 0xcbf29ce484222325ull;
|
||||
auto mix = [&id](uint64_t v) {
|
||||
id ^= v;
|
||||
id *= 0x100000001b3ull;
|
||||
};
|
||||
|
||||
mix(color_count);
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
mix(static_cast<uint32_t>(color_formats[i]));
|
||||
}
|
||||
mix(with_depth ? 1u : 0u);
|
||||
mix(static_cast<uint32_t>(depth_format));
|
||||
mix(static_cast<uint32_t>(depth_layout));
|
||||
mix(samples);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
class FramebufferCache {
|
||||
public:
|
||||
explicit FramebufferCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
~FramebufferCache() { KYTY_NOT_IMPLEMENTED; }
|
||||
KYTY_CLASS_NO_COPY(FramebufferCache);
|
||||
|
||||
VulkanFramebuffer* CreateFramebuffer(RenderColorInfo* colors, uint32_t color_count,
|
||||
RenderDepthInfo& depth);
|
||||
void FreeFramebufferByColor(VulkanImage& image);
|
||||
void FreeFramebufferByDepth(DepthStencilVulkanImage& image);
|
||||
|
||||
private:
|
||||
struct Framebuffer {
|
||||
VulkanFramebuffer* framebuffer = nullptr;
|
||||
uint64_t image_id[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
vk::ImageView color_view[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint64_t depth_id = 0;
|
||||
vk::ImageView depth_view = nullptr;
|
||||
bool color_clear_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool depth_clear_enable = false;
|
||||
bool stencil_clear_enable = false;
|
||||
bool depth_read_only = false;
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
std::vector<Framebuffer> m_framebuffers;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
|
||||
@@ -1,67 +0,0 @@
|
||||
#include "graphics/host_gpu/renderer/gdsBuffer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void GdsBuffer::Init() {
|
||||
if (m_buffer == nullptr) {
|
||||
m_buffer = std::make_unique<VulkanBuffer>();
|
||||
|
||||
m_buffer->usage = vk::BufferUsageFlagBits::eStorageBuffer;
|
||||
m_buffer->memory.property = vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent |
|
||||
vk::MemoryPropertyFlagBits::eHostCached;
|
||||
m_graphics.CreateBuffer(DW_SIZE * 4, *m_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void GdsBuffer::Clear(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Init();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset + dw_num > DW_SIZE);
|
||||
|
||||
void* data = nullptr;
|
||||
m_graphics.MapMemory(m_buffer->memory, data);
|
||||
|
||||
for (uint32_t i = 0; i < dw_num; i++) {
|
||||
static_cast<uint32_t*>(data)[dw_offset + i] = clear_value;
|
||||
}
|
||||
|
||||
m_graphics.UnmapMemory(m_buffer->memory);
|
||||
}
|
||||
|
||||
void GdsBuffer::Read(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
|
||||
EXIT_IF(dst == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Init();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset + dw_size > DW_SIZE);
|
||||
|
||||
void* data = nullptr;
|
||||
m_graphics.MapMemory(m_buffer->memory, data);
|
||||
|
||||
for (uint32_t i = 0; i < dw_size; i++) {
|
||||
dst[i] = static_cast<uint32_t*>(data)[dw_offset + i];
|
||||
}
|
||||
|
||||
m_graphics.UnmapMemory(m_buffer->memory);
|
||||
}
|
||||
|
||||
VulkanBuffer& GdsBuffer::GetBuffer() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Init();
|
||||
|
||||
return *m_buffer;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class GdsBuffer {
|
||||
public:
|
||||
explicit GdsBuffer(GraphicContext& graphics): m_graphics(graphics) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
~GdsBuffer() { KYTY_NOT_IMPLEMENTED; }
|
||||
KYTY_CLASS_NO_COPY(GdsBuffer);
|
||||
|
||||
void Clear(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
|
||||
void Read(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
|
||||
|
||||
VulkanBuffer& GetBuffer();
|
||||
|
||||
private:
|
||||
static constexpr uint64_t DW_SIZE = 0x3000;
|
||||
|
||||
void Init();
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
std::unique_ptr<VulkanBuffer> m_buffer;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
|
||||
@@ -3,17 +3,14 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/host_gpu/objects/label.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
GpuResourceManager::GpuResourceManager(GraphicContext& graphics)
|
||||
: m_page_manager(FaultThunk, this), m_buffer_cache(graphics, m_page_manager, m_resource_mutex),
|
||||
m_texture_cache(graphics, m_page_manager, m_buffer_cache, m_resource_mutex) {
|
||||
m_buffer_cache.SetTextureCache(m_texture_cache);
|
||||
}
|
||||
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
: m_page_manager(FaultThunk, this),
|
||||
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
|
||||
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
|
||||
|
||||
GpuResourceManager::~GpuResourceManager() = default;
|
||||
|
||||
@@ -24,6 +21,15 @@ bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint6
|
||||
|
||||
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
// Let the authoritative image materialize first. A clean overlapping buffer marks a write
|
||||
// fault CPU-dirty when it begins ownership transfer; doing that before image preflight would
|
||||
// make the image appear to race a real CPU write. Completion and release retain buffer-first
|
||||
// ordering so its pending fault is gone before TextureCache publishes the downloaded backing.
|
||||
if (phase == PageFaultPhase::Invalidate) {
|
||||
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
return buffer_handled || image_handled;
|
||||
}
|
||||
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
return buffer_handled || image_handled;
|
||||
@@ -33,19 +39,23 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
|
||||
if (!m_page_manager.IsMapped(fault_vaddr, 1)) {
|
||||
return false;
|
||||
}
|
||||
if (LabelInCallback()) {
|
||||
EXIT("unsupported guest-memory fault from an asynchronous GPU label callback, "
|
||||
if (CommandScheduler::InDeferredOperation()) {
|
||||
EXIT("unsupported guest-memory fault from an asynchronous GPU completion, "
|
||||
"addr=0x%016" PRIx64 " access=%u\n",
|
||||
fault_vaddr, static_cast<uint32_t>(access));
|
||||
}
|
||||
if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) {
|
||||
cp->BeginReadbackTransaction();
|
||||
bool handled = false;
|
||||
bool handled = false;
|
||||
const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) {
|
||||
cp.BeginReadbackTransaction();
|
||||
(void)m_buffer_cache.SynchronizeBacking(fault_vaddr, 1);
|
||||
{
|
||||
ResourceMutex::FaultScope fault(m_resource_mutex);
|
||||
handled = m_page_manager.HandleFault(access, fault_vaddr);
|
||||
}
|
||||
cp->EndReadbackTransaction();
|
||||
cp.EndReadbackTransaction();
|
||||
};
|
||||
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
|
||||
resolve(*cp);
|
||||
return handled;
|
||||
}
|
||||
if (m_resource_mutex.IsOwnedByCurrentThread()) {
|
||||
@@ -53,36 +63,36 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
|
||||
" access=%u\n",
|
||||
fault_vaddr, static_cast<uint32_t>(access));
|
||||
}
|
||||
// Stop command-processor jobs before taking the shared cache transaction. External readback
|
||||
// workers inherit this paused state and therefore never form resource -> submission lock
|
||||
// inversion.
|
||||
GraphicsRunSubmissionLock submissions;
|
||||
ResourceMutex::FaultScope fault(m_resource_mutex);
|
||||
return m_page_manager.HandleFault(access, fault_vaddr);
|
||||
EXIT_IF(m_gpu == nullptr);
|
||||
m_gpu->SendCommandSyncWithProcessor(resolve);
|
||||
return handled;
|
||||
}
|
||||
|
||||
void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
|
||||
if (!m_page_manager.HasAnyMapping(vaddr, size)) {
|
||||
return;
|
||||
}
|
||||
if (LabelInCallback()) {
|
||||
EXIT("unsupported host write from an asynchronous GPU label callback, addr=0x%016" PRIx64
|
||||
if (CommandScheduler::InDeferredOperation()) {
|
||||
EXIT("unsupported host write from an asynchronous GPU completion, addr=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 "\n",
|
||||
vaddr, size);
|
||||
}
|
||||
const auto handle_range = [this, vaddr, size]() {
|
||||
const auto handle_range = [this, vaddr, size] {
|
||||
if (!m_page_manager.HandleWriteRange(vaddr, size)) {
|
||||
EXIT("failed to prepare host write, addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
vaddr, size);
|
||||
}
|
||||
};
|
||||
if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) {
|
||||
cp->BeginReadbackTransaction();
|
||||
const auto resolve = [this, &handle_range](CommandProcessor& cp) {
|
||||
cp.BeginReadbackTransaction();
|
||||
{
|
||||
ResourceMutex::FaultScope fault(m_resource_mutex);
|
||||
handle_range();
|
||||
}
|
||||
cp->EndReadbackTransaction();
|
||||
cp.EndReadbackTransaction();
|
||||
};
|
||||
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
|
||||
resolve(*cp);
|
||||
return;
|
||||
}
|
||||
if (m_resource_mutex.IsOwnedByCurrentThread()) {
|
||||
@@ -90,9 +100,8 @@ void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
|
||||
" size=0x%016" PRIx64 "\n",
|
||||
vaddr, size);
|
||||
}
|
||||
GraphicsRunSubmissionLock submissions;
|
||||
ResourceMutex::FaultScope fault(m_resource_mutex);
|
||||
handle_range();
|
||||
EXIT_IF(m_gpu == nullptr);
|
||||
m_gpu->SendCommandSyncWithProcessor(resolve);
|
||||
}
|
||||
|
||||
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
|
||||
@@ -107,27 +116,26 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess ac
|
||||
if (!IsMapped(vaddr, size)) {
|
||||
EXIT("cannot unmap an unmapped GPU resource range\n");
|
||||
}
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_buffer_cache.UnmapMemory(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size, access);
|
||||
const auto unmap = [this, vaddr, size, access] {
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_buffer_cache.UnmapMemory(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size, access);
|
||||
};
|
||||
if (m_gpu == nullptr) {
|
||||
if (m_resource_mutex.IsOwnedByCurrentThread()) {
|
||||
EXIT("cannot synchronously unmap from a resource transaction\n");
|
||||
}
|
||||
unmap();
|
||||
return;
|
||||
}
|
||||
Gpu::SubmissionLock submissions(*m_gpu);
|
||||
m_gpu->SendCommandSync(unmap);
|
||||
}
|
||||
|
||||
void GpuResourceManager::FillBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
|
||||
uint32_t value) {
|
||||
if (command.IsInvalid()) {
|
||||
EXIT("cannot fill a buffer without a valid render command context\n");
|
||||
}
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
m_buffer_cache.FillBuffer(&command, vaddr, size, value);
|
||||
}
|
||||
|
||||
void GpuResourceManager::CopyBuffer(CommandBuffer& command, uint64_t dst_vaddr, uint64_t src_vaddr,
|
||||
uint64_t size) {
|
||||
if (command.IsInvalid()) {
|
||||
EXIT("cannot copy a buffer without a valid render command context\n");
|
||||
}
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
m_buffer_cache.CopyBuffer(&command, dst_vaddr, src_vaddr, size);
|
||||
void GpuResourceManager::RunGarbageCollector() {
|
||||
m_texture_cache.ProcessDownloadImages();
|
||||
m_texture_cache.RunGarbageCollector();
|
||||
m_buffer_cache.RunGarbageCollector();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -12,24 +12,25 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
class CommandScheduler;
|
||||
class Gpu;
|
||||
|
||||
class GpuResourceManager {
|
||||
public:
|
||||
explicit GpuResourceManager(GraphicContext& graphics);
|
||||
GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler);
|
||||
~GpuResourceManager();
|
||||
KYTY_CLASS_NO_COPY(GpuResourceManager);
|
||||
|
||||
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
|
||||
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
|
||||
void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; }
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
|
||||
void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
|
||||
void FillBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size, uint32_t value);
|
||||
void CopyBuffer(CommandBuffer& command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
@@ -41,6 +42,7 @@ private:
|
||||
ResourceMutex m_resource_mutex;
|
||||
BufferCache m_buffer_cache;
|
||||
TextureCache m_texture_cache;
|
||||
Gpu* m_gpu = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,49 +2,93 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/imageInfo.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <compare>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct DepthStencilVulkanImage;
|
||||
struct GpuTextureVulkanImage;
|
||||
struct GraphicContext;
|
||||
struct RenderTextureVulkanImage;
|
||||
struct VideoOutVulkanImage;
|
||||
struct VulkanImage;
|
||||
class Buffer;
|
||||
class CommandScheduler;
|
||||
struct ImageTestAccess;
|
||||
|
||||
struct Image final: ImageInfo {
|
||||
Image& operator=(const ImageInfo& value) {
|
||||
if (IsCpuDirty()) {
|
||||
EXIT("dirty sampled image cannot be reassigned\n");
|
||||
}
|
||||
static_cast<ImageInfo&>(*this) = value;
|
||||
m_track_begin = address;
|
||||
m_track_end = address + size;
|
||||
m_maybe_cpu_hash_valid = false;
|
||||
return *this;
|
||||
struct ImageId {
|
||||
uint32_t index = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t generation = 0;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return index != std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
auto operator<=>(const ImageId&) const = default;
|
||||
};
|
||||
|
||||
struct CachedImageView {
|
||||
ImageViewInfo info;
|
||||
vk::ImageView view = nullptr;
|
||||
};
|
||||
|
||||
struct ImageViewCache {
|
||||
ImageViewCache() = default;
|
||||
KYTY_CLASS_NO_COPY(ImageViewCache);
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<CachedImageView> views;
|
||||
};
|
||||
|
||||
struct ImageUsage {
|
||||
bool texture = false;
|
||||
bool storage = false;
|
||||
bool render_target = false;
|
||||
bool depth_target = false;
|
||||
bool video_out = false;
|
||||
};
|
||||
|
||||
struct ImageBinding {
|
||||
bool is_bound = false;
|
||||
bool is_target = false;
|
||||
bool needs_rebind = false;
|
||||
bool force_general = false;
|
||||
};
|
||||
|
||||
class Image final {
|
||||
public:
|
||||
Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& info);
|
||||
~Image();
|
||||
KYTY_CLASS_NO_COPY(Image);
|
||||
|
||||
[[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info);
|
||||
void AssociateDepth(ImageId image_id) { depth_id = image_id; }
|
||||
using Barriers = std::vector<vk::ImageMemoryBarrier2>;
|
||||
[[nodiscard]] Barriers
|
||||
GetBarriers(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
|
||||
vk::PipelineStageFlags2 destination_stage,
|
||||
std::optional<ImageSubresourceRange> range);
|
||||
void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
|
||||
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer);
|
||||
void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
|
||||
uint64_t offset, uint64_t size);
|
||||
void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
|
||||
uint64_t offset, uint64_t size);
|
||||
void CopyImage(Image& source);
|
||||
void Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
const ImageSubresourceRange& destination_range);
|
||||
void CopyImageWithBuffer(Image& source, Buffer& buffer);
|
||||
void CopyMip(Image& source, uint32_t mip, uint32_t layer);
|
||||
|
||||
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
|
||||
if (ImageRangeOverlaps(address, this->size, vaddr, size)) {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_cpu_hash_valid = false;
|
||||
m_track_begin = m_track_end;
|
||||
} else if (ImagePageRangesOverlap(address, this->size, vaddr, size)) {
|
||||
constexpr uint64_t page_mask = 4096 - 1;
|
||||
if (vaddr + size <= address) {
|
||||
const auto next_page = (address + page_mask) & ~page_mask;
|
||||
m_track_begin = std::min(m_track_end, std::max(m_track_begin, next_page));
|
||||
} else if (vaddr >= address + this->size) {
|
||||
const auto page = (address + this->size) & ~page_mask;
|
||||
m_track_end = std::max(m_track_begin, std::min(m_track_end, page));
|
||||
}
|
||||
m_maybe_cpu_dirty = m_track_begin == m_track_end;
|
||||
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
|
||||
m_maybe_cpu_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,117 +96,93 @@ struct Image final: ImageInfo {
|
||||
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
|
||||
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
|
||||
[[nodiscard]] bool NeedsMaybeCpuHash() const {
|
||||
return m_maybe_cpu_dirty && !m_maybe_cpu_hash_valid;
|
||||
}
|
||||
[[nodiscard]] bool IsCpuTrackingComplete() const {
|
||||
return m_track_begin == address && m_track_end == address + size;
|
||||
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
|
||||
}
|
||||
void SetMaybeCpuHash(uint64_t hash) {
|
||||
if (!NeedsMaybeCpuHash()) {
|
||||
EXIT("sampled image cannot initialize maybe-dirty hash\n");
|
||||
EXIT("image cannot initialize maybe-dirty hash\n");
|
||||
}
|
||||
m_maybe_cpu_hash = hash;
|
||||
m_maybe_cpu_hash_valid = true;
|
||||
m_maybe_cpu_hash = hash;
|
||||
m_maybe_hash_valid = true;
|
||||
}
|
||||
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
|
||||
if (!m_maybe_cpu_dirty || !m_maybe_cpu_hash_valid || m_cpu_dirty) {
|
||||
EXIT("sampled image cannot resolve maybe-dirty hash\n");
|
||||
}
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_cpu_hash_valid = false;
|
||||
m_cpu_dirty = hash != m_maybe_cpu_hash;
|
||||
if (!m_cpu_dirty) {
|
||||
m_track_begin = address;
|
||||
m_track_end = address + size;
|
||||
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
|
||||
EXIT("image cannot resolve maybe-dirty hash\n");
|
||||
}
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
m_cpu_dirty |= hash != m_maybe_cpu_hash;
|
||||
return m_cpu_dirty;
|
||||
}
|
||||
|
||||
void RefreshComplete() {
|
||||
if (!IsCpuDirty()) {
|
||||
EXIT("clean sampled image cannot complete a refresh\n");
|
||||
EXIT("clean image cannot complete a refresh\n");
|
||||
}
|
||||
m_cpu_dirty = false;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_cpu_hash_valid = false;
|
||||
m_track_begin = address;
|
||||
m_track_end = address + size;
|
||||
m_cpu_dirty = false;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
|
||||
void MarkGpuModified() noexcept { m_gpu_modified = true; }
|
||||
void ClearGpuModified() noexcept { m_gpu_modified = false; }
|
||||
|
||||
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
|
||||
void MarkBufferModified() noexcept { m_buffer_modified = true; }
|
||||
void ClearBufferModified() noexcept { m_buffer_modified = false; }
|
||||
|
||||
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size, bool pages = false) const noexcept {
|
||||
return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size)
|
||||
: ImageRangeOverlaps(info.data.address, info.data.size, address, size);
|
||||
}
|
||||
[[nodiscard]] bool GpuOverlaps(uint64_t address, uint64_t size) const noexcept {
|
||||
return IsGpuModified() && Overlaps(address, size);
|
||||
}
|
||||
[[nodiscard]] bool SafeToDownload() const noexcept {
|
||||
return IsGpuModified() && !IsBufferModified() && !IsCpuDirty();
|
||||
}
|
||||
[[nodiscard]] uint64_t AccountedSize() const noexcept {
|
||||
return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023};
|
||||
}
|
||||
[[nodiscard]] uint64_t HashGuestEdges() const;
|
||||
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
ImageViewCache views;
|
||||
ImageUsage usage;
|
||||
ImageBinding binding;
|
||||
bool registered = false;
|
||||
ImageId depth_id {};
|
||||
uint64_t tick_accessed_last = 0;
|
||||
size_t lru_id = 0;
|
||||
|
||||
private:
|
||||
bool m_cpu_dirty = false;
|
||||
bool m_maybe_cpu_dirty = false;
|
||||
bool m_maybe_cpu_hash_valid = false;
|
||||
uint64_t m_track_begin = 0;
|
||||
uint64_t m_track_end = 0;
|
||||
uint64_t m_maybe_cpu_hash = 0;
|
||||
friend struct ImageTestAccess;
|
||||
|
||||
[[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept;
|
||||
[[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
|
||||
uint64_t capacity) noexcept;
|
||||
[[nodiscard]] static std::pair<uint32_t, uint32_t>
|
||||
SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth);
|
||||
|
||||
GraphicContext* m_graphics = nullptr;
|
||||
CommandScheduler* m_scheduler = nullptr;
|
||||
uint64_t m_maybe_cpu_hash = 0;
|
||||
bool m_cpu_dirty = false;
|
||||
bool m_maybe_cpu_dirty = false;
|
||||
bool m_maybe_hash_valid = false;
|
||||
bool m_gpu_modified = false;
|
||||
bool m_buffer_modified = false;
|
||||
};
|
||||
|
||||
namespace ImageOps {
|
||||
|
||||
[[nodiscard]] GpuTextureVulkanImage* CreateTexture(const ImageInfo& info,
|
||||
bool storage, vk::ComponentMapping& components);
|
||||
void CreateTextureViews(GpuTextureVulkanImage& image,
|
||||
const ImageInfo& info, bool storage, vk::ComponentMapping components);
|
||||
|
||||
[[nodiscard]] RenderTextureVulkanImage* CreateRenderTarget(
|
||||
const RenderTargetInfo& info);
|
||||
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
|
||||
void UploadRenderTargetLayers(RenderTextureVulkanImage& image,
|
||||
const RenderTargetInfo& info, uint32_t base_layer,
|
||||
uint32_t layer_count, bool refresh);
|
||||
void UploadRenderTarget(RenderTextureVulkanImage& image,
|
||||
const RenderTargetInfo& info, bool refresh);
|
||||
|
||||
[[nodiscard]] DepthStencilVulkanImage* CreateDepthTarget(
|
||||
const DepthTargetInfo& info);
|
||||
|
||||
void ValidateVideoOut(const VideoOutInfo& info);
|
||||
[[nodiscard]] VideoOutVulkanImage* CreateVideoOut(
|
||||
const VideoOutInfo& info);
|
||||
void SwapVideoOutBgra16(void* data, uint64_t size);
|
||||
void UploadVideoOut(VideoOutVulkanImage& image, const VideoOutInfo& info,
|
||||
bool refresh);
|
||||
|
||||
[[nodiscard]] GpuTextureVulkanImage* CreateDummyTexture(bool uint_format,
|
||||
bool image_3d, bool storage);
|
||||
|
||||
void Destroy(VulkanImage& image);
|
||||
void Validate(const ImageInfo& info);
|
||||
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
|
||||
|
||||
} // namespace ImageOps
|
||||
|
||||
struct ImageRetirementRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
bool retire = false;
|
||||
};
|
||||
|
||||
struct ImageRetirementConflict {
|
||||
size_t retired = SIZE_MAX;
|
||||
size_t retained = SIZE_MAX;
|
||||
|
||||
[[nodiscard]] bool Exists() const { return retired != SIZE_MAX; }
|
||||
};
|
||||
|
||||
[[nodiscard]] inline ImageRetirementConflict
|
||||
FindImageRetirementConflict(std::span<const ImageRetirementRange> ranges) {
|
||||
for (size_t retired = 0; retired < ranges.size(); retired++) {
|
||||
if (!ranges[retired].retire) {
|
||||
continue;
|
||||
}
|
||||
for (size_t retained = 0; retained < ranges.size(); retained++) {
|
||||
if (ranges[retained].retire) {
|
||||
continue;
|
||||
}
|
||||
if (ImageRangeOverlaps(ranges[retired].address, ranges[retired].size,
|
||||
ranges[retained].address, ranges[retained].size)) {
|
||||
return {retired, retained};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,8 @@
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/textureCache.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
@@ -11,62 +10,297 @@ namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
void CreateView(VulkanImage& image, int view_index,
|
||||
vk::ImageViewType view_type, vk::ImageAspectFlags aspect_mask,
|
||||
vk::ComponentMapping components, uint32_t base_array_layer, uint32_t base_mip_level,
|
||||
uint32_t layer_count, uint32_t level_count,
|
||||
vk::Format view_format = vk::Format::eUndefined,
|
||||
vk::ImageUsageFlags view_usage = {}) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
if (view_index < 0 || view_index >= VulkanImage::VIEW_MAX ||
|
||||
image.image_view[view_index] != nullptr) {
|
||||
EXIT("invalid image-view creation target: image=%p index=%d current_view=%d\n",
|
||||
static_cast<const void*>(&image), view_index,
|
||||
view_index >= 0 && view_index < VulkanImage::VIEW_MAX &&
|
||||
image.image_view[view_index] != nullptr);
|
||||
}
|
||||
|
||||
vk::ImageViewUsageCreateInfo usage_info {};
|
||||
usage_info.sType = vk::StructureType::eImageViewUsageCreateInfo;
|
||||
usage_info.usage = view_usage;
|
||||
|
||||
vk::ImageViewCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eImageViewCreateInfo;
|
||||
create_info.pNext = view_usage ? &usage_info : nullptr;
|
||||
create_info.image = image.image;
|
||||
create_info.viewType = view_type;
|
||||
create_info.format = view_format != vk::Format::eUndefined ? view_format : image.format;
|
||||
create_info.components = components;
|
||||
create_info.subresourceRange.aspectMask = aspect_mask;
|
||||
create_info.subresourceRange.baseArrayLayer = base_array_layer;
|
||||
create_info.subresourceRange.baseMipLevel = base_mip_level;
|
||||
create_info.subresourceRange.layerCount = layer_count;
|
||||
create_info.subresourceRange.levelCount = level_count;
|
||||
|
||||
const auto result =
|
||||
graphics.device.createImageView(&create_info, nullptr, &image.image_view[view_index]);
|
||||
if (result != vk::Result::eSuccess || image.image_view[view_index] == nullptr) {
|
||||
EXIT("failed to create image view: result=%d image_format=%d view_format=%d index=%d\n",
|
||||
static_cast<int>(result), static_cast<int>(image.format),
|
||||
static_cast<int>(create_info.format), view_index);
|
||||
[[nodiscard]] bool IsComponentSwizzle(vk::ComponentSwizzle swizzle) {
|
||||
switch (swizzle) {
|
||||
case vk::ComponentSwizzle::eIdentity:
|
||||
case vk::ComponentSwizzle::eZero:
|
||||
case vk::ComponentSwizzle::eOne:
|
||||
case vk::ComponentSwizzle::eR:
|
||||
case vk::ComponentSwizzle::eG:
|
||||
case vk::ComponentSwizzle::eB:
|
||||
case vk::ComponentSwizzle::eA: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateRenderTargetView(VulkanImage& image, int index,
|
||||
vk::ComponentSwizzle r, vk::ComponentSwizzle g, vk::ComponentSwizzle b,
|
||||
vk::ComponentSwizzle a, vk::ImageViewType type = vk::ImageViewType::e2D,
|
||||
vk::Format view_format = vk::Format::eUndefined,
|
||||
vk::ImageUsageFlags view_usage = {}, uint32_t level_count = 0) {
|
||||
const auto layer_count = type == vk::ImageViewType::e2DArray ? image.layers : 1u;
|
||||
CreateView(image, index, type, vk::ImageAspectFlagBits::eColor, {r, g, b, a}, 0, 0,
|
||||
layer_count, level_count == 0 ? image.mip_levels : level_count, view_format,
|
||||
view_usage);
|
||||
[[nodiscard]] bool IsCompatibleViewFormat(vk::Format image_format, vk::Format view_format) {
|
||||
return ImageViewOps::FormatsCompatible(image_format, view_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsStencilViewFormat(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eS8Uint:
|
||||
case vk::Format::eR8Uint:
|
||||
case vk::Format::eR8Unorm: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsDepthViewFormat(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eD16Unorm:
|
||||
case vk::Format::eR16Unorm:
|
||||
case vk::Format::eD32Sfloat:
|
||||
case vk::Format::eR32Sfloat:
|
||||
case vk::Format::eR32Uint: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsValidViewType(const VulkanImage& image, const ImageViewInfo& info) {
|
||||
switch (image.image_type) {
|
||||
case vk::ImageType::e1D:
|
||||
if (info.type != vk::ImageViewType::e1D && info.type != vk::ImageViewType::e1DArray) {
|
||||
return false;
|
||||
}
|
||||
return info.type != vk::ImageViewType::e1D || info.layer_count == 1;
|
||||
case vk::ImageType::e2D:
|
||||
switch (info.type) {
|
||||
case vk::ImageViewType::e2D: return info.layer_count == 1;
|
||||
case vk::ImageViewType::e2DArray: return true;
|
||||
case vk::ImageViewType::eCube:
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::eCubeCompatible) &&
|
||||
info.base_layer % 6 == 0 && info.layer_count == 6;
|
||||
case vk::ImageViewType::eCubeArray:
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::eCubeCompatible) &&
|
||||
info.base_layer % 6 == 0 && info.layer_count % 6 == 0;
|
||||
default: return false;
|
||||
}
|
||||
case vk::ImageType::e3D:
|
||||
switch (info.type) {
|
||||
case vk::ImageViewType::e3D:
|
||||
return info.base_layer == 0 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2D:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2DArray:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1;
|
||||
default: return false;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsValidAspect(const VulkanImage& image, vk::ImageAspectFlags aspect) {
|
||||
const auto depth_format = DepthAspectTransferFormat(image.format);
|
||||
if (depth_format == vk::Format::eUndefined) {
|
||||
return aspect == vk::ImageAspectFlagBits::eColor;
|
||||
}
|
||||
const auto supported = ImageViewOps::DepthAspectMask(image.format);
|
||||
return static_cast<bool>(aspect) && !(aspect & ~supported);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ImageViewOps {
|
||||
|
||||
namespace {
|
||||
|
||||
enum CompatibilityClass : uint32_t {
|
||||
None = 0,
|
||||
Bit8 = 1u << 0,
|
||||
Bit16 = 1u << 1,
|
||||
Bit24 = 1u << 2,
|
||||
Bit32 = 1u << 3,
|
||||
Bit48 = 1u << 4,
|
||||
Bit64 = 1u << 5,
|
||||
Bit96 = 1u << 6,
|
||||
Bit128 = 1u << 7,
|
||||
Bit192 = 1u << 8,
|
||||
Bit256 = 1u << 9,
|
||||
Bc1Rgb = 1u << 10,
|
||||
Bc1Rgba = 1u << 11,
|
||||
Bc2 = 1u << 12,
|
||||
Bc3 = 1u << 13,
|
||||
Bc4 = 1u << 14,
|
||||
Bc5 = 1u << 15,
|
||||
Bc6h = 1u << 16,
|
||||
Bc7 = 1u << 17,
|
||||
D16 = 1u << 18,
|
||||
D16S8 = 1u << 19,
|
||||
D24 = 1u << 20,
|
||||
D24S8 = 1u << 21,
|
||||
D32 = 1u << 22,
|
||||
D32S8 = 1u << 23,
|
||||
S8 = 1u << 24,
|
||||
};
|
||||
|
||||
[[nodiscard]] uint32_t FormatClass(vk::Format format) noexcept {
|
||||
switch (format) {
|
||||
case vk::Format::eR4G4UnormPack8:
|
||||
case vk::Format::eR8Sint:
|
||||
case vk::Format::eR8Snorm:
|
||||
case vk::Format::eR8Srgb:
|
||||
case vk::Format::eR8Sscaled:
|
||||
case vk::Format::eR8Uint:
|
||||
case vk::Format::eR8Unorm:
|
||||
case vk::Format::eR8Uscaled: return Bit8;
|
||||
|
||||
case vk::Format::eA1R5G5B5UnormPack16:
|
||||
case vk::Format::eA4B4G4R4UnormPack16:
|
||||
case vk::Format::eA4R4G4B4UnormPack16:
|
||||
case vk::Format::eB4G4R4A4UnormPack16:
|
||||
case vk::Format::eB5G5R5A1UnormPack16:
|
||||
case vk::Format::eB5G6R5UnormPack16:
|
||||
case vk::Format::eR10X6UnormPack16:
|
||||
case vk::Format::eR12X4UnormPack16:
|
||||
case vk::Format::eR16Sfloat:
|
||||
case vk::Format::eR16Sint:
|
||||
case vk::Format::eR16Snorm:
|
||||
case vk::Format::eR16Sscaled:
|
||||
case vk::Format::eR16Uint:
|
||||
case vk::Format::eR16Unorm:
|
||||
case vk::Format::eR16Uscaled:
|
||||
case vk::Format::eR4G4B4A4UnormPack16:
|
||||
case vk::Format::eR5G5B5A1UnormPack16:
|
||||
case vk::Format::eR5G6B5UnormPack16:
|
||||
case vk::Format::eR8G8Sint:
|
||||
case vk::Format::eR8G8Snorm:
|
||||
case vk::Format::eR8G8Srgb:
|
||||
case vk::Format::eR8G8Sscaled:
|
||||
case vk::Format::eR8G8Uint:
|
||||
case vk::Format::eR8G8Unorm:
|
||||
case vk::Format::eR8G8Uscaled: return Bit16;
|
||||
|
||||
case vk::Format::eB8G8R8Sint:
|
||||
case vk::Format::eB8G8R8Snorm:
|
||||
case vk::Format::eB8G8R8Srgb:
|
||||
case vk::Format::eB8G8R8Sscaled:
|
||||
case vk::Format::eB8G8R8Uint:
|
||||
case vk::Format::eB8G8R8Unorm:
|
||||
case vk::Format::eB8G8R8Uscaled:
|
||||
case vk::Format::eR8G8B8Sint:
|
||||
case vk::Format::eR8G8B8Snorm:
|
||||
case vk::Format::eR8G8B8Srgb:
|
||||
case vk::Format::eR8G8B8Sscaled:
|
||||
case vk::Format::eR8G8B8Uint:
|
||||
case vk::Format::eR8G8B8Unorm:
|
||||
case vk::Format::eR8G8B8Uscaled: return Bit24;
|
||||
|
||||
case vk::Format::eA2B10G10R10SintPack32:
|
||||
case vk::Format::eA2B10G10R10SnormPack32:
|
||||
case vk::Format::eA2B10G10R10SscaledPack32:
|
||||
case vk::Format::eA2B10G10R10UintPack32:
|
||||
case vk::Format::eA2B10G10R10UnormPack32:
|
||||
case vk::Format::eA2B10G10R10UscaledPack32:
|
||||
case vk::Format::eA2R10G10B10SintPack32:
|
||||
case vk::Format::eA2R10G10B10SnormPack32:
|
||||
case vk::Format::eA2R10G10B10SscaledPack32:
|
||||
case vk::Format::eA2R10G10B10UintPack32:
|
||||
case vk::Format::eA2R10G10B10UnormPack32:
|
||||
case vk::Format::eA2R10G10B10UscaledPack32:
|
||||
case vk::Format::eA8B8G8R8SintPack32:
|
||||
case vk::Format::eA8B8G8R8SnormPack32:
|
||||
case vk::Format::eA8B8G8R8SrgbPack32:
|
||||
case vk::Format::eA8B8G8R8SscaledPack32:
|
||||
case vk::Format::eA8B8G8R8UintPack32:
|
||||
case vk::Format::eA8B8G8R8UnormPack32:
|
||||
case vk::Format::eA8B8G8R8UscaledPack32:
|
||||
case vk::Format::eB10G11R11UfloatPack32:
|
||||
case vk::Format::eB8G8R8A8Sint:
|
||||
case vk::Format::eB8G8R8A8Snorm:
|
||||
case vk::Format::eB8G8R8A8Srgb:
|
||||
case vk::Format::eB8G8R8A8Sscaled:
|
||||
case vk::Format::eB8G8R8A8Uint:
|
||||
case vk::Format::eB8G8R8A8Unorm:
|
||||
case vk::Format::eB8G8R8A8Uscaled:
|
||||
case vk::Format::eE5B9G9R9UfloatPack32:
|
||||
case vk::Format::eR10X6G10X6Unorm2Pack16:
|
||||
case vk::Format::eR12X4G12X4Unorm2Pack16:
|
||||
case vk::Format::eR16G16Sfloat:
|
||||
case vk::Format::eR16G16Sint:
|
||||
case vk::Format::eR16G16Snorm:
|
||||
case vk::Format::eR16G16Sscaled:
|
||||
case vk::Format::eR16G16Uint:
|
||||
case vk::Format::eR16G16Unorm:
|
||||
case vk::Format::eR16G16Uscaled:
|
||||
case vk::Format::eR32Sfloat:
|
||||
case vk::Format::eR32Sint:
|
||||
case vk::Format::eR32Uint:
|
||||
case vk::Format::eR8G8B8A8Sint:
|
||||
case vk::Format::eR8G8B8A8Snorm:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eR8G8B8A8Sscaled:
|
||||
case vk::Format::eR8G8B8A8Uint:
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Uscaled: return Bit32;
|
||||
|
||||
case vk::Format::eR16G16B16Sfloat:
|
||||
case vk::Format::eR16G16B16Sint:
|
||||
case vk::Format::eR16G16B16Snorm:
|
||||
case vk::Format::eR16G16B16Sscaled:
|
||||
case vk::Format::eR16G16B16Uint:
|
||||
case vk::Format::eR16G16B16Unorm:
|
||||
case vk::Format::eR16G16B16Uscaled: return Bit48;
|
||||
|
||||
case vk::Format::eR16G16B16A16Sfloat:
|
||||
case vk::Format::eR16G16B16A16Sint:
|
||||
case vk::Format::eR16G16B16A16Snorm:
|
||||
case vk::Format::eR16G16B16A16Sscaled:
|
||||
case vk::Format::eR16G16B16A16Uint:
|
||||
case vk::Format::eR16G16B16A16Unorm:
|
||||
case vk::Format::eR16G16B16A16Uscaled:
|
||||
case vk::Format::eR32G32Sfloat:
|
||||
case vk::Format::eR32G32Sint:
|
||||
case vk::Format::eR32G32Uint:
|
||||
case vk::Format::eR64Sfloat:
|
||||
case vk::Format::eR64Sint:
|
||||
case vk::Format::eR64Uint: return Bit64;
|
||||
|
||||
case vk::Format::eR32G32B32Sfloat:
|
||||
case vk::Format::eR32G32B32Sint:
|
||||
case vk::Format::eR32G32B32Uint: return Bit96;
|
||||
|
||||
case vk::Format::eR32G32B32A32Sfloat:
|
||||
case vk::Format::eR32G32B32A32Sint:
|
||||
case vk::Format::eR32G32B32A32Uint:
|
||||
case vk::Format::eR64G64Sfloat:
|
||||
case vk::Format::eR64G64Sint:
|
||||
case vk::Format::eR64G64Uint: return Bit128;
|
||||
|
||||
case vk::Format::eR64G64B64Sfloat:
|
||||
case vk::Format::eR64G64B64Sint:
|
||||
case vk::Format::eR64G64B64Uint: return Bit192;
|
||||
|
||||
case vk::Format::eR64G64B64A64Sfloat:
|
||||
case vk::Format::eR64G64B64A64Sint:
|
||||
case vk::Format::eR64G64B64A64Uint: return Bit256;
|
||||
|
||||
case vk::Format::eBc1RgbSrgbBlock:
|
||||
case vk::Format::eBc1RgbUnormBlock: return Bc1Rgb | Bit64;
|
||||
case vk::Format::eBc1RgbaSrgbBlock:
|
||||
case vk::Format::eBc1RgbaUnormBlock: return Bc1Rgba | Bit64;
|
||||
case vk::Format::eBc2SrgbBlock:
|
||||
case vk::Format::eBc2UnormBlock: return Bc2 | Bit128;
|
||||
case vk::Format::eBc3SrgbBlock:
|
||||
case vk::Format::eBc3UnormBlock: return Bc3 | Bit128;
|
||||
case vk::Format::eBc4SnormBlock:
|
||||
case vk::Format::eBc4UnormBlock: return Bc4 | Bit64;
|
||||
case vk::Format::eBc5SnormBlock:
|
||||
case vk::Format::eBc5UnormBlock: return Bc5 | Bit128;
|
||||
case vk::Format::eBc6HSfloatBlock:
|
||||
case vk::Format::eBc6HUfloatBlock: return Bc6h | Bit128;
|
||||
case vk::Format::eBc7SrgbBlock:
|
||||
case vk::Format::eBc7UnormBlock: return Bc7 | Bit128;
|
||||
|
||||
case vk::Format::eD16Unorm: return D16;
|
||||
case vk::Format::eD16UnormS8Uint: return D16S8;
|
||||
case vk::Format::eX8D24UnormPack32: return D24;
|
||||
case vk::Format::eD24UnormS8Uint: return D24S8;
|
||||
case vk::Format::eD32Sfloat: return D32;
|
||||
case vk::Format::eD32SfloatS8Uint: return D32S8;
|
||||
case vk::Format::eS8Uint: return S8;
|
||||
default: return None;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::ImageAspectFlags DepthAspectMask(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eD16Unorm:
|
||||
@@ -79,314 +313,104 @@ vk::ImageAspectFlags DepthAspectMask(vk::Format format) {
|
||||
}
|
||||
}
|
||||
|
||||
bool FormatSupportsStorage(vk::Format format) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
const auto properties = graphics.GetFormatProperties(format);
|
||||
return static_cast<bool>(properties.optimalTilingFeatures &
|
||||
vk::FormatFeatureFlagBits::eStorageImage);
|
||||
}
|
||||
|
||||
void CreateRenderTargetViews(RenderTextureVulkanImage& image) {
|
||||
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity);
|
||||
if (image.layers > 1) {
|
||||
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT_ARRAY,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ImageViewType::e2DArray);
|
||||
}
|
||||
if (image.samples == 1 && FormatSupportsStorage(image.format)) {
|
||||
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ImageViewType::e2D, vk::Format::eUndefined,
|
||||
vk::ImageUsageFlags {}, 1);
|
||||
if (image.layers > 1) {
|
||||
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE_ARRAY,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ImageViewType::e2DArray, vk::Format::eUndefined,
|
||||
vk::ImageUsageFlags {}, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDepthViews(DepthStencilVulkanImage& image) {
|
||||
CreateView(image, VulkanImage::VIEW_DEFAULT, vk::ImageViewType::e2D,
|
||||
DepthAspectMask(image.format),
|
||||
{vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity},
|
||||
0, 0, 1, 1);
|
||||
}
|
||||
|
||||
void CreateVideoOutViews(VideoOutVulkanImage& image) {
|
||||
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity);
|
||||
if ((image.format == vk::Format::eR8G8B8A8Srgb || image.format == vk::Format::eB8G8R8A8Srgb) &&
|
||||
FormatSupportsStorage(vk::Format::eR8G8B8A8Uint)) {
|
||||
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
|
||||
vk::ImageViewType::e2D, vk::Format::eR8G8B8A8Uint,
|
||||
vk::ImageUsageFlagBits::eStorage, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyViews(VulkanImage& image) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
for (auto& cached: image.view_cache.views) {
|
||||
if (cached.view != nullptr) {
|
||||
graphics.device.destroyImageView(cached.view, nullptr);
|
||||
cached.view = nullptr;
|
||||
}
|
||||
}
|
||||
image.view_cache.views.clear();
|
||||
for (auto& view: image.image_view) {
|
||||
if (view != nullptr) {
|
||||
graphics.device.destroyImageView(view, nullptr);
|
||||
view = nullptr;
|
||||
}
|
||||
bool FormatsCompatible(vk::Format base, vk::Format view) noexcept {
|
||||
if (base == view) {
|
||||
return true;
|
||||
}
|
||||
const auto base_class = FormatClass(base);
|
||||
const auto view_class = FormatClass(view);
|
||||
return view_class != None && (base_class & view_class) == view_class;
|
||||
}
|
||||
|
||||
} // namespace ImageViewOps
|
||||
|
||||
vk::ImageView TextureCache::GetRenderTargetAttachmentView(RenderTextureVulkanImage& image,
|
||||
vk::Format format, uint32_t level,
|
||||
uint32_t base_layer,
|
||||
uint32_t layer_count) {
|
||||
if (image.format == vk::Format::eUndefined || level >= image.mip_levels || level >= 16 ||
|
||||
layer_count == 0 || base_layer >= image.layers || layer_count > image.layers - base_layer) {
|
||||
EXIT("TextureCache: invalid render-target attachment view, image=%p format=%d"
|
||||
" level=%u image_levels=%u base_layer=%u layer_count=%u image_layers=%u\n",
|
||||
static_cast<const void*>(&image), static_cast<int>(format), level, image.mip_levels,
|
||||
base_layer, layer_count, image.layers);
|
||||
vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
const auto& image = backing;
|
||||
auto normalized = view_info;
|
||||
const bool is_storage =
|
||||
static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
|
||||
normalized.aspect = FullAspectMask(image.format);
|
||||
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
|
||||
IsDepthViewFormat(normalized.format)) {
|
||||
normalized.format = image.format;
|
||||
normalized.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
}
|
||||
if (format != image.format && !IsRgba8SrgbReinterpretation(image.format, format)) {
|
||||
EXIT("TextureCache: incompatible render-target attachment view, image_format=%d"
|
||||
" view_format=%d level=%u\n",
|
||||
static_cast<int>(image.format), static_cast<int>(format), level);
|
||||
if (normalized.aspect & vk::ImageAspectFlagBits::eStencil &&
|
||||
IsStencilViewFormat(normalized.format)) {
|
||||
normalized.format = image.format;
|
||||
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
|
||||
}
|
||||
normalized.usage =
|
||||
is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
|
||||
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
|
||||
IsCompatibleViewFormat(image.format, normalized.format);
|
||||
const bool slice_view = image.image_type == vk::ImageType::e3D &&
|
||||
(normalized.type == vk::ImageViewType::e2D ||
|
||||
normalized.type == vk::ImageViewType::e2DArray);
|
||||
const bool levels_valid = normalized.level_count != 0 &&
|
||||
normalized.base_level < image.mip_levels &&
|
||||
normalized.level_count <= image.mip_levels - normalized.base_level;
|
||||
const auto view_layers = slice_view && levels_valid
|
||||
? std::max(image.extent.depth >> normalized.base_level, 1u)
|
||||
: image.layers;
|
||||
const bool ranges_valid = levels_valid &&
|
||||
normalized.layer_count != 0 && normalized.base_layer < view_layers &&
|
||||
normalized.layer_count <= view_layers - normalized.base_layer;
|
||||
const bool mapping_valid =
|
||||
IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) &&
|
||||
IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a);
|
||||
if (image.image == nullptr || !format_compatible || !ranges_valid || !mapping_valid ||
|
||||
!IsValidViewType(image, normalized) ||
|
||||
!IsValidAspect(image, normalized.aspect)) {
|
||||
EXIT("invalid image view: image_format=%d view_format=%d type=%d aspect=0x%x "
|
||||
"mip=%u+%u layer=%u+%u usage=0x%x image_levels=%u image_layers=%u\n",
|
||||
static_cast<int>(image.format), static_cast<int>(normalized.format),
|
||||
static_cast<int>(normalized.type),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(normalized.aspect), normalized.base_level,
|
||||
normalized.level_count, normalized.base_layer, normalized.layer_count,
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(normalized.usage), image.mip_levels,
|
||||
image.layers);
|
||||
}
|
||||
|
||||
return GetImageView(
|
||||
image, {format, layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray,
|
||||
vk::ImageAspectFlagBits::eColor, level, 1, base_layer, layer_count,
|
||||
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eColorAttachment});
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::GetDepthTargetAttachmentView(DepthStencilVulkanImage& image,
|
||||
uint32_t base_layer,
|
||||
uint32_t layer_count) {
|
||||
if (layer_count == 0 || base_layer >= image.layers || layer_count > image.layers - base_layer) {
|
||||
EXIT("TextureCache: invalid depth-target attachment view, image=%p base_layer=%u "
|
||||
"layer_count=%u image_layers=%u\n",
|
||||
static_cast<const void*>(&image), base_layer, layer_count, image.layers);
|
||||
}
|
||||
return GetImageView(image,
|
||||
{image.format,
|
||||
layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray,
|
||||
ImageViewOps::DepthAspectMask(image.format), 0, 1, base_layer, layer_count,
|
||||
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eDepthStencilAttachment});
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::GetImageView(VulkanImage& image, const ImageViewInfo& info) {
|
||||
const bool supported_type = info.type == vk::ImageViewType::e2D ||
|
||||
info.type == vk::ImageViewType::e2DArray ||
|
||||
info.type == vk::ImageViewType::e3D;
|
||||
const bool supported_usage = info.usage == vk::ImageUsageFlagBits::eSampled ||
|
||||
info.usage == vk::ImageUsageFlagBits::eStorage ||
|
||||
info.usage == vk::ImageUsageFlagBits::eColorAttachment ||
|
||||
info.usage == vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
const bool valid_shape =
|
||||
(info.type == vk::ImageViewType::e2D && info.layer_count == 1) ||
|
||||
info.type == vk::ImageViewType::e2DArray ||
|
||||
(info.type == vk::ImageViewType::e3D && info.base_layer == 0 && info.layer_count == 1);
|
||||
if (info.format == vk::Format::eUndefined || !info.aspect || info.level_count == 0 ||
|
||||
info.base_level >= (image.mip_levels) ||
|
||||
info.level_count > image.mip_levels - info.base_level || info.layer_count == 0 ||
|
||||
info.base_layer >= image.layers || info.layer_count > image.layers - info.base_layer ||
|
||||
!supported_type || !valid_shape || !supported_usage) {
|
||||
EXIT("TextureCache: invalid dynamic image view, image=%p format=%d aspect=0x%x"
|
||||
" swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x"
|
||||
" image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(&image), static_cast<int>(info.format),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(info.aspect), info.swizzle,
|
||||
info.base_level, info.level_count, info.base_layer, info.layer_count,
|
||||
static_cast<int>(info.type), static_cast<vk::ImageUsageFlags::MaskType>(info.usage),
|
||||
image.mip_levels, image.layers);
|
||||
}
|
||||
|
||||
auto& cache = image.view_cache;
|
||||
std::lock_guard lock(cache.mutex);
|
||||
for (const auto& cached: cache.views) {
|
||||
if (cached.info == info) {
|
||||
std::lock_guard lock(views.mutex);
|
||||
for (const auto& cached: views.views) {
|
||||
if (cached.info == normalized) {
|
||||
return cached.view;
|
||||
}
|
||||
}
|
||||
|
||||
vk::ImageViewUsageCreateInfo usage {};
|
||||
usage.sType = vk::StructureType::eImageViewUsageCreateInfo;
|
||||
usage.usage = info.usage;
|
||||
usage.usage = image.usage;
|
||||
if (!is_storage) {
|
||||
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
vk::ImageViewCreateInfo create {};
|
||||
create.sType = vk::StructureType::eImageViewCreateInfo;
|
||||
create.pNext = &usage;
|
||||
create.image = image.image;
|
||||
create.viewType = info.type;
|
||||
create.format = info.format;
|
||||
create.components = info.usage == vk::ImageUsageFlagBits::eSampled
|
||||
? TextureGetComponentMapping(info.swizzle)
|
||||
: vk::ComponentMapping {};
|
||||
create.subresourceRange.aspectMask = info.aspect;
|
||||
create.subresourceRange.baseMipLevel = info.base_level;
|
||||
create.subresourceRange.levelCount = info.level_count;
|
||||
create.subresourceRange.baseArrayLayer = info.base_layer;
|
||||
create.subresourceRange.layerCount = info.layer_count;
|
||||
vk::ImageView view = nullptr;
|
||||
const auto result = m_graphics.device.createImageView(&create, nullptr, &view);
|
||||
create.viewType = normalized.type;
|
||||
create.format = normalized.format;
|
||||
create.components = normalized.mapping;
|
||||
create.subresourceRange.aspectMask = normalized.aspect;
|
||||
create.subresourceRange.baseMipLevel = normalized.base_level;
|
||||
create.subresourceRange.levelCount = normalized.level_count;
|
||||
create.subresourceRange.baseArrayLayer = normalized.base_layer;
|
||||
create.subresourceRange.layerCount = normalized.layer_count;
|
||||
|
||||
vk::ImageView view = nullptr;
|
||||
const auto result = m_graphics->device.createImageView(&create, nullptr, &view);
|
||||
if (result != vk::Result::eSuccess || view == nullptr) {
|
||||
EXIT("TextureCache: failed to create dynamic image view, result=%d format=%d"
|
||||
" aspect=0x%x swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x\n",
|
||||
static_cast<int>(result), static_cast<int>(info.format),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(info.aspect), info.swizzle,
|
||||
info.base_level, info.level_count, info.base_layer, info.layer_count,
|
||||
static_cast<int>(info.type), static_cast<vk::ImageUsageFlags::MaskType>(info.usage));
|
||||
EXIT("failed to create image view: result=%d image_format=%d view_format=%d type=%d "
|
||||
"aspect=0x%x mip=%u+%u layer=%u+%u usage=0x%x\n",
|
||||
static_cast<int>(result), static_cast<int>(image.format),
|
||||
static_cast<int>(view_info.format), static_cast<int>(view_info.type),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(view_info.aspect), view_info.base_level,
|
||||
view_info.level_count, view_info.base_layer, view_info.layer_count,
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
|
||||
}
|
||||
cache.views.push_back({info, view});
|
||||
views.views.push_back({normalized, view});
|
||||
return view;
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::GetDepthTargetSampledView(DepthStencilVulkanImage& image,
|
||||
vk::Format view_format, uint32_t swizzle,
|
||||
uint32_t base_level, uint32_t level_count,
|
||||
vk::ImageViewType type, uint32_t base_layer,
|
||||
uint32_t layer_count) {
|
||||
if (view_format == vk::Format::eUndefined ||
|
||||
!IsSupportedSampledDepthView(image.format, view_format, swizzle)) {
|
||||
EXIT("TextureCache: invalid sampled depth-target view, image=%p image_format=%d"
|
||||
" view_format=%d swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d"
|
||||
" image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(&image), static_cast<int>(image.format),
|
||||
static_cast<int>(view_format), swizzle, base_level, level_count, base_layer,
|
||||
layer_count, static_cast<int>(type), image.mip_levels, image.layers);
|
||||
}
|
||||
return GetImageView(image, {image.format, type, vk::ImageAspectFlagBits::eDepth, base_level,
|
||||
level_count, base_layer, layer_count, swizzle});
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::GetSampledColorView(VulkanImage& image, vk::Format view_format,
|
||||
uint32_t swizzle, uint32_t base_level,
|
||||
uint32_t level_count, vk::ImageViewType type,
|
||||
uint32_t base_layer, uint32_t layer_count) {
|
||||
if (view_format == vk::Format::eUndefined || base_level >= 16 ||
|
||||
(type != vk::ImageViewType::e2D && type != vk::ImageViewType::e2DArray) ||
|
||||
!IsSupportedSampledColorView(image.format, view_format, swizzle)) {
|
||||
EXIT("TextureCache: invalid sampled color view, image=%p swizzle=0x%03x"
|
||||
" view_format=%d mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(&image), swizzle, static_cast<int>(view_format), base_level,
|
||||
level_count, base_layer, layer_count, static_cast<int>(type), image.mip_levels,
|
||||
image.layers);
|
||||
}
|
||||
const auto precreated_view = type == vk::ImageViewType::e2DArray
|
||||
? VulkanImage::VIEW_DEFAULT_ARRAY
|
||||
: VulkanImage::VIEW_DEFAULT;
|
||||
const bool full_view = base_level == 0 && level_count == image.mip_levels && base_layer == 0 &&
|
||||
layer_count == (type == vk::ImageViewType::e2DArray ? image.layers : 1u);
|
||||
if (view_format == image.format && swizzle == DstSel(4, 5, 6, 7) && full_view &&
|
||||
image.image_view[precreated_view] != nullptr) {
|
||||
return image.image_view[precreated_view];
|
||||
}
|
||||
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, base_level,
|
||||
level_count, base_layer, layer_count, swizzle});
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::GetRenderTargetStorageView(RenderTextureVulkanImage& image,
|
||||
vk::Format view_format, uint32_t base_level,
|
||||
uint32_t level_count, vk::ImageViewType type,
|
||||
uint32_t base_layer, uint32_t layer_count) {
|
||||
if (view_format == vk::Format::eUndefined ||
|
||||
(type != vk::ImageViewType::e2D && type != vk::ImageViewType::e2DArray)) {
|
||||
EXIT("TextureCache: invalid render-target storage view, image=%p view_format=%d"
|
||||
" mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
|
||||
static_cast<const void*>(&image), static_cast<int>(view_format), base_level,
|
||||
level_count, base_layer, layer_count, static_cast<int>(type), image.mip_levels,
|
||||
image.layers);
|
||||
}
|
||||
const bool exact = view_format == image.format;
|
||||
const bool compatible = view_format == BgraSrgbStorageViewFormat(image.format);
|
||||
if (!exact && !compatible) {
|
||||
EXIT("TextureCache: incompatible render-target storage view, image_format=%d"
|
||||
" view_format=%d base=%u count=%u\n",
|
||||
static_cast<int>(image.format), static_cast<int>(view_format), base_level,
|
||||
level_count);
|
||||
}
|
||||
if (exact) {
|
||||
const auto index = type == vk::ImageViewType::e2DArray ? VulkanImage::VIEW_STORAGE_ARRAY
|
||||
: VulkanImage::VIEW_STORAGE;
|
||||
const bool full_view =
|
||||
base_level == 0 && level_count == 1 && base_layer == 0 &&
|
||||
layer_count == (type == vk::ImageViewType::e2DArray ? image.layers : 1u);
|
||||
if (full_view && image.image_view[index] != nullptr) {
|
||||
return image.image_view[index];
|
||||
}
|
||||
}
|
||||
|
||||
if (compatible && !ImageViewOps::FormatSupportsStorage(view_format)) {
|
||||
EXIT("TextureCache: compatible render-target storage format lacks storage support,"
|
||||
" image_format=%d view_format=%d base=%u count=%u\n",
|
||||
static_cast<int>(image.format), static_cast<int>(view_format), base_level,
|
||||
level_count);
|
||||
}
|
||||
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, base_level,
|
||||
level_count, base_layer, layer_count, DstSel(4, 5, 6, 7),
|
||||
vk::ImageUsageFlagBits::eStorage});
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::GetStorageTextureSampledView(StorageTextureVulkanImage& image,
|
||||
const ImageInfo& info) {
|
||||
const auto shape = SelectStorageSampledViewShape(info.type, info.depth, image.layers);
|
||||
if (image.image == nullptr || shape == StorageSampledViewShape::Unsupported ||
|
||||
info.base_array != 0 || info.levels != image.mip_levels || info.base_level >= info.levels ||
|
||||
info.view_levels == 0 || info.base_level + info.view_levels > info.levels) {
|
||||
EXIT("TextureCache: invalid sampled view of storage texture, image=%p type=%u depth=%u"
|
||||
" base=%u levels=%u view_levels=%u image_levels=%u base_array=%u\n",
|
||||
static_cast<const void*>(&image), info.type, info.depth, info.base_level, info.levels,
|
||||
info.view_levels, image.mip_levels, info.base_array);
|
||||
}
|
||||
const auto view_format = TextureGetFormat(info.format);
|
||||
if (view_format != image.format && !IsRgba8SrgbReinterpretation(image.format, view_format) &&
|
||||
!IsR32UintFloatReinterpretation(image.format, view_format)) {
|
||||
EXIT("TextureCache: incompatible sampled view of storage texture, image_format=%d"
|
||||
" view_format=%d swizzle=0x%03x\n",
|
||||
static_cast<int>(image.format), static_cast<int>(view_format), info.swizzle);
|
||||
}
|
||||
|
||||
vk::ImageViewType type = static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM);
|
||||
switch (shape) {
|
||||
case StorageSampledViewShape::Image2D: type = vk::ImageViewType::e2D; break;
|
||||
case StorageSampledViewShape::Image2DArray: type = vk::ImageViewType::e2DArray; break;
|
||||
case StorageSampledViewShape::Image3D: type = vk::ImageViewType::e3D; break;
|
||||
case StorageSampledViewShape::Unsupported:
|
||||
EXIT("TextureCache: unsupported sampled storage-image view shape\n");
|
||||
}
|
||||
const auto layer_count = shape == StorageSampledViewShape::Image2DArray ? info.depth : 1u;
|
||||
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, info.base_level,
|
||||
info.view_levels, 0, layer_count, info.swizzle});
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::GetStorageTextureStorageView(StorageTextureVulkanImage& image,
|
||||
uint32_t base_level) {
|
||||
if (image.image == nullptr || base_level >= (image.mip_levels)) {
|
||||
EXIT("TextureCache: invalid storage-texture mip view, image=%p level=%u levels=%u\n",
|
||||
static_cast<const void*>(&image), base_level, image.mip_levels);
|
||||
}
|
||||
if (base_level == 0) {
|
||||
return image.image_view[VulkanImage::VIEW_DEFAULT];
|
||||
}
|
||||
return GetImageView(image, {image.format, vk::ImageViewType::e2D,
|
||||
vk::ImageAspectFlagBits::eColor, base_level, 1, 0, 1,
|
||||
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eStorage});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -2,91 +2,19 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/imageInfo.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
[[nodiscard]] inline bool IsSupportedStorageSwizzle(uint32_t format, uint32_t swizzle) noexcept {
|
||||
const bool single_channel =
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm) ||
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) ||
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UInt) ||
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) ||
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16Float) ||
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
|
||||
return swizzle == DstSel(4, 5, 6, 7) ||
|
||||
(single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1) ||
|
||||
swizzle == DstSel(4, 4, 4, 4))) ||
|
||||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32UInt) &&
|
||||
swizzle == DstSel(4, 5, 0, 1)) ||
|
||||
((format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm) ||
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UInt)) &&
|
||||
(swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) ||
|
||||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float) &&
|
||||
swizzle == DstSel(5, 6, 7, 4));
|
||||
}
|
||||
namespace ImageViewOps {
|
||||
|
||||
[[nodiscard]] inline bool IsSupportedStorageDepthTile(uint32_t format, uint32_t type,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t depth) noexcept {
|
||||
return (format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) &&
|
||||
type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) && width != 0 &&
|
||||
height != 0 && depth == 1) ||
|
||||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) &&
|
||||
type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && width != 0 &&
|
||||
height != 0 && depth == 1);
|
||||
}
|
||||
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
|
||||
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
|
||||
} // namespace ImageViewOps
|
||||
|
||||
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
|
||||
vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
|
||||
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format BgraToRgbaSampledViewFormat(vk::Format image_format) noexcept {
|
||||
switch (image_format) {
|
||||
case vk::Format::eB8G8R8A8Unorm: return vk::Format::eR8G8B8A8Unorm;
|
||||
case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Srgb;
|
||||
case vk::Format::eA2R10G10B10UnormPack32: return vk::Format::eA2B10G10R10UnormPack32;
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsBgraToRgbaSampledView(vk::Format image_format,
|
||||
vk::Format view_format) noexcept {
|
||||
switch (image_format) {
|
||||
case vk::Format::eB8G8R8A8Unorm:
|
||||
case vk::Format::eB8G8R8A8Srgb:
|
||||
switch (view_format) {
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Srgb: return true;
|
||||
default: return false;
|
||||
}
|
||||
case vk::Format::eA2R10G10B10UnormPack32:
|
||||
return view_format == vk::Format::eA2B10G10R10UnormPack32;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format BgraSrgbStorageViewFormat(vk::Format image_format) noexcept {
|
||||
return image_format == vk::Format::eB8G8R8A8Srgb ? vk::Format::eR8G8B8A8Unorm
|
||||
: vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format SrgbStorageViewFormat(vk::Format image_format) noexcept {
|
||||
return image_format == vk::Format::eR8G8B8A8Srgb ? vk::Format::eR8G8B8A8Unorm
|
||||
: BgraSrgbStorageViewFormat(image_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsBgraSrgbStorageView(vk::Format image_format, vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
return view_format == BgraSrgbStorageViewFormat(image_format) && swizzle == DstSel(6, 5, 4, 7);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsValidSampledColorSwizzle(uint32_t swizzle) noexcept {
|
||||
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept {
|
||||
if ((swizzle & ~0xfffu) != 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -104,21 +32,25 @@ namespace Libs::Graphics {
|
||||
return true;
|
||||
}
|
||||
|
||||
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
|
||||
vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
|
||||
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format SrgbStorageViewFormat(vk::Format image_format) noexcept {
|
||||
switch (image_format) {
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Unorm;
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format,
|
||||
vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
if (!IsValidSampledColorSwizzle(swizzle)) {
|
||||
return false;
|
||||
}
|
||||
if (image_format == view_format || IsRgba8SrgbReinterpretation(image_format, view_format)) {
|
||||
return true;
|
||||
}
|
||||
if ((IsRgba16UintFloatReinterpretation(image_format, view_format) ||
|
||||
IsRgba8UnormUintReinterpretation(image_format, view_format)) &&
|
||||
swizzle == DstSel(4, 5, 6, 7)) {
|
||||
return true;
|
||||
}
|
||||
return IsBgraToRgbaSampledView(image_format, view_format) && swizzle == DstSel(6, 5, 4, 7);
|
||||
return IsValidImageSwizzle(swizzle) &&
|
||||
ImageViewOps::FormatsCompatible(image_format, view_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t
|
||||
@@ -169,33 +101,24 @@ IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& r
|
||||
!resource.written && !resource.atomic && !resource.depth_compare;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline int SelectStorageColorView(vk::Format image_format, vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
const bool single_channel =
|
||||
view_format == vk::Format::eR8Unorm || view_format == vk::Format::eR8Uint ||
|
||||
view_format == vk::Format::eR16Uint || view_format == vk::Format::eR32Uint ||
|
||||
view_format == vk::Format::eR16Sfloat || view_format == vk::Format::eR32Sfloat;
|
||||
const bool swizzle_ok =
|
||||
swizzle == DstSel(4, 5, 6, 7) ||
|
||||
(single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1) ||
|
||||
swizzle == DstSel(4, 4, 4, 4))) ||
|
||||
(view_format == vk::Format::eR32G32Uint && swizzle == DstSel(4, 5, 0, 1)) ||
|
||||
((view_format == vk::Format::eR8G8B8A8Unorm || view_format == vk::Format::eR8G8B8A8Uint) &&
|
||||
(swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) ||
|
||||
(view_format == vk::Format::eR32G32B32A32Sfloat && swizzle == DstSel(5, 6, 7, 4));
|
||||
if ((image_format != view_format &&
|
||||
!IsBgraSrgbStorageView(image_format, view_format, swizzle)) ||
|
||||
!swizzle_ok) {
|
||||
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
const auto srgb_view = SrgbStorageViewFormat(image_format);
|
||||
const bool srgb_storage_view =
|
||||
srgb_view != vk::Format::eUndefined && view_format == srgb_view;
|
||||
if ((image_format != view_format && !srgb_storage_view) ||
|
||||
!IsValidImageSwizzle(swizzle)) {
|
||||
UnsupportedColorView("storage", image_format, view_format, swizzle);
|
||||
}
|
||||
return VulkanImage::VIEW_STORAGE;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
|
||||
@@ -213,18 +136,6 @@ ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource
|
||||
}
|
||||
}
|
||||
|
||||
namespace ImageViewOps {
|
||||
|
||||
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
|
||||
[[nodiscard]] bool FormatSupportsStorage(vk::Format format);
|
||||
|
||||
void CreateRenderTargetViews(RenderTextureVulkanImage& image);
|
||||
void CreateDepthViews(DepthStencilVulkanImage& image);
|
||||
void CreateVideoOutViews(VideoOutVulkanImage& image);
|
||||
void DestroyViews(VulkanImage& image);
|
||||
|
||||
} // namespace ImageViewOps
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "graphics/host_gpu/renderer/masterSemaphore.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
MasterSemaphore::MasterSemaphore(GraphicContext& graphics): m_graphics(graphics) {
|
||||
vk::SemaphoreTypeCreateInfo type_info {};
|
||||
type_info.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
|
||||
type_info.semaphoreType = vk::SemaphoreType::eTimeline;
|
||||
type_info.initialValue = 0;
|
||||
|
||||
vk::SemaphoreCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eSemaphoreCreateInfo;
|
||||
create_info.pNext = &type_info;
|
||||
|
||||
const auto result = m_graphics.device.createSemaphore(&create_info, nullptr, &m_semaphore);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_semaphore == nullptr);
|
||||
}
|
||||
|
||||
MasterSemaphore::~MasterSemaphore() {
|
||||
if (m_semaphore != nullptr) {
|
||||
m_graphics.device.destroySemaphore(m_semaphore, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void MasterSemaphore::Refresh() {
|
||||
uint64_t counter = 0;
|
||||
const auto result = m_graphics.device.getSemaphoreCounterValue(m_semaphore, &counter);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
auto known = m_gpu_tick.load(std::memory_order_acquire);
|
||||
while (known < counter &&
|
||||
!m_gpu_tick.compare_exchange_weak(known, counter, std::memory_order_release,
|
||||
std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
|
||||
void MasterSemaphore::Wait(uint64_t tick) {
|
||||
if (IsFree(tick)) {
|
||||
return;
|
||||
}
|
||||
Refresh();
|
||||
if (IsFree(tick)) {
|
||||
return;
|
||||
}
|
||||
|
||||
vk::SemaphoreWaitInfo wait_info {};
|
||||
wait_info.sType = vk::StructureType::eSemaphoreWaitInfo;
|
||||
wait_info.semaphoreCount = 1;
|
||||
wait_info.pSemaphores = &m_semaphore;
|
||||
wait_info.pValues = &tick;
|
||||
|
||||
const auto result = m_graphics.device.waitSemaphores(&wait_info, UINT64_MAX);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
|
||||
class MasterSemaphore {
|
||||
public:
|
||||
explicit MasterSemaphore(GraphicContext& graphics);
|
||||
~MasterSemaphore();
|
||||
KYTY_CLASS_NO_COPY(MasterSemaphore);
|
||||
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept {
|
||||
return m_current_tick.load(std::memory_order_acquire);
|
||||
}
|
||||
[[nodiscard]] uint64_t KnownGpuTick() const noexcept {
|
||||
return m_gpu_tick.load(std::memory_order_acquire);
|
||||
}
|
||||
[[nodiscard]] bool IsFree(uint64_t tick) const noexcept { return KnownGpuTick() >= tick; }
|
||||
[[nodiscard]] uint64_t NextTick() noexcept {
|
||||
return m_current_tick.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
[[nodiscard]] vk::Semaphore Handle() const noexcept { return m_semaphore; }
|
||||
|
||||
void Refresh();
|
||||
void Wait(uint64_t tick);
|
||||
|
||||
private:
|
||||
GraphicContext& m_graphics;
|
||||
vk::Semaphore m_semaphore = nullptr;
|
||||
std::atomic<uint64_t> m_gpu_tick {0};
|
||||
std::atomic<uint64_t> m_current_tick {1};
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
|
||||
@@ -1,13 +1,14 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
|
||||
[[nodiscard]] Entry& GetOrCreate(size_t page) {
|
||||
if (!IsValidPage(page)) {
|
||||
throw std::out_of_range("MultiLevelPageTable page is outside the guest address space");
|
||||
EXIT("MultiLevelPageTable page is outside the guest address space");
|
||||
}
|
||||
auto& bucket = m_first_level[FirstLevelIndex(page)];
|
||||
if (bucket == nullptr) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
@@ -36,13 +36,25 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
|
||||
|
||||
} // namespace
|
||||
|
||||
PipelineCache::~PipelineCache() {
|
||||
auto destroy = [this](const auto& pipelines) {
|
||||
for (const auto& [key, pipeline]: pipelines) {
|
||||
(void)key;
|
||||
m_graphics.device.destroyPipeline(pipeline->pipeline, nullptr);
|
||||
m_graphics.device.destroyPipelineLayout(pipeline->pipeline_layout, nullptr);
|
||||
}
|
||||
};
|
||||
destroy(m_graphics_pipelines);
|
||||
destroy(m_compute_pipelines);
|
||||
}
|
||||
|
||||
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
|
||||
return std::memcmp(this, &other, sizeof(*this)) == 0;
|
||||
}
|
||||
|
||||
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
VulkanFramebuffer& framebuffer, RenderColorInfo* colors, uint32_t color_count,
|
||||
RenderDepthInfo& depth, ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
|
||||
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv) {
|
||||
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
|
||||
@@ -61,10 +73,10 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
const HW::BlendColor& bclr = ctx.GetBlendColor();
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
color_mask[i] = (colors[i].vulkan_buffer != nullptr
|
||||
? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
|
||||
ctx.GetRenderTargetMask(), colors[i].target_slot))
|
||||
: 0);
|
||||
color_mask[i] =
|
||||
(colors[i].image_id ? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
|
||||
ctx.GetRenderTargetMask(), colors[i].target_slot))
|
||||
: 0);
|
||||
}
|
||||
const HW::ModeControl& mc = ctx.GetModeControl();
|
||||
|
||||
@@ -76,11 +88,40 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
|
||||
PipelineStaticParameters static_params {};
|
||||
GraphicsPipeline p {};
|
||||
p.render_pass_id = framebuffer.render_pass_id;
|
||||
p.ps_shader_id = ps_id;
|
||||
p.vs_shader_id = vs_id;
|
||||
|
||||
static_params.color_count = color_count;
|
||||
PipelineRenderingState rendering {};
|
||||
rendering.color_count = color_count;
|
||||
uint32_t attachment_samples = 0;
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
EXIT_IF(!colors[i].image_id || colors[i].format == vk::Format::eUndefined);
|
||||
rendering.color_formats[i] = colors[i].format;
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = colors[i].samples;
|
||||
} else if (attachment_samples != colors[i].samples) {
|
||||
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, colors[i].samples);
|
||||
}
|
||||
}
|
||||
const bool with_depth =
|
||||
depth.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
|
||||
if (with_depth) {
|
||||
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
|
||||
rendering.depth_format =
|
||||
aspects & vk::ImageAspectFlagBits::eDepth ? depth.format : vk::Format::eUndefined;
|
||||
rendering.stencil_format =
|
||||
aspects & vk::ImageAspectFlagBits::eStencil ? depth.format : vk::Format::eUndefined;
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.samples;
|
||||
} else if (attachment_samples != depth.samples) {
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, depth.samples);
|
||||
}
|
||||
}
|
||||
EXIT_IF(attachment_samples == 0 ||
|
||||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {});
|
||||
|
||||
if (ps_active && depth.depth_test_enable && ps_input_info->ps_execute_on_noop) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
@@ -94,14 +135,13 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
static_params.negative_one_to_one = !clip_control.dx_clip_space;
|
||||
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
|
||||
static_params.topology = topology;
|
||||
static_params.samples = framebuffer.samples;
|
||||
static_params.samples = attachment_samples;
|
||||
static_params.sample_shading_enable =
|
||||
ps_active && framebuffer.samples > 1 && ps_input_info->ps_sample_shading;
|
||||
ps_active && attachment_samples > 1 && ps_input_info->ps_sample_shading;
|
||||
if (static_params.sample_shading_enable && !m_graphics.sample_rate_shading_enabled) {
|
||||
EXIT("Pipeline: sample-rate shading is required but unsupported by the host\n");
|
||||
}
|
||||
static_params.with_depth =
|
||||
(depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
|
||||
static_params.with_depth = with_depth;
|
||||
static_params.depth_test_enable = depth.depth_test_enable;
|
||||
static_params.depth_write_enable = (depth.depth_write_enable && !depth.depth_clear_enable);
|
||||
static_params.depth_compare_op = depth.depth_compare_op;
|
||||
@@ -139,7 +179,7 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
NormalizeStaticParamsForDynamicState(static_params);
|
||||
|
||||
GraphicsPipelineKey key {};
|
||||
key.render_pass_id = p.render_pass_id;
|
||||
key.rendering = rendering;
|
||||
key.vs_shader_id = p.vs_shader_id;
|
||||
key.ps_shader_id = p.ps_shader_id;
|
||||
key.static_params = static_params;
|
||||
@@ -162,7 +202,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
auto cached = std::make_unique<GraphicsPipeline>(p);
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32);
|
||||
CreatePipelineInternal(*cached, framebuffer.render_pass, vs_input_info, vs_spirv, ps_input_info,
|
||||
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
|
||||
vs_spirv, ps_input_info,
|
||||
ps_spirv, static_params, vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32, ps_active);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
@@ -204,7 +245,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<ComputePipeline>(p);
|
||||
CreatePipelineInternal(*cached, input_info, cs_spirv);
|
||||
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, input_info, cs_spirv);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace Libs::Graphics {
|
||||
struct GraphicContext;
|
||||
struct RenderColorInfo;
|
||||
struct RenderDepthInfo;
|
||||
struct VulkanFramebuffer;
|
||||
class RenderCommandBuffer;
|
||||
class DescriptorCache;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
@@ -86,12 +86,22 @@ static_assert(sizeof(PipelineStaticParameters) ==
|
||||
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
|
||||
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
|
||||
|
||||
struct PipelineRenderingState {
|
||||
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
|
||||
vk::Format depth_format = vk::Format::eUndefined;
|
||||
vk::Format stencil_format = vk::Format::eUndefined;
|
||||
uint32_t color_count = 0;
|
||||
|
||||
bool operator==(const PipelineRenderingState&) const = default;
|
||||
};
|
||||
|
||||
class PipelineCache {
|
||||
public:
|
||||
explicit PipelineCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
PipelineCache(GraphicContext& graphics, DescriptorCache& descriptor_cache)
|
||||
: m_graphics(graphics), m_descriptor_cache(descriptor_cache) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
~PipelineCache() { KYTY_NOT_IMPLEMENTED; }
|
||||
~PipelineCache();
|
||||
KYTY_CLASS_NO_COPY(PipelineCache);
|
||||
|
||||
struct Pipeline {
|
||||
@@ -100,7 +110,6 @@ public:
|
||||
};
|
||||
|
||||
struct GraphicsPipeline: Pipeline {
|
||||
uint64_t render_pass_id = 0;
|
||||
ShaderId vs_shader_id;
|
||||
ShaderId ps_shader_id;
|
||||
};
|
||||
@@ -110,8 +119,8 @@ public:
|
||||
};
|
||||
|
||||
GraphicsPipeline& CreateGraphicsPipeline(
|
||||
VulkanFramebuffer& framebuffer, RenderColorInfo* colors, uint32_t color_count,
|
||||
RenderDepthInfo& depth, ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
|
||||
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
|
||||
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
@@ -120,13 +129,13 @@ public:
|
||||
|
||||
private:
|
||||
struct GraphicsPipelineKey {
|
||||
uint64_t render_pass_id = 0;
|
||||
PipelineRenderingState rendering;
|
||||
ShaderId vs_shader_id;
|
||||
ShaderId ps_shader_id;
|
||||
PipelineStaticParameters static_params;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& other) const {
|
||||
return render_pass_id == other.render_pass_id && vs_shader_id == other.vs_shader_id &&
|
||||
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
|
||||
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
|
||||
}
|
||||
};
|
||||
@@ -160,12 +169,21 @@ private:
|
||||
Mix(hash, bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void MixRendering(std::size_t& hash, const PipelineRenderingState& rendering) {
|
||||
Mix(hash, rendering.color_count);
|
||||
for (uint32_t i = 0; i < rendering.color_count; i++) {
|
||||
Mix(hash, static_cast<uint32_t>(rendering.color_formats[i]));
|
||||
}
|
||||
Mix(hash, static_cast<uint32_t>(rendering.depth_format));
|
||||
Mix(hash, static_cast<uint32_t>(rendering.stencil_format));
|
||||
}
|
||||
};
|
||||
|
||||
struct GraphicsPipelineKeyHash {
|
||||
std::size_t operator()(const GraphicsPipelineKey& key) const {
|
||||
std::size_t hash = 0;
|
||||
PipelineKeyHash::Mix(hash, key.render_pass_id);
|
||||
PipelineKeyHash::MixRendering(hash, key.rendering);
|
||||
PipelineKeyHash::MixShaderId(hash, key.vs_shader_id);
|
||||
PipelineKeyHash::MixShaderId(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::MixStaticParams(hash, key.static_params);
|
||||
@@ -182,6 +200,7 @@ private:
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
DescriptorCache& m_descriptor_cache;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
|
||||
GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
@@ -192,7 +211,9 @@ private:
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
|
||||
uint32_t ps_crc32);
|
||||
void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::RenderPass render_pass,
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
@@ -200,7 +221,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
|
||||
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
|
||||
bool ps_active);
|
||||
void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
std::span<const uint32_t> cs_shader);
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRENDER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/renderer/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -15,29 +18,23 @@ namespace HW {
|
||||
class Context;
|
||||
class UserConfig;
|
||||
class Shader;
|
||||
struct DepthRenderTarget;
|
||||
} // namespace HW
|
||||
|
||||
struct GraphicContext;
|
||||
struct ShaderBufferResource;
|
||||
struct ShaderComputeInputInfo;
|
||||
struct VideoOutVulkanImage;
|
||||
struct DepthStencilVulkanImage;
|
||||
struct TextureVulkanImage;
|
||||
struct StorageTextureVulkanImage;
|
||||
struct RenderTextureVulkanImage;
|
||||
struct CommandSlot;
|
||||
struct VulkanBuffer;
|
||||
struct VulkanDescriptorSet;
|
||||
struct VulkanFramebuffer;
|
||||
struct RenderDepthInfo;
|
||||
struct RenderColorInfo;
|
||||
class BufferCache;
|
||||
|
||||
struct HtileClearTarget {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct DrawCallInfo;
|
||||
struct DrawEmitInfo;
|
||||
struct DrawIndexBufferSource;
|
||||
struct DrawRenderState;
|
||||
class RenderContext;
|
||||
class CommandScheduler;
|
||||
struct RenderExecutorTestAccess;
|
||||
|
||||
enum class CommandBufferDebugOp : uint32_t {
|
||||
DispatchDirect,
|
||||
@@ -66,10 +63,36 @@ private:
|
||||
std::vector<std::shared_ptr<void>> m_resources;
|
||||
};
|
||||
|
||||
struct SubmitInfo {
|
||||
static constexpr uint32_t MaxSemaphores = 3;
|
||||
|
||||
std::array<vk::Semaphore, MaxSemaphores> wait_semaphores {};
|
||||
std::array<uint64_t, MaxSemaphores> wait_ticks {};
|
||||
std::array<vk::PipelineStageFlags, MaxSemaphores> wait_stages {};
|
||||
std::array<vk::Semaphore, MaxSemaphores> signal_semaphores {};
|
||||
std::array<uint64_t, MaxSemaphores> signal_ticks {};
|
||||
uint32_t num_wait_semaphores = 0;
|
||||
uint32_t num_signal_semaphores = 0;
|
||||
|
||||
void AddWait(vk::Semaphore semaphore, uint64_t tick = 1,
|
||||
vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eAllCommands) {
|
||||
EXIT_IF(semaphore == nullptr || num_wait_semaphores >= MaxSemaphores);
|
||||
wait_semaphores[num_wait_semaphores] = semaphore;
|
||||
wait_ticks[num_wait_semaphores] = tick;
|
||||
wait_stages[num_wait_semaphores++] = stage;
|
||||
}
|
||||
|
||||
void AddSignal(vk::Semaphore semaphore, uint64_t tick = 1) {
|
||||
EXIT_IF(semaphore == nullptr || num_signal_semaphores >= MaxSemaphores);
|
||||
signal_semaphores[num_signal_semaphores] = semaphore;
|
||||
signal_ticks[num_signal_semaphores++] = tick;
|
||||
}
|
||||
};
|
||||
|
||||
class CommandBuffer {
|
||||
public:
|
||||
CommandBuffer();
|
||||
~CommandBuffer() { Release(); }
|
||||
explicit CommandBuffer(CommandScheduler& scheduler);
|
||||
~CommandBuffer();
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandBuffer);
|
||||
|
||||
@@ -77,95 +100,144 @@ public:
|
||||
|
||||
void Begin() const;
|
||||
void End() const;
|
||||
void Execute();
|
||||
void ExecuteWithSemaphore(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
|
||||
vk::Semaphore signal_semaphore);
|
||||
void Execute(const SubmitInfo& submit = {});
|
||||
void SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0 = 0, uint32_t arg1 = 0,
|
||||
uint32_t arg2 = 0, uint32_t arg3 = 0, uint64_t arg4 = 0);
|
||||
void BeginRenderPass(VulkanFramebuffer& framebuffer, RenderColorInfo* colors,
|
||||
uint32_t color_count, RenderDepthInfo& depth) const;
|
||||
void EndRenderPass() const;
|
||||
void BeginRendering(const RenderState& state) const;
|
||||
void EndRendering() const;
|
||||
void WaitForFenceOnly();
|
||||
void WaitForFence();
|
||||
void WaitForFenceAndReset();
|
||||
void DeleteAfterFence(VulkanBuffer& buffer);
|
||||
void RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer);
|
||||
void RetainResourceUntilFence(std::shared_ptr<void> resource);
|
||||
void RecycleDescriptorAfterFence(VulkanDescriptorSet& set);
|
||||
|
||||
[[nodiscard]] vk::CommandBuffer Handle() const;
|
||||
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
|
||||
[[nodiscard]] RenderContext& GetContext() const noexcept { return m_context; }
|
||||
[[nodiscard]] bool IsExecute() const { return m_execute; }
|
||||
[[nodiscard]] uint64_t GetRecordingGeneration() const { return m_recording_generation; }
|
||||
|
||||
private:
|
||||
friend class BufferCache;
|
||||
|
||||
void Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
|
||||
vk::Semaphore signal_semaphore);
|
||||
void Release();
|
||||
void FinalizeFence(bool reset_recording);
|
||||
void ReleaseResourcesAfterFence();
|
||||
void DeleteBuffersAfterFence();
|
||||
void RecycleDescriptorsAfterFence();
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandSlot* m_slot = nullptr;
|
||||
bool m_execute = false;
|
||||
bool m_fence_waited = false;
|
||||
uint64_t m_submit_seq = 0;
|
||||
uint64_t m_recording_generation = 0;
|
||||
uint32_t m_debug_op = 0;
|
||||
uint64_t m_debug_submit_id = 0;
|
||||
uint32_t m_debug_arg0 = 0;
|
||||
uint32_t m_debug_arg1 = 0;
|
||||
uint32_t m_debug_arg2 = 0;
|
||||
uint32_t m_debug_arg3 = 0;
|
||||
uint64_t m_debug_arg4 = 0;
|
||||
std::vector<VulkanBuffer*> m_delete_after_fence;
|
||||
FenceResourceRetainer m_fence_resources;
|
||||
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
|
||||
HostStreamBuffer m_host_stream;
|
||||
RenderContext& m_context;
|
||||
CommandScheduler& m_scheduler;
|
||||
GraphicContext& m_graphics;
|
||||
CommandSlot* m_slot = nullptr;
|
||||
bool m_execute = false;
|
||||
bool m_fence_waited = false;
|
||||
uint64_t m_submit_seq = 0;
|
||||
uint32_t m_debug_op = 0;
|
||||
uint64_t m_debug_submit_id = 0;
|
||||
uint32_t m_debug_arg0 = 0;
|
||||
uint32_t m_debug_arg1 = 0;
|
||||
uint32_t m_debug_arg2 = 0;
|
||||
uint32_t m_debug_arg3 = 0;
|
||||
uint64_t m_debug_arg4 = 0;
|
||||
std::vector<std::unique_ptr<VulkanBuffer>> m_retired_buffers;
|
||||
FenceResourceRetainer m_fence_resources;
|
||||
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
|
||||
mutable RenderState m_render_state;
|
||||
mutable bool m_rendering = false;
|
||||
};
|
||||
|
||||
class RenderCommandBuffer final: public CommandBuffer {
|
||||
public:
|
||||
RenderCommandBuffer(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders)
|
||||
: m_registers(registers), m_user_config(user_config), m_shaders(shaders) {}
|
||||
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
|
||||
|
||||
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return m_registers; }
|
||||
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return m_user_config; }
|
||||
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return m_shaders; }
|
||||
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
|
||||
m_registers = ®isters;
|
||||
m_user_config = &user_config;
|
||||
m_shaders = &shaders;
|
||||
}
|
||||
|
||||
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
|
||||
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
|
||||
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
|
||||
|
||||
private:
|
||||
HW::Context& m_registers;
|
||||
HW::UserConfig& m_user_config;
|
||||
HW::Shader& m_shaders;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
};
|
||||
|
||||
void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
|
||||
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
|
||||
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
|
||||
void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
|
||||
uint32_t flags, uint32_t render_target_slice_offset = 0,
|
||||
uint32_t instance_count = 1, uint32_t first_vertex = 0,
|
||||
uint32_t first_instance = 0);
|
||||
void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
class RenderExecutor {
|
||||
public:
|
||||
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
||||
KYTY_CLASS_NO_COPY(RenderExecutor);
|
||||
|
||||
void GraphicsRenderInit(GraphicContext& graphics);
|
||||
void GraphicsRenderReleaseThreadCommandPool();
|
||||
void DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
|
||||
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
|
||||
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
|
||||
void DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
|
||||
uint32_t flags, uint32_t render_target_slice_offset = 0,
|
||||
uint32_t instance_count = 1, uint32_t first_vertex = 0,
|
||||
uint32_t first_instance = 0);
|
||||
void DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
|
||||
[[nodiscard]] DescriptorCache::PreparedBindings
|
||||
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
|
||||
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
|
||||
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
|
||||
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
|
||||
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
|
||||
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
|
||||
|
||||
private:
|
||||
struct GraphicsBindings {
|
||||
DescriptorCache::PreparedBindings vertex;
|
||||
std::optional<DescriptorCache::PreparedBindings> pixel;
|
||||
};
|
||||
|
||||
[[nodiscard]] DescriptorCache::TextureBinding
|
||||
ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(CommandBuffer& buffer,
|
||||
const ShaderStageRuntime& vertex,
|
||||
const ShaderStageRuntime& pixel,
|
||||
bool pixel_active);
|
||||
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
|
||||
uint32_t render_target_slot = UINT32_MAX,
|
||||
bool ignore_target_mask = false, bool exact_format = false);
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderDepthInfo& target);
|
||||
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
bool log_setup_phases, DrawRenderState& state);
|
||||
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source, bool log_pipeline_phase,
|
||||
bool set_bind_debug, bool set_auto_debug);
|
||||
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
||||
uint32_t color_count, RenderDepthInfo& depth);
|
||||
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset);
|
||||
void BindImage(ImageId id, bool storage);
|
||||
void BindRenderTarget(ImageId id);
|
||||
void TrackImageBinding(ImageId id);
|
||||
void ResetBindings();
|
||||
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const RenderCommandBuffer& buffer);
|
||||
|
||||
RenderContext& m_context;
|
||||
std::vector<std::shared_ptr<Image>> m_bound_images;
|
||||
|
||||
friend struct RenderExecutorTestAccess;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode,
|
||||
ShaderBufferResource& descriptor,
|
||||
uint32_t& packed_clear, uint64_t& size);
|
||||
[[nodiscard]] bool ResolveHtileClearTarget(const HW::DepthRenderTarget& target,
|
||||
uint64_t descriptor_size, HtileClearTarget& resolved);
|
||||
|
||||
void GraphicsRenderMemoryBarrier(CommandBuffer& buffer);
|
||||
void GraphicsRenderTextureBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
void GraphicsRenderDepthStencilBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -9,20 +9,15 @@
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/label.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/descriptors.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
@@ -50,217 +45,35 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
|
||||
return stride == 0 ? records : records * stride;
|
||||
}
|
||||
|
||||
bool ResolveHtileClearTarget(const HW::DepthRenderTarget& z, uint64_t descriptor_size,
|
||||
HtileClearTarget& resolved) {
|
||||
resolved = {};
|
||||
const bool has_stencil =
|
||||
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
|
||||
const auto* depth_policy = FindDepthFormatPolicy(z.z_info.format);
|
||||
const bool supported_depth_state =
|
||||
z.z_info.tile_surface_enable && depth_policy != nullptr && z.z_info.tile_mode_index == 0 &&
|
||||
z.z_info.num_samples <= 3 && z.z_info.zrange_precision <= 1 && !z.z_info.expclear_enabled &&
|
||||
!z.z_info.embedded_sample_locations && !z.z_info.partially_resident &&
|
||||
z.z_info.num_mip_levels == 0 && z.z_info.plane_compression == 0 &&
|
||||
z.depth_view.current_mip_level == 0 && z.depth_view.slice_start == 0 &&
|
||||
z.depth_view.slice_max == 0 && z.depth_info.addr5_swizzle_mask == 0 &&
|
||||
z.depth_info.array_mode == 0 && z.depth_info.pipe_config == 0 &&
|
||||
z.depth_info.bank_width == 0 && z.depth_info.bank_height == 0 &&
|
||||
z.depth_info.macro_tile_aspect == 0 && z.depth_info.num_banks == 0;
|
||||
const bool supported_stencil_state =
|
||||
z.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
|
||||
!z.stencil_info.expclear_enabled &&
|
||||
depth_htile_stencil_acceleration_compatible(has_stencil, true,
|
||||
z.stencil_info.tile_stencil_disable) &&
|
||||
!z.stencil_info.texture_compatible_stencil && !z.stencil_info.partially_resident &&
|
||||
(!has_stencil ||
|
||||
(z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) &&
|
||||
!z.depth_view.stencil_write_disable));
|
||||
const bool supported_htile_state =
|
||||
z.htile_surface.linear == 0 && z.htile_surface.full_cache == 0 &&
|
||||
z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.preload == 0 &&
|
||||
z.htile_surface.prefetch_width == 0 && z.htile_surface.prefetch_height == 0 &&
|
||||
z.htile_surface.dst_outside_zero_to_one == 0;
|
||||
const bool supported_addresses =
|
||||
z.z_read_base_addr != 0 && z.z_write_base_addr == z.z_read_base_addr &&
|
||||
(z.z_read_base_addr & 0xffffu) == 0 &&
|
||||
(has_stencil ? z.stencil_read_base_addr != 0 &&
|
||||
z.stencil_write_base_addr == z.stencil_read_base_addr &&
|
||||
(z.stencil_read_base_addr & 0xffffu) == 0
|
||||
: z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0) &&
|
||||
z.htile_data_base_addr != 0 && (z.htile_data_base_addr & 0x7fffu) == 0 &&
|
||||
descriptor_size != 0 && (descriptor_size & 0x7fffu) == 0 &&
|
||||
z.htile_data_base_addr < TRACKER_ADDRESS_SIZE &&
|
||||
descriptor_size <= TRACKER_ADDRESS_SIZE - z.htile_data_base_addr;
|
||||
if (!supported_depth_state || !supported_stencil_state || !supported_htile_state ||
|
||||
!supported_addresses) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool size_xy_valid = z.size.valid;
|
||||
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
|
||||
if (!size_xy_valid && !wh_valid) {
|
||||
// Prospero emits an exact full-surface metadata descriptor even
|
||||
// when the compute context omits depth extent registers. Admit only that wholly absent
|
||||
// layout state; partially programmed layouts remain unsupported.
|
||||
const bool descriptor_backed_state =
|
||||
!z.size.valid && z.size.x_max == 0 && z.size.y_max == 0 && !z.width_height_valid &&
|
||||
z.width == 0 && z.height == 0 && !z.pitch_height_valid && z.pitch_div8_minus1 == 0 &&
|
||||
z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0;
|
||||
if (!descriptor_backed_state) {
|
||||
return false;
|
||||
}
|
||||
resolved = {.address = z.htile_data_base_addr, .size = descriptor_size};
|
||||
return true;
|
||||
}
|
||||
const uint32_t width = size_xy_valid ? static_cast<uint32_t>(z.size.x_max) + 1u : z.width;
|
||||
const uint32_t height = size_xy_valid ? static_cast<uint32_t>(z.size.y_max) + 1u : z.height;
|
||||
if (width > 16384 || height > 16384 ||
|
||||
(size_xy_valid && wh_valid && (width != z.width || height != z.height)) ||
|
||||
(!z.pitch_height_valid &&
|
||||
(z.pitch_div8_minus1 != 0 || z.height_div8_minus1 != 0 || z.slice_div64_minus1 != 0))) {
|
||||
return false;
|
||||
}
|
||||
TileSizeAlign htile_size {};
|
||||
if (!TileGetHtileSize(width, height, htile_size) || htile_size.size != descriptor_size) {
|
||||
return false;
|
||||
}
|
||||
resolved = {.address = z.htile_data_base_addr, .size = htile_size.size};
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ValidateFullHtileClearDispatch(const ShaderComputeInputInfo& input,
|
||||
const ShaderBufferResource& metadata, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
const bool thread_dimensions = input.dispatch_thread_dimensions;
|
||||
const bool supported_shape =
|
||||
input.threads_num[0] == 64 && input.threads_num[1] == 1 && input.threads_num[2] == 1 &&
|
||||
group_x != 0 && group_y == 1 && group_z == 1 && input.group_id[0] && !input.group_id[1] &&
|
||||
!input.group_id[2] && input.thread_ids_num == 1 && input.wave_size == 32 &&
|
||||
!input.tg_size_en && mode == (thread_dimensions ? 0x61u : 0x41u);
|
||||
const bool dimensions_match =
|
||||
thread_dimensions
|
||||
? input.dispatch_threads_num[0] == group_x && input.dispatch_threads_num[1] == 1 &&
|
||||
input.dispatch_threads_num[2] == 1 && group_x % input.threads_num[0] == 0
|
||||
: input.dispatch_threads_num[0] == 0 && input.dispatch_threads_num[1] == 0 &&
|
||||
input.dispatch_threads_num[2] == 0;
|
||||
const uint64_t launched_threads =
|
||||
thread_dimensions ? group_x : static_cast<uint64_t>(group_x) * input.threads_num[0];
|
||||
if (!supported_shape || !dimensions_match || metadata.Stride() == 0 ||
|
||||
launched_threads != metadata.NumRecords()) {
|
||||
EXIT("HTile compute clear does not cover the complete metadata surface\n");
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const RenderCommandBuffer& buffer, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const RenderCommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
EXIT("compute runtime buffer count does not match shader metadata\n");
|
||||
}
|
||||
const auto& z = ctx.GetDepthRenderTarget();
|
||||
const uint64_t meta_addr = z.htile_data_base_addr;
|
||||
auto& cache = GetRenderContext().GetTextureCache();
|
||||
uint32_t current_references = 0;
|
||||
uint32_t registered_writes = 0;
|
||||
uint64_t described_meta_size = 0;
|
||||
HtileClearTarget registered_target {};
|
||||
TextureCache::MetaRangeInfo registered_meta {};
|
||||
auto& cache = buffer.GetContext().GetTextureCache();
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& resource = program.info.buffers[i];
|
||||
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
|
||||
const auto descriptor_size = BufferDescriptorSize(descriptor);
|
||||
if (meta_addr != 0 && descriptor.Base48() == meta_addr) {
|
||||
current_references++;
|
||||
described_meta_size = descriptor_size;
|
||||
}
|
||||
// An exact registered metadata range remains
|
||||
// identifiable even when it is no longer the currently bound depth target.
|
||||
TextureCache::MetaRangeInfo resolved_meta {};
|
||||
if (resource.written &&
|
||||
cache.ResolveMetaRange(descriptor.Base48(), descriptor_size, resolved_meta)) {
|
||||
registered_writes++;
|
||||
registered_target = {.address = descriptor.Base48(), .size = descriptor_size};
|
||||
registered_meta = resolved_meta;
|
||||
if (!resource.written && cache.IsMeta(descriptor.Base48())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (current_references == 0 && registered_writes == 0) {
|
||||
return false;
|
||||
}
|
||||
if (current_references > 1 || (current_references == 0 && registered_writes > 1)) {
|
||||
EXIT("HTile clear has ambiguous metadata descriptors: current=%u registered=%u\n",
|
||||
current_references, registered_writes);
|
||||
}
|
||||
HtileClearTarget target {};
|
||||
if (current_references != 0) {
|
||||
if (!ResolveHtileClearTarget(z, described_meta_size, target)) {
|
||||
EXIT("unsupported HTile compute-clear target state: current=%u registered=%u "
|
||||
"meta=0x%016" PRIx64 "+0x%016" PRIx64 " depth=0x%016" PRIx64 "/0x%016" PRIx64
|
||||
" stencil=0x%016" PRIx64 "/0x%016" PRIx64
|
||||
" extent=%d:%ux%u wh=%d:%ux%u pitch=%d:%u/%u/%u zfmt=%u sfmt=%u samples=%u\n",
|
||||
current_references, registered_writes, meta_addr, described_meta_size,
|
||||
z.z_read_base_addr, z.z_write_base_addr, z.stencil_read_base_addr,
|
||||
z.stencil_write_base_addr, z.size.valid, static_cast<uint32_t>(z.size.x_max) + 1u,
|
||||
static_cast<uint32_t>(z.size.y_max) + 1u, z.width_height_valid, z.width, z.height,
|
||||
z.pitch_height_valid, z.pitch_div8_minus1, z.height_div8_minus1,
|
||||
z.slice_div64_minus1, z.z_info.format, z.stencil_info.format,
|
||||
z.z_info.num_samples);
|
||||
}
|
||||
cache.RegisterMeta(target.address, target.size);
|
||||
if (!cache.ResolveMetaRange(target.address, target.size, registered_meta)) {
|
||||
EXIT("failed to resolve registered HTile compute-clear range\n");
|
||||
}
|
||||
} else {
|
||||
target = registered_target;
|
||||
}
|
||||
GetRenderContext().GetBufferCache().ValidateGpuAccess(target.address, target.size, false, true);
|
||||
|
||||
uint32_t metadata_writes = 0;
|
||||
ShaderBufferResource metadata_descriptor {};
|
||||
if (!program.info.images.empty() || !program.info.samplers.empty() ||
|
||||
!program.info.addresses.empty()) {
|
||||
EXIT("HTile clear with non-buffer resources is unsupported: images=%zu samplers=%zu "
|
||||
"addresses=%zu\n",
|
||||
program.info.images.size(), program.info.samplers.size(),
|
||||
program.info.addresses.size());
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& resource = program.info.buffers[i];
|
||||
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
|
||||
const auto descriptor_size = BufferDescriptorSize(descriptor);
|
||||
if (descriptor.Base48() == target.address) {
|
||||
if (!resource.written || resource.read || resource.atomic ||
|
||||
descriptor_size != target.size || descriptor.SwizzleEnabled() ||
|
||||
descriptor.IndexStride() != 0 || descriptor.AddTid() ||
|
||||
resource.packed_stride != descriptor.PackedStride()) {
|
||||
EXIT("unsupported HTile compute metadata access\n");
|
||||
if (!program.info.has_bitwise_xor) {
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& resource = program.info.buffers[i];
|
||||
if (resource.written) {
|
||||
const auto descriptor =
|
||||
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
|
||||
if (cache.ClearMeta(descriptor.Base48())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
metadata_descriptor = descriptor;
|
||||
metadata_writes++;
|
||||
continue;
|
||||
}
|
||||
if (resource.written || !resource.read || resource.atomic || descriptor.Base48() == 0 ||
|
||||
descriptor_size == 0 ||
|
||||
cache.QueryRegion(descriptor.Base48(), descriptor_size).metadata_pages) {
|
||||
EXIT("unsupported HTile clear side-buffer access\n");
|
||||
}
|
||||
GetRenderContext().GetBufferCache().ValidateGpuAccess(descriptor.Base48(), descriptor_size,
|
||||
true, false);
|
||||
}
|
||||
if (metadata_writes != 1) {
|
||||
EXIT("HTile clear requires exactly one write-only metadata buffer, writes=%u\n",
|
||||
metadata_writes);
|
||||
}
|
||||
ValidateFullHtileClearDispatch(input, metadata_descriptor, group_x, group_y, group_z, mode);
|
||||
const bool recorded = registered_meta.full ? cache.ClearMeta(registered_meta.metadata_address)
|
||||
: cache.TouchMeta(registered_meta.metadata_address,
|
||||
registered_meta.slice, true);
|
||||
if (!recorded) {
|
||||
EXIT("failed to record HTile compute clear\n");
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
@@ -324,7 +137,7 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
|
||||
size)) {
|
||||
return false;
|
||||
}
|
||||
auto& cache = GetRenderContext().GetTextureCache();
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
return false;
|
||||
}
|
||||
@@ -337,8 +150,9 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode) {
|
||||
void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
uint32_t thread_group_x, uint32_t thread_group_y,
|
||||
uint32_t thread_group_z, uint32_t mode) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
@@ -347,8 +161,7 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
|
||||
thread_group_x, thread_group_y, thread_group_z, mode,
|
||||
sh_ctx.GetCs().cs_regs.data_addr);
|
||||
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (sh_ctx.GetCs().cs_regs.data_addr == 0) {
|
||||
LOGF("GraphicsRenderDispatchDirect: temporary: ignoring dispatch with null CS shader, "
|
||||
"groups=%ux%ux%u mode=%u\n",
|
||||
@@ -396,17 +209,18 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
|
||||
input_info.dispatch_threads_num[2] = thread_group_z;
|
||||
}
|
||||
|
||||
const uint32_t frame_num = GraphicsRunGetFrameNum();
|
||||
const uint32_t frame_num = static_cast<uint32_t>(m_context.GetGpu().GetFrameNum());
|
||||
const bool large_workgroup =
|
||||
(input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512);
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
if (TryConsumeComputeMetaClear(input_info, buffer, thread_group_x, thread_group_y,
|
||||
thread_group_z, mode)) {
|
||||
if (TryConsumeComputeMetaClear(input_info, buffer)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
|
||||
thread_group_z, mode)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
const auto sampled_images = std::count_if(
|
||||
@@ -501,38 +315,32 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const auto recording_generation = buffer.GetRecordingGeneration();
|
||||
auto vk_buffer = buffer.Handle();
|
||||
auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline(
|
||||
input_info, sh_ctx.GetCs(), cs_shader);
|
||||
buffer.EndRendering();
|
||||
auto& pipeline =
|
||||
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
|
||||
auto bindings = PrepareBindings(buffer, input_info.stage, vk::ShaderStageFlagBits::eCompute,
|
||||
DescriptorCache::Stage::Compute);
|
||||
RebindBuffers(buffer, bindings);
|
||||
RebindImages(buffer, bindings);
|
||||
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
|
||||
auto vk_buffer = buffer.Handle();
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
|
||||
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z);
|
||||
|
||||
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eCompute,
|
||||
pipeline.pipeline_layout, input_info.stage,
|
||||
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute);
|
||||
if (buffer.GetRecordingGeneration() != recording_generation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z);
|
||||
|
||||
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
|
||||
has_storage_writes =
|
||||
std::any_of(
|
||||
program.info.images.begin(), program.info.images.end(),
|
||||
[](const auto& image) {
|
||||
return image.written &&
|
||||
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
|
||||
}) ||
|
||||
has_storage_writes;
|
||||
if (has_storage_writes) {
|
||||
ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
}
|
||||
break;
|
||||
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
|
||||
has_storage_writes =
|
||||
std::any_of(program.info.images.begin(), program.info.images.end(),
|
||||
[](const auto& image) {
|
||||
return image.written &&
|
||||
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
|
||||
}) ||
|
||||
has_storage_writes;
|
||||
if (has_storage_writes) {
|
||||
ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
}
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
@@ -10,24 +12,66 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
RenderContext::RenderContext(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_pipeline_cache(graphics), m_descriptor_cache(graphics),
|
||||
m_framebuffer_cache(graphics), m_sampler_cache(graphics), m_gds_buffer(graphics),
|
||||
m_gpu_resources(graphics) {
|
||||
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
|
||||
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
|
||||
m_sampler_cache(graphics),
|
||||
m_gpu_resources(graphics, m_command_scheduler) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
RenderContext::~RenderContext() {
|
||||
ShutdownGpu();
|
||||
m_command_scheduler.Shutdown();
|
||||
}
|
||||
|
||||
void RenderContext::InitializeGpu(VideoOut::VideoOutDriver* video_out) {
|
||||
EXIT_IF(m_gpu != nullptr);
|
||||
m_video_out = video_out;
|
||||
m_gpu = std::make_unique<Gpu>(*this);
|
||||
m_gpu_resources.SetGpu(m_gpu.get());
|
||||
}
|
||||
|
||||
void RenderContext::ShutdownGpu() {
|
||||
if (m_gpu != nullptr) {
|
||||
m_gpu_resources.SetGpu(nullptr);
|
||||
m_gpu->Shutdown();
|
||||
m_gpu.reset();
|
||||
}
|
||||
if (m_video_out != nullptr) {
|
||||
if (m_command_scheduler.Active()) {
|
||||
m_command_scheduler.FinishCurrent();
|
||||
}
|
||||
m_command_scheduler.DrainPriorityOperations();
|
||||
m_video_out = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Gpu& RenderContext::GetGpu() const {
|
||||
EXIT_IF(m_gpu == nullptr);
|
||||
return *m_gpu;
|
||||
}
|
||||
|
||||
VideoOut::VideoOutDriver& RenderContext::GetVideoOut() const {
|
||||
EXIT_IF(m_video_out == nullptr);
|
||||
return *m_video_out;
|
||||
}
|
||||
|
||||
void RenderContext::AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
|
||||
auto queue = LibKernel::EventQueue::KernelPinEqueue(eq);
|
||||
if (!queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(m_eop_mutex);
|
||||
|
||||
auto it = std::find_if(m_eop_eqs.begin(), m_eop_eqs.end(), [eq, id](const auto& entry) {
|
||||
return entry.eq == eq && entry.id == id;
|
||||
});
|
||||
if (it != m_eop_eqs.end()) {
|
||||
it->count++;
|
||||
return;
|
||||
}
|
||||
|
||||
m_eop_eqs.push_back({eq, id, 1});
|
||||
m_eop_eqs.push_back({eq, std::move(queue), id});
|
||||
}
|
||||
|
||||
void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
|
||||
@@ -40,22 +84,27 @@ void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id)
|
||||
return;
|
||||
}
|
||||
|
||||
if (--it->count == 0) {
|
||||
m_eop_eqs.erase(it);
|
||||
}
|
||||
m_eop_eqs.erase(it);
|
||||
}
|
||||
|
||||
void RenderContext::TriggerEopEvent(uint32_t context_id) {
|
||||
Common::LockGuard lock(m_eop_mutex);
|
||||
std::vector<EopEqRegistration> registrations;
|
||||
{
|
||||
Common::LockGuard lock(m_eop_mutex);
|
||||
registrations = m_eop_eqs;
|
||||
}
|
||||
|
||||
for (auto& eop_entry: m_eop_eqs) {
|
||||
if (eop_entry.eq != nullptr) {
|
||||
const auto id = static_cast<uintptr_t>(eop_entry.id);
|
||||
auto result = LibKernel::EventQueue::KernelTriggerEvent(
|
||||
eop_entry.eq, id, LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
for (const auto& registration: registrations) {
|
||||
const auto result = LibKernel::EventQueue::KernelTriggerEvent(
|
||||
registration.eq, static_cast<uintptr_t>(registration.id),
|
||||
LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
|
||||
if (result == LibKernel::KERNEL_ERROR_EBADF ||
|
||||
result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
DeleteEopEq(registration.eq, registration.id);
|
||||
continue;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != OK);
|
||||
}
|
||||
|
||||
auto tsc = LibKernel::KernelReadTsc();
|
||||
|
||||
@@ -6,38 +6,47 @@
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/renderer/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/gdsBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/gpuResourceManager.h"
|
||||
#include "graphics/host_gpu/renderer/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/samplerCache.h"
|
||||
#include "graphics/host_gpu/renderer/textureCache.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::VideoOut {
|
||||
class VideoOutDriver;
|
||||
}
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
constexpr int AGC_USER_INTERRUPT_EVENT = 0x1800;
|
||||
class Gpu;
|
||||
|
||||
class RenderContext {
|
||||
public:
|
||||
explicit RenderContext(GraphicContext& graphics);
|
||||
~RenderContext() { KYTY_NOT_IMPLEMENTED; }
|
||||
~RenderContext();
|
||||
KYTY_CLASS_NO_COPY(RenderContext);
|
||||
|
||||
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
|
||||
void InitializeGpu(VideoOut::VideoOutDriver* video_out);
|
||||
void ShutdownGpu();
|
||||
[[nodiscard]] Gpu& GetGpu() const;
|
||||
[[nodiscard]] VideoOut::VideoOutDriver& GetVideoOut() const;
|
||||
|
||||
Common::Mutex& GetMutex() { return m_mutex; }
|
||||
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
|
||||
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
|
||||
DescriptorCache& GetDescriptorCache() { return m_descriptor_cache; }
|
||||
FramebufferCache& GetFramebufferCache() { return m_framebuffer_cache; }
|
||||
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
|
||||
GdsBuffer& GetGdsBuffer() { return m_gds_buffer; }
|
||||
GpuResourceManager& GetGpuResources() { return m_gpu_resources; }
|
||||
BufferCache& GetBufferCache() { return m_gpu_resources.GetBufferCache(); }
|
||||
TextureCache& GetTextureCache() { return m_gpu_resources.GetTextureCache(); }
|
||||
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
|
||||
|
||||
void AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
void DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
@@ -45,26 +54,26 @@ public:
|
||||
|
||||
private:
|
||||
struct EopEqRegistration {
|
||||
LibKernel::EventQueue::KernelEqueue eq = nullptr;
|
||||
int id = 0;
|
||||
uint32_t count = 0;
|
||||
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
LibKernel::EventQueue::KernelEqueueRef queue;
|
||||
int id = 0;
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
PipelineCache m_pipeline_cache;
|
||||
DescriptorCache m_descriptor_cache;
|
||||
FramebufferCache m_framebuffer_cache;
|
||||
SamplerCache m_sampler_cache;
|
||||
GdsBuffer m_gds_buffer;
|
||||
GpuResourceManager m_gpu_resources;
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
RenderExecutor m_render_executor;
|
||||
CommandScheduler m_command_scheduler;
|
||||
DescriptorCache m_descriptor_cache;
|
||||
PipelineCache m_pipeline_cache;
|
||||
SamplerCache m_sampler_cache;
|
||||
GpuResourceManager m_gpu_resources;
|
||||
std::unique_ptr<Gpu> m_gpu;
|
||||
VideoOut::VideoOutDriver* m_video_out = nullptr;
|
||||
|
||||
Common::Mutex m_eop_mutex;
|
||||
std::vector<EopEqRegistration> m_eop_eqs;
|
||||
};
|
||||
|
||||
[[nodiscard]] RenderContext& GetRenderContext() noexcept;
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERCONTEXT_H_
|
||||
|
||||
@@ -13,18 +13,15 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/label.h"
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
@@ -36,9 +33,12 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -70,13 +70,9 @@ static std::atomic<uint32_t> g_mrt_state_log_count = 0;
|
||||
static std::atomic<uint32_t> g_shader_stage_log_count = 0;
|
||||
static std::atomic<uint32_t> g_framebuffer_skip_log_count = 0;
|
||||
|
||||
static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset);
|
||||
|
||||
static const char* RenderColorTypeName(RenderColorType type) {
|
||||
switch (type) {
|
||||
case RenderColorType::NoColorOutput: return "NoColorOutput";
|
||||
case RenderColorType::DisplayBuffer: return "DisplayBuffer";
|
||||
case RenderColorType::RenderTexture: return "RenderTexture";
|
||||
default: return "Unknown";
|
||||
}
|
||||
@@ -105,8 +101,8 @@ static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& col
|
||||
" color_image=%s depth_format=%s depth_image=%s depth_vaddr_num=%d target_mask=0x%08" PRIx32
|
||||
" prim=%u index_count=%u flags=0x%08" PRIx32 "\n",
|
||||
log_id, draw_name, RenderColorTypeName(color.type), color.base_addr, color.buffer_size,
|
||||
color.vulkan_buffer != nullptr ? "yes" : "no", VulkanToString(depth.format).c_str(),
|
||||
depth.vulkan_buffer != nullptr ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
|
||||
color.image_id ? "yes" : "no", VulkanToString(depth.format).c_str(),
|
||||
depth.image_id ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
|
||||
ucfg.GetPrimType(), index_count, flags);
|
||||
}
|
||||
|
||||
@@ -181,7 +177,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
|
||||
}
|
||||
|
||||
auto log_id = g_draw_state_log_count.fetch_add(1);
|
||||
if (log_id >= 192 && color.type != RenderColorType::DisplayBuffer) {
|
||||
if (log_id >= 192) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,7 +193,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
|
||||
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
|
||||
});
|
||||
|
||||
vk::Extent2D extent = color.vulkan_buffer != nullptr ? color.extent : vk::Extent2D {};
|
||||
vk::Extent2D extent = color.image_id ? color.extent : vk::Extent2D {};
|
||||
auto sc = calc_final_scissor(vp, ctx.GetScanModeControl(), extent);
|
||||
|
||||
LOGF(
|
||||
@@ -207,12 +203,13 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
|
||||
" blend=%s src=%u dst=%u comb=%u ps_tex=%d sampled=%d storage=%d ps_kill=%s target_mode0=%u"
|
||||
" depth_test=%s depth_write=%s depth_func=%u depth_clear=%s viewport=(%.1f,%.1f %.1fx%.1f) "
|
||||
"scissor=(%d,%d)-(%d,%d)\n",
|
||||
log_id, GraphicsRunGetFrameNum(), draw_name, RenderColorTypeName(color.type),
|
||||
color.base_addr, extent.width, extent.height, ucfg.GetPrimType(), index_count, flags,
|
||||
ctx.GetRenderTargetMask(), color.color_clear_enable ? "true" : "false",
|
||||
color.color_clear_value.float32[0], color.color_clear_value.float32[1],
|
||||
color.color_clear_value.float32[2], color.color_clear_value.float32[3], cc.mode, cc.op,
|
||||
bc.enable ? "true" : "false", bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn,
|
||||
log_id, buffer.GetContext().GetGpu().GetFrameNum(), draw_name,
|
||||
RenderColorTypeName(color.type), color.base_addr, extent.width, extent.height,
|
||||
ucfg.GetPrimType(), index_count, flags, ctx.GetRenderTargetMask(),
|
||||
color.color_clear_enable ? "true" : "false", color.color_clear_value.float32[0],
|
||||
color.color_clear_value.float32[1], color.color_clear_value.float32[2],
|
||||
color.color_clear_value.float32[3], cc.mode, cc.op, bc.enable ? "true" : "false",
|
||||
bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn,
|
||||
static_cast<int>(ps_resources.images.size()), static_cast<int>(sampled_images),
|
||||
static_cast<int>(ps_resources.images.size() - sampled_images),
|
||||
ps_input_info.ps_pixel_kill_enable ? "true" : "false", ps_input_info.target_output_mode[0],
|
||||
@@ -224,20 +221,21 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
|
||||
LogMrtState(draw_name, buffer, ps_input_info);
|
||||
}
|
||||
|
||||
static void LogDrawInputState(const RenderColorInfo& color,
|
||||
static void LogDrawInputState(const RenderCommandBuffer& buffer,
|
||||
const RenderColorInfo& color,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
uint32_t index_type_and_size, uint32_t index_count,
|
||||
const void* index_addr) {
|
||||
auto log_id = g_draw_input_log_count.fetch_add(1);
|
||||
if (log_id >= 64 && color.type != RenderColorType::DisplayBuffer) {
|
||||
if (log_id >= 64) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGF("DrawInputState[%u]: frame=%d target=%s addr=0x%010" PRIx64
|
||||
" index_type=%u index_count=%u index_addr=0x%016" PRIx64
|
||||
" vs_resources=%d vs_buffers=%d\n",
|
||||
log_id, GraphicsRunGetFrameNum(), RenderColorTypeName(color.type), color.base_addr,
|
||||
index_type_and_size, index_count, reinterpret_cast<uint64_t>(index_addr),
|
||||
log_id, buffer.GetContext().GetGpu().GetFrameNum(), RenderColorTypeName(color.type),
|
||||
color.base_addr, index_type_and_size, index_count, reinterpret_cast<uint64_t>(index_addr),
|
||||
vs_input_info.resources_num, vs_input_info.buffers_num);
|
||||
|
||||
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
|
||||
@@ -303,15 +301,6 @@ static void LogDrawInputState(const RenderColorInfo& color,
|
||||
}
|
||||
}
|
||||
|
||||
static void VulkanCmdSetColorWriteEnableEXT(vk::CommandBuffer command_buffer,
|
||||
uint32_t attachment_count,
|
||||
const vk::Bool32* p_color_write_enables) {
|
||||
if (VULKAN_HPP_DEFAULT_DISPATCHER.vkCmdSetColorWriteEnableEXT == nullptr) {
|
||||
EXIT("vkCmdSetColorWriteEnableEXT not present\n");
|
||||
}
|
||||
command_buffer.setColorWriteEnableEXT(attachment_count, p_color_write_enables);
|
||||
}
|
||||
|
||||
static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandBuffer& buffer,
|
||||
const RenderColorInfo* colors,
|
||||
uint32_t color_count,
|
||||
@@ -325,10 +314,10 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
|
||||
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
vk::Extent2D framebuffer_extent {};
|
||||
if (color_count > 0 && colors[0].vulkan_buffer != nullptr) {
|
||||
if (color_count > 0 && colors[0].image_id) {
|
||||
framebuffer_extent = colors[0].extent;
|
||||
} else if (depth.vulkan_buffer != nullptr) {
|
||||
framebuffer_extent = depth.vulkan_buffer->extent;
|
||||
} else if (depth.image_id) {
|
||||
framebuffer_extent = {depth.width, depth.height};
|
||||
}
|
||||
|
||||
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
|
||||
@@ -406,11 +395,18 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
|
||||
dynamic_params.stencil_back.reference);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK has no VK_EXT_color_write_enable; the pipeline is created without the
|
||||
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
|
||||
#else
|
||||
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) {
|
||||
enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE);
|
||||
}
|
||||
VulkanCmdSetColorWriteEnableEXT(vk_buffer, dynamic_params.color_write_count, enable);
|
||||
if (dynamic_params.color_write_count != 0) {
|
||||
vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
|
||||
@@ -486,8 +482,7 @@ struct DrawRenderState {
|
||||
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
bool ps_active = true;
|
||||
VulkanFramebuffer* framebuffer = nullptr;
|
||||
vk::CommandBuffer vk_buffer = nullptr;
|
||||
RenderState rendering;
|
||||
ShaderVertexInputInfo vs_input_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
std::span<const uint32_t> vs_shader;
|
||||
@@ -503,6 +498,123 @@ struct DrawCallInfo {
|
||||
uint32_t first_instance = 0;
|
||||
};
|
||||
|
||||
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
||||
uint32_t color_count, RenderDepthInfo& depth) {
|
||||
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
RenderState state {};
|
||||
state.width = std::numeric_limits<uint32_t>::max();
|
||||
state.height = std::numeric_limits<uint32_t>::max();
|
||||
state.num_layers = std::numeric_limits<uint32_t>::max();
|
||||
state.num_color_attachments = color_count;
|
||||
uint32_t attachment_samples = 0;
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
auto& target = colors[i];
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto old_image = cache.ResolveOwner(target.image_id);
|
||||
if (old_image == nullptr ||
|
||||
(!old_image->registered && !old_image->info.data.Empty()) ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
}
|
||||
target.image_id = cache.FindImage(target.desc);
|
||||
BindRenderTarget(target.image_id);
|
||||
}
|
||||
target.image_view = cache.FindRenderTarget(target.image_id, target.desc);
|
||||
auto& image = cache.GetImage(target.image_id);
|
||||
EXIT_IF(image.backing.samples != target.samples || target.image_view == nullptr);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = target.samples;
|
||||
} else if (attachment_samples != target.samples) {
|
||||
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, target.samples);
|
||||
}
|
||||
const auto& view = target.desc.view_info;
|
||||
const auto layout =
|
||||
image.binding.is_bound ? vk::ImageLayout::eGeneral
|
||||
: vk::ImageLayout::eColorAttachmentOptimal;
|
||||
image.Transit(layout,
|
||||
vk::AccessFlagBits2::eColorAttachmentRead |
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
|
||||
view.layer_count},
|
||||
buffer.Handle());
|
||||
state.width = std::min(state.width, target.extent.width);
|
||||
state.height = std::min(state.height, target.extent.height);
|
||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||
auto& attachment = state.color_attachments[i];
|
||||
attachment.image_view = target.image_view;
|
||||
attachment.image_layout = layout;
|
||||
attachment.clear_value = target.color_clear_value.uint32;
|
||||
attachment.is_clear = target.color_clear_enable;
|
||||
}
|
||||
if (depth.image_id) {
|
||||
const auto owner = cache.ResolveOwner(depth.image_id);
|
||||
if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) {
|
||||
EXIT("depth target changed after render-state discovery\n");
|
||||
}
|
||||
depth.image_view = cache.FindDepthTarget(depth.image_id, depth.desc);
|
||||
if (depth.htile && depth.depth_clear_enable && !cache.ClearMeta(depth.htile_buffer_vaddr)) {
|
||||
EXIT("failed to acquire HTile metadata for a depth clear\n");
|
||||
}
|
||||
depth.depth_meta_clear_enable =
|
||||
depth.htile &&
|
||||
cache.IsMetaCleared(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer);
|
||||
depth.depth_load_clear_enable =
|
||||
depth.depth_clear_enable || depth.depth_meta_clear_enable;
|
||||
if (depth.depth_meta_clear_enable &&
|
||||
!cache.TouchMeta(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer, false)) {
|
||||
EXIT("failed to consume HTile clear state\n");
|
||||
}
|
||||
auto& image = cache.GetImage(depth.image_id);
|
||||
EXIT_IF(depth.image_view == nullptr || image.backing.samples != depth.samples);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.samples;
|
||||
} else if (attachment_samples != depth.samples) {
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, depth.samples);
|
||||
}
|
||||
const auto layout = depth_attachment_layout(depth);
|
||||
const auto writes = depth.AttachmentWriteAspects();
|
||||
auto access = vk::AccessFlags2 {vk::AccessFlagBits2::eDepthStencilAttachmentRead};
|
||||
if (writes) {
|
||||
access |= vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
|
||||
}
|
||||
const auto& view = depth.desc.view_info;
|
||||
image.Transit(layout, access,
|
||||
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
|
||||
view.layer_count},
|
||||
buffer.Handle());
|
||||
state.width = std::min(state.width, depth.width);
|
||||
state.height = std::min(state.height, depth.height);
|
||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
|
||||
auto& attachment = state.depth_stencil_attachment;
|
||||
attachment.image_view = depth.image_view;
|
||||
attachment.image_layout = layout;
|
||||
attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value);
|
||||
attachment.clear_value[1] = depth.stencil_clear_value;
|
||||
attachment.has_depth =
|
||||
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth);
|
||||
attachment.depth_clear = depth.depth_load_clear_enable;
|
||||
attachment.has_stencil =
|
||||
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
|
||||
attachment.stencil_clear = depth.stencil_clear_enable;
|
||||
}
|
||||
if (attachment_samples == 0 ||
|
||||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
|
||||
EXIT("render state has no valid attachments\n");
|
||||
}
|
||||
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
|
||||
state.num_layers = 1;
|
||||
}
|
||||
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
|
||||
state.width == std::numeric_limits<uint32_t>::max() ||
|
||||
state.height == std::numeric_limits<uint32_t>::max());
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
|
||||
const DrawRenderState& state, const DrawCallInfo& draw) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
@@ -510,7 +622,7 @@ static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
|
||||
const auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
|
||||
state.depth_info.vulkan_buffer != nullptr);
|
||||
static_cast<bool>(state.depth_info.image_id));
|
||||
if (state.color_count != 0 || !with_depth) {
|
||||
return true;
|
||||
}
|
||||
@@ -558,6 +670,16 @@ struct DrawIndexBufferSource {
|
||||
vk::IndexType type = vk::IndexType::eUint16;
|
||||
};
|
||||
|
||||
struct PreparedIndexBuffer {
|
||||
std::shared_ptr<void> owner;
|
||||
vk::Buffer buffer = nullptr;
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::IndexType type = vk::IndexType::eUint16;
|
||||
bool streamed = false;
|
||||
};
|
||||
|
||||
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) {
|
||||
return (buffer.stride != 0 ? static_cast<uint64_t>(buffer.stride) * buffer.num_records
|
||||
: buffer.num_records);
|
||||
@@ -613,24 +735,16 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, uint32_t render_target_slice_offset,
|
||||
bool skip_null_framebuffer, bool log_setup_phases,
|
||||
DrawRenderState& state) {
|
||||
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
bool log_setup_phases, DrawRenderState& state) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
|
||||
if (log_setup_phases) {
|
||||
LogDrawPhase(draw.name, "ResolveRenderDepthTarget");
|
||||
}
|
||||
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
|
||||
|
||||
if (ResolveColorTargets(submit_id, buffer, render_target_slice_offset)) {
|
||||
MarkRenderTargetGpuWritten(state.depth_info);
|
||||
return false;
|
||||
}
|
||||
MarkRenderTargetGpuWritten(state.depth_info);
|
||||
|
||||
if (log_setup_phases) {
|
||||
LogDrawPhase(draw.name, "ResolveRenderColorTarget");
|
||||
}
|
||||
@@ -639,15 +753,18 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
|
||||
ctx.GetRenderTarget(slot).base.addr != 0)) {
|
||||
ResolveRenderColorTarget(submit_id, buffer, state.color_info[state.color_count],
|
||||
render_target_slice_offset, slot);
|
||||
if (state.color_info[state.color_count].vulkan_buffer != nullptr) {
|
||||
MarkRenderTargetGpuWritten(state.color_info[state.color_count]);
|
||||
if (state.color_info[state.color_count].image_id) {
|
||||
state.color_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (log_setup_phases) {
|
||||
LogDrawPhase(draw.name, "ResolveRenderDepthTarget");
|
||||
}
|
||||
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
|
||||
|
||||
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
|
||||
state.depth_info.vulkan_buffer != nullptr);
|
||||
static_cast<bool>(state.depth_info.image_id));
|
||||
if (state.color_count == 0 && !with_depth) {
|
||||
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
|
||||
draw.index_count, draw.flags);
|
||||
@@ -655,22 +772,6 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
|
||||
}
|
||||
state.ps_active = DrawHasActivePixelShader(buffer, state, draw);
|
||||
|
||||
if (log_setup_phases) {
|
||||
LogDrawPhase(draw.name, "CreateFramebuffer");
|
||||
}
|
||||
state.framebuffer = GetRenderContext().GetFramebufferCache().CreateFramebuffer(
|
||||
state.color_info, state.color_count, state.depth_info);
|
||||
|
||||
if (state.framebuffer == nullptr && skip_null_framebuffer) {
|
||||
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
|
||||
draw.index_count, draw.flags);
|
||||
return false;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(state.framebuffer == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(state.framebuffer->render_pass == nullptr);
|
||||
|
||||
state.vk_buffer = buffer.Handle();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -714,56 +815,107 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
|
||||
}
|
||||
}
|
||||
|
||||
static void BindDrawVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, vk::CommandBuffer vk_buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info) {
|
||||
static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t submit_id,
|
||||
RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
const ShaderVertexInputInfo& vs_input_info) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
(void)submit_id;
|
||||
|
||||
LogDrawPhase(draw.name, "BindVertexBuffers");
|
||||
LogDrawPhase(draw.name, "PrepareVertexBuffers");
|
||||
std::vector<BufferBinding> bindings;
|
||||
bindings.reserve(vs_input_info.buffers_num);
|
||||
for (int i = 0; i < vs_input_info.buffers_num; i++) {
|
||||
const auto& b = vs_input_info.buffers[i];
|
||||
uint64_t addr = b.addr;
|
||||
uint64_t size = VertexBufferDescriptorSize(b);
|
||||
VulkanBuffer* vertices = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
|
||||
const auto& b = vs_input_info.buffers[i];
|
||||
const auto size = VertexBufferDescriptorSize(b);
|
||||
if (size == 0) {
|
||||
vertices = &GetRenderContext().GetBufferCache().ObtainNullBuffer(buffer);
|
||||
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
|
||||
bindings.push_back({owner, owner->Handle(), 0});
|
||||
} else {
|
||||
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(buffer, addr, size);
|
||||
vertices = &binding.buffer;
|
||||
offset = binding.offset;
|
||||
bindings.push_back(
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(vertices == nullptr);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
vk_buffer.bindVertexBuffers(i, 1, &vertices->buffer, &offset);
|
||||
static void RebindVertexBuffers(RenderCommandBuffer& buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
std::vector<BufferBinding>& bindings) {
|
||||
EXIT_IF(bindings.size() != static_cast<size_t>(vs_input_info.buffers_num));
|
||||
for (int i = 0; i < vs_input_info.buffers_num; i++) {
|
||||
const auto& vertex = vs_input_info.buffers[i];
|
||||
const auto size = VertexBufferDescriptorSize(vertex);
|
||||
if (size == 0) {
|
||||
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
|
||||
bindings[i] = {owner, owner->Handle(), 0};
|
||||
} else {
|
||||
bindings[i] =
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void BindDrawIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const DrawIndexBufferSource& source) {
|
||||
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
|
||||
const DrawIndexBufferSource& source) {
|
||||
PreparedIndexBuffer prepared;
|
||||
if (!source.enabled) {
|
||||
return;
|
||||
return prepared;
|
||||
}
|
||||
EXIT_IF(source.size == 0);
|
||||
|
||||
VulkanBuffer* index_buffer = nullptr;
|
||||
vk::DeviceSize index_offset = 0;
|
||||
prepared.address = source.address;
|
||||
prepared.size = source.size;
|
||||
prepared.type = source.type;
|
||||
if (source.host_data != nullptr) {
|
||||
vk::DeviceSize range = 0;
|
||||
if (!GetRenderContext().GetBufferCache().UploadHostData(
|
||||
buffer, source.host_data, source.size, 16, index_buffer, index_offset, range)) {
|
||||
EXIT("failed to upload host index buffer\n");
|
||||
}
|
||||
auto binding =
|
||||
buffer.GetContext().GetBufferCache().UploadTransient(source.host_data, source.size, 16);
|
||||
prepared.owner = std::move(binding.owner);
|
||||
prepared.buffer = binding.buffer;
|
||||
prepared.offset = binding.offset;
|
||||
prepared.streamed = true;
|
||||
} else {
|
||||
auto binding =
|
||||
GetRenderContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
|
||||
index_buffer = &binding.buffer;
|
||||
index_offset = binding.offset;
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
|
||||
prepared.owner = std::move(binding.owner);
|
||||
prepared.buffer = binding.buffer;
|
||||
prepared.offset = binding.offset;
|
||||
}
|
||||
EXIT_IF(index_buffer == nullptr);
|
||||
vk_buffer.bindIndexBuffer(index_buffer->buffer, index_offset, source.type);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
static void RebindIndexBuffer(RenderCommandBuffer& buffer, PreparedIndexBuffer& prepared) {
|
||||
if (prepared.size == 0 || prepared.streamed) {
|
||||
return;
|
||||
}
|
||||
auto binding =
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, prepared.address, prepared.size);
|
||||
prepared.owner = std::move(binding.owner);
|
||||
prepared.buffer = binding.buffer;
|
||||
prepared.offset = binding.offset;
|
||||
}
|
||||
|
||||
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
std::vector<BufferBinding>& bindings) {
|
||||
for (uint32_t slot = 0; slot < bindings.size(); slot++) {
|
||||
auto& binding = bindings[slot];
|
||||
if (binding.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(binding.owner);
|
||||
}
|
||||
EXIT_IF(binding.buffer == nullptr);
|
||||
vk_buffer.bindVertexBuffers(slot, 1, &binding.buffer, &binding.offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const PreparedIndexBuffer& prepared) {
|
||||
if (prepared.size == 0) {
|
||||
return;
|
||||
}
|
||||
if (prepared.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(prepared.owner);
|
||||
}
|
||||
EXIT_IF(prepared.buffer == nullptr);
|
||||
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
|
||||
}
|
||||
|
||||
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
@@ -784,7 +936,7 @@ static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCa
|
||||
LogDrawTargetState(draw.name, state.color_info[0], state.depth_info, buffer,
|
||||
state.ps_input_info, draw.index_count, draw.flags);
|
||||
}
|
||||
LogDrawInputState(state.color_info[0], state.vs_input_info, index_type_and_size,
|
||||
LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size,
|
||||
draw.index_count, index_addr);
|
||||
// LogDrawTextureState(draw.name, state.color_info[0], state.ps_input_info);
|
||||
}
|
||||
@@ -858,102 +1010,94 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
}
|
||||
}
|
||||
|
||||
static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source, bool log_pipeline_phase,
|
||||
bool set_bind_debug, bool set_auto_debug) {
|
||||
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source,
|
||||
bool log_pipeline_phase, bool set_bind_debug,
|
||||
bool set_auto_debug) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
|
||||
for (;;) {
|
||||
const auto recording_generation = buffer.GetRecordingGeneration();
|
||||
if (log_pipeline_phase) {
|
||||
LogDrawPhase(draw.name, "CreatePipeline");
|
||||
}
|
||||
auto& pipeline = GetRenderContext().GetPipelineCache().CreateGraphicsPipeline(
|
||||
*state.framebuffer, state.color_info, state.color_count, state.depth_info,
|
||||
state.vs_input_info, buffer, &state.ps_input_info, topology, state.ps_active,
|
||||
state.vs_shader, state.ps_shader);
|
||||
LogDrawPhase(draw.name, "PrepareBindings");
|
||||
auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage,
|
||||
state.ps_input_info.stage, state.ps_active);
|
||||
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
|
||||
auto index_binding = PrepareIndexBuffer(buffer, index_source);
|
||||
RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
|
||||
RebindIndexBuffer(buffer, index_binding);
|
||||
state.rendering =
|
||||
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info);
|
||||
|
||||
if (set_bind_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u);
|
||||
}
|
||||
state.vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
||||
const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info,
|
||||
state.color_count, state.depth_info);
|
||||
SetDynamicParams(buffer, state.vk_buffer, dynamic_params);
|
||||
if (log_pipeline_phase) {
|
||||
LogDrawPhase(draw.name, "CreatePipeline");
|
||||
}
|
||||
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
|
||||
state.color_info, state.color_count, state.depth_info, state.vs_input_info, buffer,
|
||||
&state.ps_input_info, topology, state.ps_active, state.vs_shader, state.ps_shader);
|
||||
|
||||
// EXIT_NOT_IMPLEMENTED(vs_input_info.buffers_num > 1);
|
||||
BindDrawVertexBuffers(submit_id, buffer, draw, state.vk_buffer, state.vs_input_info);
|
||||
|
||||
LogDrawPhase(draw.name, "BindDescriptorsVS");
|
||||
// Resource preparation above may synchronously finish and restart the scheduler. From this
|
||||
// point onward, every operation targets the current command buffer and cannot touch guest
|
||||
// memory.
|
||||
auto vk_buffer = buffer.Handle();
|
||||
if (set_bind_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u);
|
||||
}
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
|
||||
}
|
||||
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings);
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
|
||||
bindings.vertex);
|
||||
if (bindings.pixel.has_value()) {
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
|
||||
}
|
||||
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eGraphics,
|
||||
pipeline.pipeline_layout, state.vs_input_info.stage,
|
||||
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex);
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
|
||||
*bindings.pixel);
|
||||
}
|
||||
CommitIndexBuffer(buffer, vk_buffer, index_binding);
|
||||
|
||||
if (state.ps_active) {
|
||||
LogDrawPhase(draw.name, "BindDescriptorsPS");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
|
||||
}
|
||||
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eGraphics,
|
||||
pipeline.pipeline_layout, state.ps_input_info.stage,
|
||||
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
|
||||
}
|
||||
if (buffer.GetRecordingGeneration() != recording_generation) {
|
||||
continue;
|
||||
}
|
||||
// Index data may use this command buffer's host stream. Resolve it only after the other
|
||||
// fault-capable bindings, and rebuild it whenever a fault reset changes the generation.
|
||||
BindDrawIndexBuffer(buffer, state.vk_buffer, index_source);
|
||||
if (buffer.GetRecordingGeneration() != recording_generation) {
|
||||
continue;
|
||||
}
|
||||
const auto dynamic_params =
|
||||
BuildGraphicsDynamicParams(buffer, state.color_info, state.color_count, state.depth_info);
|
||||
SetDynamicParams(buffer, vk_buffer, dynamic_params);
|
||||
|
||||
LogDrawPhase(draw.name, "BeginRenderPass");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
|
||||
}
|
||||
buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count,
|
||||
state.depth_info);
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
||||
}
|
||||
LogDrawPhase(draw.name, "BeginRendering");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
|
||||
}
|
||||
m_context.GetCommandScheduler().BeginRendering(state.rendering);
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
||||
}
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, state.vs_input_info, draw, emit);
|
||||
|
||||
EmitDrawPrimitives(ucfg, state.vk_buffer, state.vs_input_info, draw, emit);
|
||||
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
|
||||
}
|
||||
buffer.EndRenderPass();
|
||||
vk::PipelineStageFlags shader_write_stages = {};
|
||||
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader;
|
||||
}
|
||||
if (state.ps_active) {
|
||||
if (HasShaderBufferWrites(state.ps_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
|
||||
}
|
||||
}
|
||||
if (shader_write_stages) {
|
||||
ShaderWriteBarrier(state.vk_buffer, shader_write_stages);
|
||||
}
|
||||
LogDrawPhase(draw.name, "EndRenderPass");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x700u);
|
||||
}
|
||||
break;
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
|
||||
}
|
||||
vk::PipelineStageFlags shader_write_stages = {};
|
||||
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader;
|
||||
}
|
||||
if (state.ps_active && HasShaderBufferWrites(state.ps_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
|
||||
}
|
||||
if (shader_write_stages) {
|
||||
m_context.GetCommandScheduler().EndRendering();
|
||||
ShaderWriteBarrier(vk_buffer, shader_write_stages);
|
||||
}
|
||||
LogDrawPhase(draw.name, "DrawComplete");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x700u);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
|
||||
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count, uint32_t render_target_slice_offset,
|
||||
int32_t vertex_offset_add, uint32_t first_instance) {
|
||||
void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
uint32_t index_type_and_size, uint32_t index_count,
|
||||
const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count, uint32_t render_target_slice_offset,
|
||||
int32_t vertex_offset_add, uint32_t first_instance) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
@@ -964,13 +1108,13 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
|
||||
index_count, flags, type, instance_count,
|
||||
reinterpret_cast<uint64_t>(index_addr));
|
||||
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (index_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1001,7 +1145,6 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
|
||||
instance_count, render_target_slice_offset, static_cast<uint32_t>(vertex_offset_add),
|
||||
first_instance);
|
||||
}
|
||||
sh_check(sh_ctx);
|
||||
|
||||
uc_check(ucfg);
|
||||
|
||||
@@ -1063,8 +1206,8 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
|
||||
index_source.type = index_type;
|
||||
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, true,
|
||||
state)) {
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, true, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1081,12 +1224,15 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, true, true,
|
||||
false);
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
|
||||
uint32_t flags, uint32_t render_target_slice_offset,
|
||||
uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
uint32_t index_count,
|
||||
uint32_t flags, uint32_t render_target_slice_offset,
|
||||
uint32_t instance_count, uint32_t first_vertex,
|
||||
uint32_t first_instance) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
@@ -1096,13 +1242,13 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DrawIndexAuto), submit_id,
|
||||
index_count, flags, first_vertex, instance_count, first_instance);
|
||||
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (index_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1129,7 +1275,6 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
|
||||
index_count, flags, render_target_slice_offset, instance_count, first_vertex,
|
||||
first_instance);
|
||||
}
|
||||
sh_check(sh_ctx);
|
||||
|
||||
uc_check(ucfg);
|
||||
|
||||
@@ -1145,8 +1290,8 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
|
||||
instance_count, first_instance};
|
||||
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, true, false,
|
||||
state)) {
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1154,6 +1299,7 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
|
||||
const bool use_ngg_rectlist_draw = Config::NggRectlistDrawEnabled();
|
||||
|
||||
if (!GetDrawTopology(ucfg, true, use_ngg_rectlist_draw, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
const bool draw_prim7_as_ngg =
|
||||
@@ -1170,6 +1316,7 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
|
||||
state.ps_input_info.input_num, sh_ctx.GetPs().ps_regs.chksum,
|
||||
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
|
||||
}
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1189,27 +1336,11 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
|
||||
true);
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
bool IsSameColorResolveSubresource(const RenderColorInfo& src, const RenderColorInfo& dst) {
|
||||
return src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level &&
|
||||
src.base_array_layer == dst.base_array_layer;
|
||||
}
|
||||
|
||||
ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src, const RenderColorInfo& dst,
|
||||
uint32_t width, uint32_t height) {
|
||||
ImageImageCopy region {*src.vulkan_buffer};
|
||||
region.src_level = src.base_mip_level;
|
||||
region.dst_level = dst.base_mip_level;
|
||||
region.width = width;
|
||||
region.height = height;
|
||||
region.src_layer = src.base_array_layer;
|
||||
region.dst_layer = dst.base_array_layer;
|
||||
return region;
|
||||
}
|
||||
|
||||
static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset) {
|
||||
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
if (hw.GetColorControl().mode != 3) {
|
||||
return false;
|
||||
@@ -1224,25 +1355,23 @@ static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderColorInfo src {};
|
||||
RenderColorInfo dst {};
|
||||
ResolveRenderColorTarget(submit_id, buffer, src, render_target_slice_offset, 0, true, true);
|
||||
ResolveRenderColorTarget(submit_id, buffer, dst, render_target_slice_offset, 1, true);
|
||||
if (src.vulkan_buffer == nullptr || dst.vulkan_buffer == nullptr ||
|
||||
src.type == RenderColorType::NoColorOutput || dst.type == RenderColorType::NoColorOutput) {
|
||||
ResolveRenderColorTarget(submit_id, buffer, dst, render_target_slice_offset, 1, true, true);
|
||||
if (!src.image_id || !dst.image_id || src.type == RenderColorType::NoColorOutput ||
|
||||
dst.type == RenderColorType::NoColorOutput) {
|
||||
return false;
|
||||
}
|
||||
if (IsSameColorResolveSubresource(src, dst)) {
|
||||
if (src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level &&
|
||||
src.base_array_layer == dst.base_array_layer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t width = std::min(src.extent.width, dst.extent.width);
|
||||
const uint32_t height = std::min(src.extent.height, dst.extent.height);
|
||||
if (width == 0 || height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array regions {MakeColorResolveCopy(src, dst, width, height)};
|
||||
|
||||
MarkRenderTargetGpuWritten(dst);
|
||||
Transfer::CopyImage(buffer, regions, *dst.vulkan_buffer, dst.vulkan_buffer->layout);
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
cache.MarkGpuWritten(dst.image_id);
|
||||
auto& source = cache.GetImage(src.image_id);
|
||||
auto& destination = cache.GetImage(dst.image_id);
|
||||
destination.Resolve(source,
|
||||
{src.base_mip_level, 1, src.base_array_layer, 1},
|
||||
{dst.base_mip_level, 1, dst.base_array_layer, 1});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct ImageImageCopy;
|
||||
struct RenderColorInfo;
|
||||
struct ShaderVertexInputInfo;
|
||||
|
||||
@@ -39,13 +38,8 @@ static_assert(sizeof(PipelineDynamicParameters) ==
|
||||
sizeof(uint32_t) + sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) +
|
||||
sizeof(PipelineStencilDynamicState) * 2);
|
||||
|
||||
[[nodiscard]] bool IsSameColorResolveSubresource(const RenderColorInfo& src,
|
||||
const RenderColorInfo& dst);
|
||||
[[nodiscard]] ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src,
|
||||
const RenderColorInfo& dst, uint32_t width,
|
||||
uint32_t height);
|
||||
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info);
|
||||
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -10,6 +11,30 @@ namespace Libs::Graphics {
|
||||
|
||||
static constexpr uint32_t RENDER_COLOR_ATTACHMENTS_MAX = 8;
|
||||
|
||||
struct RenderAttachment {
|
||||
vk::ImageView image_view = nullptr;
|
||||
vk::ImageLayout image_layout = vk::ImageLayout::eUndefined;
|
||||
std::array<uint32_t, 4> clear_value = {};
|
||||
bool is_clear = false;
|
||||
bool has_depth = false;
|
||||
bool depth_clear = false;
|
||||
bool has_stencil = false;
|
||||
bool stencil_clear = false;
|
||||
|
||||
bool operator==(const RenderAttachment&) const = default;
|
||||
};
|
||||
|
||||
struct RenderState {
|
||||
std::array<RenderAttachment, RENDER_COLOR_ATTACHMENTS_MAX> color_attachments;
|
||||
RenderAttachment depth_stencil_attachment;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t num_layers = 1;
|
||||
uint32_t num_color_attachments = 0;
|
||||
|
||||
bool operator==(const RenderState&) const = default;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr uint32_t render_sample_count(uint32_t encoded_samples) {
|
||||
return encoded_samples <= 3 ? 1u << encoded_samples : 0;
|
||||
}
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
#include "graphics/host_gpu/renderer/renderTargetBarriers.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void GraphicsRenderMemoryBarrier(CommandBuffer& buffer) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
|
||||
auto vk_buffer = buffer.Handle();
|
||||
|
||||
VulkanMemoryBarrier mem_barrier {};
|
||||
mem_barrier.sType = vk::StructureType::eMemoryBarrier;
|
||||
mem_barrier.pNext = nullptr;
|
||||
mem_barrier.srcAccessMask = vk::AccessFlagBits::eMemoryWrite;
|
||||
mem_barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
|
||||
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags {}, 1,
|
||||
&mem_barrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
void GraphicsRenderTextureBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image) {
|
||||
|
||||
vk::ImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask =
|
||||
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eMemoryRead;
|
||||
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
image_memory_barrier.oldLayout = image.layout;
|
||||
image_memory_barrier.newLayout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.image = image.image;
|
||||
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
image_memory_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_memory_barrier.subresourceRange.layerCount = image.layers;
|
||||
|
||||
vk_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput |
|
||||
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer |
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
|
||||
vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
|
||||
|
||||
image.layout = image_memory_barrier.newLayout;
|
||||
}
|
||||
|
||||
void GraphicsRenderColorImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
|
||||
vk::ImageLayout new_layout) {
|
||||
if (image.layout != new_layout) {
|
||||
if (image.type == VulkanImageType::VideoOut &&
|
||||
image.layout == vk::ImageLayout::eTransferDstOptimal) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 128) {
|
||||
LOGF("GraphicsRenderColorImageBarrier: image=%p type=%d layout=%s -> %s\n",
|
||||
VulkanHandleToPointer(image.image), static_cast<int>(image.type),
|
||||
VulkanToString(image.layout).c_str(), VulkanToString(new_layout).c_str());
|
||||
}
|
||||
}
|
||||
vk::ImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask =
|
||||
vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead;
|
||||
image_memory_barrier.dstAccessMask =
|
||||
vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead;
|
||||
image_memory_barrier.oldLayout = image.layout;
|
||||
image_memory_barrier.newLayout = new_layout;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.image = image.image;
|
||||
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
image_memory_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_memory_barrier.subresourceRange.layerCount = image.layers;
|
||||
|
||||
const auto stages = static_cast<vk::PipelineStageFlags>(
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
|
||||
vk::PipelineStageFlagBits::eTransfer);
|
||||
vk_buffer.pipelineBarrier(stages, stages, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
|
||||
&image_memory_barrier);
|
||||
|
||||
image.layout = image_memory_barrier.newLayout;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsRenderDepthStencilImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
|
||||
vk::ImageLayout new_layout) {
|
||||
EXIT_IF(image.type != VulkanImageType::DepthStencil);
|
||||
if (new_layout == vk::ImageLayout::eUndefined ||
|
||||
new_layout == vk::ImageLayout::ePreinitialized) {
|
||||
EXIT("invalid destination depth/stencil image layout: %s\n",
|
||||
VulkanToString(new_layout).c_str());
|
||||
}
|
||||
if (image.layout == new_layout) {
|
||||
return;
|
||||
}
|
||||
vk::ImageMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
barrier.oldLayout = image.layout;
|
||||
barrier.newLayout = new_layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image.image;
|
||||
barrier.subresourceRange = {ImageViewOps::DepthAspectMask(image.format), 0,
|
||||
VK_REMAINING_MIP_LEVELS, 0, image.layers};
|
||||
const auto stages = static_cast<vk::PipelineStageFlags>(
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
|
||||
vk::PipelineStageFlagBits::eTransfer);
|
||||
vk_buffer.pipelineBarrier(stages, stages, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
|
||||
&barrier);
|
||||
image.layout = new_layout;
|
||||
}
|
||||
|
||||
void GraphicsRenderDepthStencilBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image) {
|
||||
EXIT_IF(image.type != VulkanImageType::DepthStencil);
|
||||
|
||||
auto& depth = static_cast<DepthStencilVulkanImage&>(image);
|
||||
if (depth.compressed) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF(
|
||||
"DepthTexture: decompressing depth target for shader read format=%s extent=%ux%u\n",
|
||||
VulkanToString(depth.format).c_str(), depth.extent.width, depth.extent.height);
|
||||
}
|
||||
depth.compressed = false;
|
||||
}
|
||||
|
||||
if (image.layout != vk::ImageLayout::eDepthStencilReadOnlyOptimal) {
|
||||
vk::ImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
image_memory_barrier.oldLayout = image.layout;
|
||||
image_memory_barrier.newLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.image = image.image;
|
||||
image_memory_barrier.subresourceRange.aspectMask =
|
||||
ImageViewOps::DepthAspectMask(image.format);
|
||||
image_memory_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_memory_barrier.subresourceRange.layerCount = image.layers;
|
||||
|
||||
vk_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
|
||||
vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
|
||||
|
||||
image.layout = image_memory_barrier.newLayout;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsRenderTextureBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
|
||||
auto vk_buffer = buffer.Handle();
|
||||
|
||||
auto* native = GetRenderContext().GetTextureCache().FindRenderTargetByRange(buffer, vaddr, size);
|
||||
if (native == nullptr) {
|
||||
EXIT("render-target barrier range has no cached image\n");
|
||||
}
|
||||
GraphicsRenderTextureBarrier(vk_buffer, *native);
|
||||
}
|
||||
|
||||
void GraphicsRenderDepthStencilBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
|
||||
Common::LockGuard lock(GetRenderContext().GetMutex());
|
||||
|
||||
auto vk_buffer = buffer.Handle();
|
||||
auto* native = GetRenderContext().GetTextureCache().FindDepthTargetByRange(buffer, vaddr, size);
|
||||
if (native == nullptr) {
|
||||
EXIT("depth-target barrier range has no cached image\n");
|
||||
}
|
||||
GraphicsRenderDepthStencilBarrier(vk_buffer, *native);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
|
||||
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct VulkanImage;
|
||||
|
||||
void GraphicsRenderTextureBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image);
|
||||
void GraphicsRenderColorImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
|
||||
vk::ImageLayout new_layout);
|
||||
void GraphicsRenderDepthStencilImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
|
||||
vk::ImageLayout new_layout);
|
||||
void GraphicsRenderDepthStencilBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
|
||||
@@ -7,6 +7,13 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
SamplerCache::~SamplerCache() {
|
||||
for (const auto& [key, sampler]: m_samplers) {
|
||||
(void)key;
|
||||
m_graphics.device.destroySampler(sampler, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
explicit SamplerCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
~SamplerCache() { KYTY_NOT_IMPLEMENTED; }
|
||||
~SamplerCache();
|
||||
KYTY_CLASS_NO_COPY(SamplerCache);
|
||||
|
||||
vk::Sampler GetSampler(const ShaderSamplerResource& r);
|
||||
|
||||
@@ -36,36 +36,8 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
|
||||
return barrier;
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read,
|
||||
bool written) {
|
||||
EXIT_IF(image.image == nullptr || image.type == VulkanImageType::DepthStencil);
|
||||
|
||||
vk::ImageMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryWrite;
|
||||
barrier.dstAccessMask = {};
|
||||
if (read) {
|
||||
barrier.dstAccessMask |= vk::AccessFlagBits::eShaderRead;
|
||||
}
|
||||
if (written) {
|
||||
barrier.dstAccessMask |= vk::AccessFlagBits::eShaderWrite;
|
||||
}
|
||||
barrier.oldLayout = image.layout;
|
||||
barrier.newLayout = vk::ImageLayout::eGeneral;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image.image;
|
||||
barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = image.layers;
|
||||
EXIT_IF(!barrier.dstAccessMask || barrier.subresourceRange.layerCount == 0);
|
||||
return barrier;
|
||||
}
|
||||
|
||||
vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer) {
|
||||
EXIT_IF(buffer.buffer == nullptr);
|
||||
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
EXIT_IF(buffer == nullptr);
|
||||
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
@@ -74,7 +46,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer) {
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = buffer.buffer;
|
||||
barrier.buffer = buffer;
|
||||
barrier.offset = 0;
|
||||
barrier.size = VK_WHOLE_SIZE;
|
||||
return barrier;
|
||||
|
||||
@@ -19,9 +19,7 @@ struct ShaderBufferWriteRange {
|
||||
|
||||
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
|
||||
VulkanMemoryBarrier MakeShaderWriteDependency();
|
||||
vk::ImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read,
|
||||
bool written);
|
||||
vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer);
|
||||
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
|
||||
std::vector<ShaderBufferWriteRange>
|
||||
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot& resources);
|
||||
|
||||
@@ -385,7 +385,9 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
return vk::BlendOp::eAdd;
|
||||
}
|
||||
|
||||
static void CreateLayout(std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
|
||||
static void CreateLayout(DescriptorCache& descriptor_cache,
|
||||
std::span<vk::DescriptorSetLayout> set_layouts,
|
||||
uint32_t& set_layouts_num,
|
||||
std::span<vk::PushConstantRange> push_constant_info,
|
||||
uint32_t& push_constant_info_num,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
@@ -405,17 +407,16 @@ static void CreateLayout(std::span<vk::DescriptorSetLayout> set_layouts, uint32_
|
||||
if (need_descriptor) {
|
||||
EXIT_IF(bindings.descriptor_set != set_layouts_num);
|
||||
|
||||
set_layouts[set_layouts_num] =
|
||||
GetRenderContext().GetDescriptorCache().GetDescriptorSetLayout(stage, program);
|
||||
set_layouts[set_layouts_num] = descriptor_cache.GetDescriptorSetLayout(stage, program);
|
||||
set_layouts_num++;
|
||||
}
|
||||
}
|
||||
|
||||
static void ConfigureSubgroupSize(vk::ShaderStageFlagBits vk_stage,
|
||||
static void ConfigureSubgroupSize(const GraphicContext& graphics,
|
||||
vk::ShaderStageFlagBits vk_stage,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
|
||||
vk::PipelineShaderStageCreateInfo& stage) {
|
||||
const auto& graphics = GetRenderContext().GetGraphics();
|
||||
const auto config =
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {graphics}, vk_stage, program);
|
||||
switch (config.mode) {
|
||||
@@ -455,7 +456,9 @@ static void ConfigureSubgroupSize(vk::ShaderStageFlagBits
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::RenderPass render_pass,
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
@@ -463,11 +466,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
|
||||
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
|
||||
bool ps_active) {
|
||||
EXIT_IF(render_pass == nullptr);
|
||||
EXIT_IF(ps_active && ps_input_info == nullptr);
|
||||
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
|
||||
vk::ShaderModule vert_shader_module = nullptr;
|
||||
vk::ShaderModule frag_shader_module = nullptr;
|
||||
|
||||
@@ -511,7 +511,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
vert_shader_stage_info.pName = "main";
|
||||
vert_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex,
|
||||
*vs_input_info.stage.program,
|
||||
vert_subgroup_size, vert_shader_stage_info);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
|
||||
@@ -525,7 +526,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
frag_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eFragment, *ps_input_info->stage.program,
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eFragment,
|
||||
*ps_input_info->stage.program,
|
||||
frag_subgroup_size, frag_shader_stage_info);
|
||||
}
|
||||
|
||||
@@ -620,10 +622,13 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
|
||||
auto expected =
|
||||
(attr_size == 1 ? DstSel(4, 0, 0)
|
||||
: (attr_size == 2 ? DstSel(4, 5, 0) : DstSel(4, 5, 6)));
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
|
||||
auto expected = DstSel(4, 5, 6);
|
||||
switch (attr_size) {
|
||||
case 1: expected = DstSel(4, 0, 0); break;
|
||||
case 2: expected = DstSel(4, 5, 0); break;
|
||||
default: break;
|
||||
}
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
@@ -724,7 +729,13 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizer {};
|
||||
rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
|
||||
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
|
||||
#if defined(__APPLE__)
|
||||
rasterizer.pNext = nullptr;
|
||||
#else
|
||||
rasterizer.pNext = &clip_ext;
|
||||
#endif
|
||||
rasterizer.flags = {};
|
||||
rasterizer.depthClampEnable = VK_FALSE;
|
||||
rasterizer.rasterizerDiscardEnable = VK_FALSE;
|
||||
@@ -802,7 +813,13 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo color_blending {};
|
||||
color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_color_write_enable; drop the dynamic color-write struct on macOS
|
||||
// and rely on each attachment's static colorWriteMask (all channels enabled by default).
|
||||
#if defined(__APPLE__)
|
||||
color_blending.pNext = nullptr;
|
||||
#else
|
||||
color_blending.pNext = &color_write;
|
||||
#endif
|
||||
color_blending.flags = {};
|
||||
color_blending.logicOpEnable = VK_FALSE;
|
||||
color_blending.logicOp = vk::LogicOp::eCopy;
|
||||
@@ -820,12 +837,14 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
uint32_t push_constant_info_num = 0;
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*vs_input_info.stage.program, vk::ShaderStageFlagBits::eVertex,
|
||||
DescriptorCache::Stage::Vertex);
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*ps_input_info->stage.program, vk::ShaderStageFlagBits::eFragment,
|
||||
DescriptorCache::Stage::Pixel);
|
||||
}
|
||||
@@ -866,7 +885,11 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
depth_stencil_info.depthWriteEnable = (static_params.depth_write_enable ? VK_TRUE : VK_FALSE);
|
||||
depth_stencil_info.depthCompareOp = static_params.depth_compare_op;
|
||||
depth_stencil_info.depthBoundsTestEnable =
|
||||
#if defined(__APPLE__)
|
||||
VK_FALSE; // MoltenVK lacks the depthBounds feature; depth-bounds testing is disabled
|
||||
#else
|
||||
(static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
|
||||
#endif
|
||||
depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
|
||||
depth_stencil_info.front.failOp = static_params.stencil_front.failOp;
|
||||
depth_stencil_info.front.passOp = static_params.stencil_front.passOp;
|
||||
@@ -886,7 +909,9 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
vk::DynamicState::eStencilCompareMask,
|
||||
vk::DynamicState::eStencilReference,
|
||||
vk::DynamicState::eStencilWriteMask,
|
||||
vk::DynamicState::eColorWriteEnableEXT,
|
||||
#if !defined(__APPLE__)
|
||||
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
|
||||
#endif
|
||||
};
|
||||
const auto dynamic_states_count =
|
||||
static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));
|
||||
@@ -899,8 +924,14 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
dynamic_state.pDynamicStates = dynamic_states;
|
||||
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info {};
|
||||
vk::PipelineRenderingCreateInfo rendering_info {};
|
||||
rendering_info.sType = vk::StructureType::ePipelineRenderingCreateInfo;
|
||||
rendering_info.colorAttachmentCount = rendering.color_count;
|
||||
rendering_info.pColorAttachmentFormats = rendering.color_formats.data();
|
||||
rendering_info.depthAttachmentFormat = rendering.depth_format;
|
||||
rendering_info.stencilAttachmentFormat = rendering.stencil_format;
|
||||
pipeline_info.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
||||
pipeline_info.pNext = nullptr;
|
||||
pipeline_info.pNext = &rendering_info;
|
||||
pipeline_info.flags = {};
|
||||
pipeline_info.stageCount = shader_stage_count;
|
||||
pipeline_info.pStages = shader_stages;
|
||||
@@ -914,7 +945,7 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
pipeline_info.pColorBlendState = &color_blending;
|
||||
pipeline_info.pDynamicState = &dynamic_state;
|
||||
pipeline_info.layout = pipeline.pipeline_layout;
|
||||
pipeline_info.renderPass = render_pass;
|
||||
pipeline_info.renderPass = nullptr;
|
||||
pipeline_info.subpass = 0;
|
||||
pipeline_info.basePipelineHandle = nullptr;
|
||||
pipeline_info.basePipelineIndex = -1;
|
||||
@@ -949,11 +980,10 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
std::span<const uint32_t> cs_shader) {
|
||||
auto& graphics = GetRenderContext().GetGraphics();
|
||||
|
||||
vk::ShaderModule comp_shader_module = nullptr;
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
@@ -982,7 +1012,8 @@ void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
|
||||
comp_shader_stage_info.pName = "main";
|
||||
comp_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!input_info.stage);
|
||||
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute,
|
||||
*input_info.stage.program,
|
||||
comp_subgroup_size, comp_shader_stage_info);
|
||||
|
||||
vk::DescriptorSetLayout set_layouts[1] = {};
|
||||
@@ -992,7 +1023,8 @@ void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
|
||||
uint32_t push_constant_info_num = 0;
|
||||
|
||||
EXIT_IF(!input_info.stage);
|
||||
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*input_info.stage.program, vk::ShaderStageFlagBits::eCompute,
|
||||
DescriptorCache::Stage::Compute);
|
||||
|
||||
|
||||
@@ -1,95 +1,360 @@
|
||||
#include "graphics/host_gpu/renderer/streamBuffer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
std::atomic<uint64_t> g_stream_buffer_count {0};
|
||||
} // namespace
|
||||
|
||||
HostStreamBuffer::HostStreamBuffer(GraphicContext& graphics): m_graphics(graphics) {}
|
||||
constexpr size_t WATCHES_INITIAL_RESERVE = 0x4000;
|
||||
constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
|
||||
|
||||
HostStreamBuffer::~HostStreamBuffer() {
|
||||
Release();
|
||||
[[nodiscard]] VmaAllocationCreateFlags AllocationFlags(MemoryUsage usage) {
|
||||
switch (usage) {
|
||||
case MemoryUsage::Upload:
|
||||
case MemoryUsage::Stream:
|
||||
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
case MemoryUsage::Download:
|
||||
return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
||||
case MemoryUsage::DeviceLocal: return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void HostStreamBuffer::EnsureBuffer() {
|
||||
if (m_buffer != nullptr) {
|
||||
if (m_mapped == nullptr) {
|
||||
EXIT("host stream buffer lost its mapped storage\n");
|
||||
}
|
||||
return;
|
||||
[[nodiscard]] VmaMemoryUsage AllocationUsage(MemoryUsage usage) {
|
||||
switch (usage) {
|
||||
case MemoryUsage::DeviceLocal:
|
||||
case MemoryUsage::Stream: return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
case MemoryUsage::Upload:
|
||||
case MemoryUsage::Download: return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
||||
}
|
||||
KYTY_PROFILER_BLOCK("HostStreamBuffer::create");
|
||||
m_buffer = std::make_unique<VulkanBuffer>();
|
||||
m_buffer->usage = vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eVertexBuffer |
|
||||
vk::BufferUsageFlagBits::eIndexBuffer |
|
||||
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst;
|
||||
m_buffer->memory.property = vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent |
|
||||
vk::MemoryPropertyFlagBits::eHostCached;
|
||||
m_graphics.CreateBuffer(CAPACITY, *m_buffer);
|
||||
m_graphics.MapMemory(m_buffer->memory, m_mapped);
|
||||
const auto count = g_stream_buffer_count.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
LOGF("HostStreamBuffer: created serial=%" PRIu64 " capacity=%" PRIu64 "\n", count, CAPACITY);
|
||||
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
}
|
||||
|
||||
bool HostStreamBuffer::Copy(const void* src, uint64_t size, uint64_t alignment,
|
||||
VulkanBuffer*& out_buffer, uint64_t& out_offset, uint64_t& out_range) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
if (src == nullptr) {
|
||||
EXIT("invalid host stream upload\n");
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool AlignUp(uint64_t value, uint64_t alignment, uint64_t& result) {
|
||||
if (alignment == 0) {
|
||||
alignment = 1;
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
if ((alignment & (alignment - 1)) != 0 || m_offset > UINT64_MAX - (alignment - 1)) {
|
||||
const auto remainder = value % alignment;
|
||||
if (remainder == 0) {
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
const auto increment = alignment - remainder;
|
||||
if (value > std::numeric_limits<uint64_t>::max() - increment) {
|
||||
return false;
|
||||
}
|
||||
const auto offset = (m_offset + alignment - 1) & ~(alignment - 1);
|
||||
if (offset > CAPACITY || size > CAPACITY - offset) {
|
||||
return false;
|
||||
}
|
||||
EnsureBuffer();
|
||||
std::memcpy(static_cast<uint8_t*>(m_mapped) + offset, src, static_cast<size_t>(size));
|
||||
m_offset = offset + size;
|
||||
out_buffer = m_buffer.get();
|
||||
out_offset = offset;
|
||||
out_range = size;
|
||||
result = value + increment;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HostStreamBuffer::Reset() noexcept {
|
||||
std::lock_guard lock(m_mutex);
|
||||
m_offset = 0;
|
||||
} // namespace
|
||||
|
||||
Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
||||
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size)
|
||||
: m_graphics(&graphics), m_scheduler(&scheduler), m_usage(usage), m_cpu_address(cpu_address),
|
||||
m_size(size), m_buffer(std::make_unique<VulkanBuffer>()) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(graphics.allocator == nullptr || size == 0);
|
||||
|
||||
vk::BufferCreateInfo buffer_info {};
|
||||
buffer_info.size = size;
|
||||
buffer_info.usage = flags;
|
||||
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
VmaAllocationCreateInfo allocation_info {};
|
||||
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
|
||||
allocation_info.usage = AllocationUsage(usage);
|
||||
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
|
||||
? VkMemoryPropertyFlags {}
|
||||
: VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
|
||||
VmaAllocationInfo allocation_result {};
|
||||
VkBuffer native_buffer = VK_NULL_HANDLE;
|
||||
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
|
||||
graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer_info), &allocation_info,
|
||||
&native_buffer, &m_buffer->memory.allocation, &allocation_result));
|
||||
if (result != vk::Result::eSuccess) {
|
||||
graphics.LogMemoryBudget();
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
m_buffer->buffer = native_buffer;
|
||||
m_buffer->usage = flags;
|
||||
m_buffer->buffer_size = size;
|
||||
m_buffer->memory.allocation_info = allocation_result;
|
||||
m_buffer->memory.memory = allocation_result.deviceMemory;
|
||||
m_buffer->memory.offset = allocation_result.offset;
|
||||
m_buffer->memory.type = allocation_result.memoryType;
|
||||
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
|
||||
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
|
||||
|
||||
VkMemoryPropertyFlags properties = 0;
|
||||
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
|
||||
m_buffer->memory.property = vk::MemoryPropertyFlags(properties);
|
||||
m_is_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
if (allocation_result.pMappedData != nullptr) {
|
||||
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
|
||||
static_cast<size_t>(size)};
|
||||
}
|
||||
VulkanTrackAllocation(m_buffer->memory);
|
||||
}
|
||||
|
||||
void HostStreamBuffer::Release() noexcept {
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (m_buffer != nullptr) {
|
||||
if (m_mapped == nullptr) {
|
||||
EXIT("invalid host stream buffer release state\n");
|
||||
}
|
||||
m_graphics.UnmapMemory(m_buffer->memory);
|
||||
m_graphics.DeleteBuffer(*m_buffer);
|
||||
m_buffer.reset();
|
||||
Buffer::~Buffer() {
|
||||
if (m_buffer->buffer != nullptr) {
|
||||
VulkanUntrackAllocation(m_buffer->memory);
|
||||
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
|
||||
}
|
||||
m_mapped = nullptr;
|
||||
m_offset = 0;
|
||||
}
|
||||
|
||||
vk::Buffer Buffer::Handle() const noexcept {
|
||||
return m_buffer->buffer;
|
||||
}
|
||||
|
||||
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
|
||||
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
|
||||
}
|
||||
|
||||
void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
|
||||
EXIT_IF(source == nullptr || m_mapped.empty() || offset > m_size || size > m_size - offset);
|
||||
std::memcpy(m_mapped.data() + offset, source, static_cast<size_t>(size));
|
||||
Flush(offset, size);
|
||||
}
|
||||
|
||||
void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
|
||||
if (!m_is_coherent && size != 0) {
|
||||
const auto result = vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation,
|
||||
offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
|
||||
vk::AccessFlags destination) const {
|
||||
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
|
||||
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " capacity=0x%016" PRIx64 "\n",
|
||||
static_cast<const void*>(Handle()), offset, size, m_size);
|
||||
}
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barrier.srcAccessMask = source;
|
||||
barrier.dstAccessMask = destination;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = Handle();
|
||||
barrier.offset = offset;
|
||||
barrier.size = size;
|
||||
return barrier;
|
||||
}
|
||||
|
||||
void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
||||
uint64_t destination_offset, uint64_t size, vk::AccessFlags source_before,
|
||||
vk::AccessFlags destination_before, vk::AccessFlags source_after,
|
||||
vk::AccessFlags destination_after) {
|
||||
if (size == 0 || source_offset > source.m_size || size > source.m_size - source_offset ||
|
||||
destination_offset > m_size || size > m_size - destination_offset) {
|
||||
EXIT("Buffer: invalid copy range\n");
|
||||
}
|
||||
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
|
||||
destination_offset < source_offset + size) {
|
||||
EXIT("Buffer: overlapping self-copy\n");
|
||||
}
|
||||
command.EndRendering();
|
||||
const vk::BufferMemoryBarrier before[] = {
|
||||
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
|
||||
Barrier(destination_offset, size, destination_before,
|
||||
vk::AccessFlagBits::eTransferWrite),
|
||||
};
|
||||
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
|
||||
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
|
||||
if (static_cast<bool>((source_before | destination_before) & host_access)) {
|
||||
before_stage |= vk::PipelineStageFlagBits::eHost;
|
||||
}
|
||||
const auto native = command.Handle();
|
||||
native.pipelineBarrier(before_stage, vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr);
|
||||
const vk::BufferCopy copy {source_offset, destination_offset, size};
|
||||
native.copyBuffer(source.Handle(), Handle(), 1, ©);
|
||||
const vk::BufferMemoryBarrier after[] = {
|
||||
source.Barrier(source_offset, size, vk::AccessFlagBits::eTransferRead, source_after),
|
||||
Barrier(destination_offset, size, vk::AccessFlagBits::eTransferWrite, destination_after),
|
||||
};
|
||||
auto after_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
|
||||
if (static_cast<bool>((source_after | destination_after) & host_access)) {
|
||||
after_stage |= vk::PipelineStageFlagBits::eHost;
|
||||
}
|
||||
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, after_stage,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, after, 0, nullptr);
|
||||
}
|
||||
|
||||
void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
|
||||
if (((offset | size) & 3u) != 0) {
|
||||
EXIT("Buffer: fill range must be dword aligned\n");
|
||||
}
|
||||
auto& command = Scheduler().Current();
|
||||
command.EndRendering();
|
||||
const auto before =
|
||||
Barrier(offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlagBits::eTransferWrite);
|
||||
const auto native = command.Handle();
|
||||
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
|
||||
0, nullptr, 1, &before, 0, nullptr);
|
||||
native.fillBuffer(Handle(), offset, size, value);
|
||||
const auto after =
|
||||
Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
||||
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &after, 0, nullptr);
|
||||
}
|
||||
|
||||
StreamBuffer::StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
||||
uint64_t size)
|
||||
: Buffer(graphics, scheduler, usage, 0, AllFlags, size) {
|
||||
ReserveWatches(m_current_watches, WATCHES_INITIAL_RESERVE);
|
||||
ReserveWatches(m_previous_watches, WATCHES_INITIAL_RESERVE);
|
||||
}
|
||||
|
||||
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
|
||||
uint64_t& alignment) {
|
||||
if (coherent) {
|
||||
return true;
|
||||
}
|
||||
if (!AlignUp(size, atom, size)) {
|
||||
return false;
|
||||
}
|
||||
const auto divisor = std::gcd(alignment, atom);
|
||||
if (alignment != 0 && alignment / divisor > UINT64_MAX / atom) {
|
||||
return false;
|
||||
}
|
||||
alignment = alignment == 0 ? atom : alignment / divisor * atom;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignment,
|
||||
bool allow_wait) {
|
||||
if (Mapped().empty()) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
uint64_t mapped_size = size;
|
||||
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
|
||||
if (!NormalizeReservation(IsCoherent(), atom, mapped_size, alignment)) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
if (mapped_size > Size()) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
|
||||
uint64_t aligned_offset = 0;
|
||||
if (!AlignUp(m_offset, alignment, aligned_offset)) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
|
||||
const bool wrap = aligned_offset > Size() - mapped_size;
|
||||
if (wrap) {
|
||||
aligned_offset = 0;
|
||||
}
|
||||
|
||||
auto wait_cursor = wrap ? size_t {0} : m_wait_cursor;
|
||||
auto wait_bound = wrap ? uint64_t {0} : m_wait_bound;
|
||||
auto invalidation_mark =
|
||||
wrap ? std::optional<size_t> {m_current_watch_cursor} : m_invalidation_mark;
|
||||
auto& pending_watches = wrap ? m_current_watches : m_previous_watches;
|
||||
if (!WaitPendingOperations(pending_watches, invalidation_mark, aligned_offset + mapped_size,
|
||||
allow_wait, wait_cursor, wait_bound)) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
|
||||
if (wrap) {
|
||||
m_invalidation_mark = invalidation_mark;
|
||||
m_current_watch_cursor = 0;
|
||||
std::swap(m_previous_watches, m_current_watches);
|
||||
}
|
||||
m_wait_cursor = wait_cursor;
|
||||
m_wait_bound = wait_bound;
|
||||
m_offset = aligned_offset;
|
||||
m_mapped_size = mapped_size;
|
||||
return {Mapped().data() + m_offset, m_offset};
|
||||
}
|
||||
|
||||
void StreamBuffer::Commit() {
|
||||
if (!IsCoherent() && Usage() != MemoryUsage::Download && m_mapped_size != 0) {
|
||||
const auto result = vmaFlushAllocation(
|
||||
Graphics().allocator, NativeBuffer().memory.allocation, m_offset, m_mapped_size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
m_offset += m_mapped_size;
|
||||
const auto tick = Scheduler().CurrentTick();
|
||||
if (m_current_watch_cursor != 0 && m_current_watches[m_current_watch_cursor - 1].tick == tick) {
|
||||
m_current_watches[m_current_watch_cursor - 1].upper_bound = m_offset;
|
||||
return;
|
||||
}
|
||||
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
|
||||
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
|
||||
}
|
||||
auto& watch = m_current_watches[m_current_watch_cursor++];
|
||||
watch.upper_bound = m_offset;
|
||||
watch.tick = tick;
|
||||
}
|
||||
|
||||
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
|
||||
if (IsCoherent() || size == 0) {
|
||||
return;
|
||||
}
|
||||
const auto result = vmaInvalidateAllocation(Graphics().allocator,
|
||||
NativeBuffer().memory.allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
|
||||
EXIT_IF(source == nullptr);
|
||||
const auto [data, offset] = Map(size, alignment);
|
||||
EXIT_IF(data == nullptr);
|
||||
std::memcpy(data, source, static_cast<size_t>(size));
|
||||
Commit();
|
||||
return offset;
|
||||
}
|
||||
|
||||
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, size_t grow_size) {
|
||||
watches.resize(watches.size() + grow_size);
|
||||
}
|
||||
|
||||
bool StreamBuffer::WaitPendingOperations(const std::vector<Watch>& watches,
|
||||
std::optional<size_t> invalidation_mark,
|
||||
uint64_t requested_upper_bound, bool allow_wait,
|
||||
size_t& wait_cursor, uint64_t& wait_bound) {
|
||||
if (!invalidation_mark.has_value()) {
|
||||
return true;
|
||||
}
|
||||
while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) {
|
||||
const auto& watch = watches[wait_cursor];
|
||||
if (!Scheduler().IsFree(watch.tick) && !allow_wait) {
|
||||
return false;
|
||||
}
|
||||
Scheduler().Wait(watch.tick);
|
||||
if (Usage() == MemoryUsage::Download) {
|
||||
Scheduler().WaitPriorityOperations(watch.tick);
|
||||
}
|
||||
wait_bound = watch.upper_bound;
|
||||
++wait_cursor;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -3,43 +3,126 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
class CommandScheduler;
|
||||
struct StreamBufferTestAccess;
|
||||
struct GraphicContext;
|
||||
struct VulkanBuffer;
|
||||
|
||||
// Host-only mapped stream mechanism. BufferCache owns the upload policy/API; one backend remains
|
||||
// attached to each CommandBuffer so Reset is provably after that command's fence. Replacing this
|
||||
// backend with scheduler watches will not change descriptor/render call sites.
|
||||
class HostStreamBuffer final {
|
||||
public:
|
||||
explicit HostStreamBuffer(GraphicContext& graphics);
|
||||
~HostStreamBuffer();
|
||||
KYTY_CLASS_NO_COPY(HostStreamBuffer);
|
||||
enum class MemoryUsage : uint8_t {
|
||||
DeviceLocal,
|
||||
Upload,
|
||||
Download,
|
||||
Stream,
|
||||
};
|
||||
|
||||
[[nodiscard]] bool Copy(const void* src, uint64_t size, uint64_t alignment,
|
||||
VulkanBuffer*& out_buffer, uint64_t& out_offset, uint64_t& out_range);
|
||||
void Reset() noexcept;
|
||||
void Release() noexcept;
|
||||
inline constexpr vk::BufferUsageFlags ReadFlags =
|
||||
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eUniformBuffer |
|
||||
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer |
|
||||
vk::BufferUsageFlagBits::eIndirectBuffer;
|
||||
|
||||
inline constexpr vk::BufferUsageFlags AllFlags =
|
||||
ReadFlags | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer;
|
||||
|
||||
class Buffer {
|
||||
public:
|
||||
Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
||||
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size);
|
||||
~Buffer();
|
||||
KYTY_CLASS_NO_COPY(Buffer);
|
||||
|
||||
[[nodiscard]] vk::Buffer Handle() const noexcept;
|
||||
[[nodiscard]] uint64_t Size() const noexcept { return m_size; }
|
||||
[[nodiscard]] std::span<uint8_t> Mapped() const noexcept { return m_mapped; }
|
||||
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
|
||||
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
|
||||
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
|
||||
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
|
||||
return address - m_cpu_address;
|
||||
}
|
||||
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
|
||||
void Write(uint64_t offset, const void* source, uint64_t size);
|
||||
void Flush(uint64_t offset, uint64_t size);
|
||||
void CopyFrom(
|
||||
CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
||||
uint64_t destination_offset, uint64_t size,
|
||||
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_before =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags source_after =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_after =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
||||
void Fill(uint64_t offset, uint64_t size, uint32_t value);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
|
||||
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
|
||||
[[nodiscard]] VulkanBuffer& NativeBuffer() noexcept { return *m_buffer; }
|
||||
|
||||
private:
|
||||
void EnsureBuffer();
|
||||
[[nodiscard]] vk::BufferMemoryBarrier Barrier(uint64_t offset, uint64_t size,
|
||||
vk::AccessFlags source,
|
||||
vk::AccessFlags destination) const;
|
||||
|
||||
// Interim fence-scoped backend: one command processor's eight live command buffers reserve up
|
||||
// to 128 MiB lazily. Additional active processors scale that aggregate until this backend is
|
||||
// replaced by a single 64 MiB scheduler-watched ring; the BufferCache seam stays fixed.
|
||||
static constexpr uint64_t CAPACITY = 16ull * 1024ull * 1024ull;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
GraphicContext& m_graphics;
|
||||
GraphicContext* m_graphics = nullptr;
|
||||
CommandScheduler* m_scheduler = nullptr;
|
||||
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
|
||||
uint64_t m_cpu_address = 0;
|
||||
uint64_t m_size = 0;
|
||||
std::unique_ptr<VulkanBuffer> m_buffer;
|
||||
void* m_mapped = nullptr;
|
||||
uint64_t m_offset = 0;
|
||||
std::span<uint8_t> m_mapped;
|
||||
bool m_is_coherent = false;
|
||||
};
|
||||
|
||||
class StreamBuffer final: public Buffer {
|
||||
public:
|
||||
StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
||||
uint64_t size);
|
||||
|
||||
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
|
||||
bool allow_wait = true);
|
||||
void Commit();
|
||||
// Download mappings become visible to the CPU only after their GPU completion tick is free.
|
||||
// Call this from the scheduler's deferred completion operation before reading Mapped().
|
||||
void Invalidate(uint64_t offset, uint64_t size);
|
||||
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
|
||||
|
||||
private:
|
||||
friend struct StreamBufferTestAccess;
|
||||
|
||||
struct Watch {
|
||||
uint64_t tick = 0;
|
||||
uint64_t upper_bound = 0;
|
||||
};
|
||||
|
||||
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
|
||||
[[nodiscard]] static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
|
||||
uint64_t& alignment);
|
||||
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
|
||||
std::optional<size_t> invalidation_mark,
|
||||
uint64_t requested_upper_bound, bool allow_wait,
|
||||
size_t& wait_cursor, uint64_t& wait_bound);
|
||||
|
||||
uint64_t m_offset = 0;
|
||||
uint64_t m_mapped_size = 0;
|
||||
std::vector<Watch> m_current_watches;
|
||||
size_t m_current_watch_cursor = 0;
|
||||
std::optional<size_t> m_invalidation_mark;
|
||||
std::vector<Watch> m_previous_watches;
|
||||
size_t m_wait_cursor = 0;
|
||||
uint64_t m_wait_bound = 0;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/label.h"
|
||||
#include "graphics/host_gpu/renderer/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
@@ -57,19 +57,6 @@ uint64_t ReadReferenceClock() {
|
||||
return value;
|
||||
}
|
||||
|
||||
static void SubmitLabel(CommandBuffer& buffer, LabelCallback callback_1 = nullptr,
|
||||
LabelCallback callback_2 = nullptr, const uint64_t* args = nullptr) {
|
||||
auto* label = LabelCreate(callback_1, callback_2, args);
|
||||
LabelSet(buffer, *label);
|
||||
LabelDelete(*label);
|
||||
}
|
||||
|
||||
static bool CompleteDisplayBufferFlip(const uint64_t* args) {
|
||||
EXIT_IF(args == nullptr);
|
||||
Presentation::DisplayBufferCompleteFlipFromGpu(args[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class EndOfPipeCompletion { None, Interrupt, Flip, FlipAndInterrupt };
|
||||
|
||||
struct EndOfPipeSignal {
|
||||
@@ -86,17 +73,6 @@ struct EndOfPipeSignal {
|
||||
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
|
||||
enum class EndOfPipeWriteAction { Write, WriteBack, Interrupt, InterruptWriteBack };
|
||||
|
||||
static bool TriggerEopEventCallback(const uint64_t* args) {
|
||||
EXIT_IF(args == nullptr);
|
||||
GetRenderContext().TriggerEopEvent(static_cast<uint32_t>(args[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TriggerDefaultEopEventCallback(const uint64_t* /*args*/) {
|
||||
GetRenderContext().TriggerEopEvent(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ValidateEndOfPipeSignal(const EndOfPipeSignal& signal) {
|
||||
if (signal.destination.has_value()) {
|
||||
EXIT_IF(*signal.destination == 0);
|
||||
@@ -111,19 +87,33 @@ static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
|
||||
signal.debug_args[0], signal.debug_args[1], signal.debug_args[2],
|
||||
signal.debug_args[3], signal.debug_data);
|
||||
|
||||
const uint64_t args[LABEL_ARGS_MAX] = {signal.completion_data};
|
||||
auto& renderer = signal.buffer->GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
if (signal.completion != EndOfPipeCompletion::None) {
|
||||
EXIT_IF(!scheduler.Active() || signal.buffer != &scheduler.Current());
|
||||
}
|
||||
switch (signal.completion) {
|
||||
case EndOfPipeCompletion::None: return;
|
||||
case EndOfPipeCompletion::Interrupt:
|
||||
SubmitLabel(*signal.buffer, nullptr, TriggerEopEventCallback, args);
|
||||
case EndOfPipeCompletion::Interrupt: {
|
||||
const auto context_id = static_cast<uint32_t>(signal.completion_data);
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
|
||||
return;
|
||||
case EndOfPipeCompletion::Flip:
|
||||
SubmitLabel(*signal.buffer, CompleteDisplayBufferFlip, nullptr, args);
|
||||
}
|
||||
case EndOfPipeCompletion::Flip: {
|
||||
const auto request_id = signal.completion_data;
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
|
||||
return;
|
||||
case EndOfPipeCompletion::FlipAndInterrupt:
|
||||
SubmitLabel(*signal.buffer, CompleteDisplayBufferFlip, TriggerDefaultEopEventCallback,
|
||||
args);
|
||||
}
|
||||
case EndOfPipeCompletion::FlipAndInterrupt: {
|
||||
const auto request_id = signal.completion_data;
|
||||
scheduler.DeferPriorityOperation([&renderer, request_id] {
|
||||
renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
renderer.TriggerEopEvent(0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +162,6 @@ void TriggerAgcUserInterrupt() {
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
}
|
||||
|
||||
void TriggerEopEvent(uint32_t context_id) {
|
||||
GetRenderContext().TriggerEopEvent(context_id);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
@@ -261,11 +247,12 @@ void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, context_id);
|
||||
}
|
||||
|
||||
uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg) {
|
||||
uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg) {
|
||||
for (;;) {
|
||||
uint64_t request_id = 0;
|
||||
const auto result = Presentation::DisplayBufferSubmitFlipFromGpu(
|
||||
auto& video_out = buffer.GetContext().GetVideoOut();
|
||||
const auto result = video_out.SubmitFlipFromGpu(
|
||||
buffer, handle, index, flip_mode, flip_arg, request_id);
|
||||
if (result == OK) {
|
||||
EXIT_IF(request_id == 0);
|
||||
@@ -276,7 +263,7 @@ uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index,
|
||||
"\n",
|
||||
result, handle, index, flip_mode, flip_arg);
|
||||
}
|
||||
Presentation::DisplayBufferWaitForFlipQueueSlot();
|
||||
video_out.WaitForSubmitSlot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,9 +318,11 @@ void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int han
|
||||
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id) {
|
||||
ValidateEndOfPipeSignal({.buffer = &buffer});
|
||||
|
||||
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(context_id)};
|
||||
SubmitLabel(buffer, nullptr, TriggerEopEventCallback, args);
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
|
||||
}
|
||||
|
||||
static void EopEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
|
||||
@@ -349,7 +338,9 @@ static void EopEventDeleteFunc(LibKernel::EventQueue::KernelEqueue eq,
|
||||
EXIT_NOT_IMPLEMENTED(event->event.filter != LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
|
||||
if (event->event.ident == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT ||
|
||||
event->event.ident == GRAPHICS_EVENT_EOP) {
|
||||
GetRenderContext().DeleteEopEq(eq, static_cast<int>(event->event.ident));
|
||||
auto* renderer = static_cast<RenderContext*>(event->filter.data);
|
||||
EXIT_IF(renderer == nullptr);
|
||||
renderer->DeleteEopEq(eq, static_cast<int>(event->event.ident));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +359,8 @@ static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
|
||||
}
|
||||
}
|
||||
|
||||
int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) {
|
||||
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
|
||||
void* udata) {
|
||||
LibKernel::EventQueue::KernelEqueueEvent event;
|
||||
event.triggered = false;
|
||||
event.event.ident = static_cast<uintptr_t>(id);
|
||||
@@ -379,13 +371,13 @@ int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) {
|
||||
event.filter.delete_event_func = EopEventDeleteFunc;
|
||||
event.filter.reset_func = EopEventResetFunc;
|
||||
event.filter.trigger_func = EopEventTriggerFunc;
|
||||
event.filter.data = nullptr;
|
||||
event.filter.data = &renderer;
|
||||
|
||||
int result = LibKernel::EventQueue::KernelAddEvent(eq, event);
|
||||
|
||||
if (result == 0 &&
|
||||
(id == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT || id == GRAPHICS_EVENT_EOP)) {
|
||||
GetRenderContext().AddEopEq(eq, id);
|
||||
renderer.AddEopEq(eq, id);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -398,12 +390,12 @@ int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void ReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
|
||||
GetRenderContext().GetGdsBuffer().Read(dst, dw_offset, dw_size);
|
||||
}
|
||||
|
||||
void DeleteBuffers() {
|
||||
GetRenderContext().GetBufferCache().ResetNullBuffer();
|
||||
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
|
||||
const auto offset = uint64_t {dw_offset} * sizeof(uint32_t);
|
||||
const auto size = uint64_t {dw_size} * sizeof(uint32_t);
|
||||
EXIT_IF(dst == nullptr || offset > gds.Size() || size > gds.Size() - offset ||
|
||||
gds.Mapped().empty());
|
||||
std::memcpy(dst, gds.Mapped().data() + offset, static_cast<size_t>(size));
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::Sync
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
class Buffer;
|
||||
class RenderContext;
|
||||
|
||||
namespace Sync {
|
||||
|
||||
@@ -16,7 +18,6 @@ namespace Sync {
|
||||
[[nodiscard]] uint64_t ReadReferenceClock();
|
||||
|
||||
void TriggerAgcUserInterrupt();
|
||||
void TriggerEopEvent(uint32_t context_id);
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id);
|
||||
|
||||
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
@@ -46,8 +47,8 @@ void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer&
|
||||
uint64_t* dst_gpu_addr, uint64_t value,
|
||||
uint32_t context_id = 0);
|
||||
|
||||
[[nodiscard]] uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index,
|
||||
int flip_mode, int64_t flip_arg);
|
||||
[[nodiscard]] uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index,
|
||||
int flip_mode, int64_t flip_arg);
|
||||
void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int handle, int index,
|
||||
int flip_mode, int64_t flip_arg, uint64_t request_id);
|
||||
void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
@@ -58,10 +59,10 @@ void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuf
|
||||
int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id);
|
||||
|
||||
int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata);
|
||||
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
|
||||
void* udata);
|
||||
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
void ReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
|
||||
void DeleteBuffers();
|
||||
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
|
||||
|
||||
} // namespace Sync
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,173 +3,184 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/lruCache.h"
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
#include "graphics/host_gpu/renderer/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/blitHelper.h"
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
#include "graphics/host_gpu/renderer/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/tiler.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <compare>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct DepthStencilVulkanImage;
|
||||
struct GpuTextureVulkanImage;
|
||||
struct GraphicContext;
|
||||
struct ImageViewInfo;
|
||||
struct RenderTextureVulkanImage;
|
||||
struct StorageTextureVulkanImage;
|
||||
struct VideoOutVulkanImage;
|
||||
struct VulkanImage;
|
||||
struct VulkanMemory;
|
||||
class Buffer;
|
||||
class BufferCache;
|
||||
class CommandBuffer;
|
||||
class DummyTextureCache;
|
||||
class CommandScheduler;
|
||||
class ResourceMutex;
|
||||
|
||||
[[nodiscard]] bool IsExactRenderTargetMipStorage(const ImageInfo& sampled, const ImageInfo& storage,
|
||||
vk::Format sampled_view_format,
|
||||
vk::Format storage_image_format) noexcept;
|
||||
class RenderExecutor;
|
||||
class StreamBuffer;
|
||||
class TileManager;
|
||||
struct TextureCacheTestAccess;
|
||||
|
||||
class TextureCache {
|
||||
public:
|
||||
struct RegionInfo {
|
||||
bool image_pages = false;
|
||||
bool image_bytes = false;
|
||||
bool gpu_image_bytes = false;
|
||||
bool non_sampled_pages = false;
|
||||
bool metadata_pages = false;
|
||||
bool metadata_bytes = false;
|
||||
bool gpu_metadata_bytes = false;
|
||||
};
|
||||
struct MetaRangeInfo {
|
||||
uint64_t metadata_address = 0;
|
||||
uint64_t metadata_size = 0;
|
||||
uint32_t slice = 0;
|
||||
bool full = false;
|
||||
enum class BindingType : uint8_t { Texture, Storage, RenderTarget, DepthTarget, VideoOut };
|
||||
|
||||
struct ImageDesc {
|
||||
ImageInfo info;
|
||||
ImageViewInfo view_info;
|
||||
BindingType type = BindingType::Texture;
|
||||
};
|
||||
|
||||
TextureCache(GraphicContext& graphics, PageManager& page_manager, BufferCache& buffer_cache,
|
||||
ResourceMutex& resource_mutex);
|
||||
struct RegionInfo {
|
||||
bool image_pages = false;
|
||||
bool image_bytes = false;
|
||||
bool gpu_image_bytes = false;
|
||||
};
|
||||
|
||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
BufferCache& buffer_cache, ResourceMutex& resource_mutex);
|
||||
~TextureCache();
|
||||
KYTY_CLASS_NO_COPY(TextureCache);
|
||||
|
||||
[[nodiscard]] VulkanImage& FindTexture(CommandBuffer& command, const ImageInfo& info,
|
||||
bool metadata_read);
|
||||
[[nodiscard]] StorageTextureVulkanImage& FindStorageTexture(CommandBuffer& command,
|
||||
const ImageInfo& info);
|
||||
[[nodiscard]] RenderTextureVulkanImage& FindRenderTarget(CommandBuffer& command,
|
||||
const RenderTargetInfo& info);
|
||||
[[nodiscard]] DepthStencilVulkanImage& FindDepthTarget(CommandBuffer& command,
|
||||
const DepthTargetInfo& info);
|
||||
[[nodiscard]] std::vector<VideoOutVulkanImage*>
|
||||
RegisterVideoOutSurfaces(const std::vector<VideoOutInfo>& infos);
|
||||
void RefreshVideoOut(VideoOutVulkanImage& image, bool render_target = false);
|
||||
void UnregisterVideoOutSurfaces(const std::vector<VideoOutVulkanImage*>& images);
|
||||
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
|
||||
uint32_t packed_clear);
|
||||
void MarkGpuWritten(VulkanImage& image);
|
||||
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
|
||||
bool formatted_buffer_write = false);
|
||||
[[nodiscard]] RenderTextureVulkanImage* FindRenderTargetByRange(CommandBuffer& command,
|
||||
uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] vk::ImageView GetRenderTargetAttachmentView(RenderTextureVulkanImage& image,
|
||||
vk::Format format, uint32_t level,
|
||||
uint32_t base_layer,
|
||||
uint32_t layer_count);
|
||||
[[nodiscard]] vk::ImageView GetDepthTargetAttachmentView(DepthStencilVulkanImage& image,
|
||||
uint32_t base_layer,
|
||||
uint32_t layer_count);
|
||||
[[nodiscard]] vk::ImageView
|
||||
GetDepthTargetSampledView(DepthStencilVulkanImage& image, vk::Format view_format,
|
||||
uint32_t swizzle, uint32_t base_level, uint32_t level_count,
|
||||
vk::ImageViewType type, uint32_t base_layer, uint32_t layer_count);
|
||||
[[nodiscard]] vk::ImageView GetSampledColorView(VulkanImage& image, vk::Format view_format,
|
||||
uint32_t swizzle, uint32_t base_level,
|
||||
uint32_t level_count, vk::ImageViewType type,
|
||||
uint32_t base_layer, uint32_t layer_count);
|
||||
[[nodiscard]] vk::ImageView
|
||||
GetRenderTargetStorageView(RenderTextureVulkanImage& image, vk::Format view_format,
|
||||
uint32_t base_level, uint32_t level_count, vk::ImageViewType type,
|
||||
uint32_t base_layer, uint32_t layer_count);
|
||||
[[nodiscard]] vk::ImageView GetStorageTextureSampledView(StorageTextureVulkanImage& image,
|
||||
const ImageInfo& info);
|
||||
[[nodiscard]] vk::ImageView GetStorageTextureStorageView(StorageTextureVulkanImage& image,
|
||||
uint32_t base_level);
|
||||
[[nodiscard]] DepthStencilVulkanImage*
|
||||
FindDepthTargetByRange(CommandBuffer& command, uint64_t vaddr, uint64_t size,
|
||||
bool allow_containing_sampled = false);
|
||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool ResolveMetaRange(uint64_t vaddr, uint64_t size, MetaRangeInfo& info);
|
||||
void RegisterMeta(uint64_t vaddr, uint64_t size, uint32_t layers = 1);
|
||||
[[nodiscard]] bool IsMeta(uint64_t vaddr);
|
||||
[[nodiscard]] bool IsMetaRange(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t vaddr, uint32_t slice);
|
||||
[[nodiscard]] bool ClearMeta(uint64_t vaddr);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t vaddr, uint32_t slice, bool is_clear);
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false);
|
||||
[[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size,
|
||||
bool ensure_valid = true);
|
||||
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] Image& GetImage(ImageId id);
|
||||
[[nodiscard]] const Image& GetImage(ImageId id) const;
|
||||
void MarkGpuWritten(ImageId id);
|
||||
|
||||
VulkanImage& GetDummySampledTexture(bool uint_format, bool image_3d);
|
||||
VulkanImage& GetDummyStorageTexture(bool uint_format, bool image_3d);
|
||||
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
|
||||
uint32_t packed_clear);
|
||||
void PrepareHostWrite(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
|
||||
bool formatted_buffer_write = false);
|
||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
|
||||
|
||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
|
||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
void ProcessDownloadImages();
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
struct CachedImage;
|
||||
using ImageOwnerIndex = MultiRangePageOwnerIndex<CachedImage*>;
|
||||
struct ReadbackWorker;
|
||||
struct MetaDataInfo {
|
||||
uint64_t size = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t clear_mask = 0;
|
||||
bool gpu_modified = false;
|
||||
};
|
||||
[[nodiscard]] vk::ImageView GetImageView(VulkanImage& image, const ImageViewInfo& info);
|
||||
[[nodiscard]] bool HasMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
|
||||
[[nodiscard]] CachedImage* FindGpuReadbackPageCandidateLocked(uint64_t vaddr, uint64_t size);
|
||||
void RequireNoMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
|
||||
void ResolveImageMetadataOverlapsLocked(uint64_t vaddr, uint64_t size);
|
||||
void MarkSampledAliasesCpuDirtyLocked(uint64_t vaddr, uint64_t size);
|
||||
void RetireSampledTargetAliases(const ImageInfo& requested);
|
||||
void ResolveStorageImageOverlaps(const ImageInfo& requested);
|
||||
void RetireStorageDepthAliasLocked(const ImageInfo& requested);
|
||||
void RegisterImageLocked(CachedImage& image);
|
||||
void UnregisterImageLocked(CachedImage& image, bool release_tracking);
|
||||
[[nodiscard]] VulkanImage& PublishImage(CommandBuffer& command,
|
||||
std::shared_ptr<CachedImage> image);
|
||||
[[nodiscard]] std::vector<CachedImage*> FindImagesInRegionLocked(uint64_t vaddr, uint64_t size,
|
||||
bool page_overlap);
|
||||
void RequireRetirementIsolation(const std::vector<CachedImage*>& retire, const char* operation,
|
||||
uint64_t address, uint64_t size) const;
|
||||
void RetireImages(const std::vector<CachedImage*>& retire,
|
||||
const CachedImage* native_image_source = nullptr);
|
||||
void RetireDepthMetadataLocked(const std::vector<CachedImage*>& retire,
|
||||
uint64_t preserve_address = 0);
|
||||
void MaterializeImagesToGuestLocked(const std::vector<std::shared_ptr<CachedImage>>& images);
|
||||
void SynchronizeColorImageToBufferLocked(CachedImage& cached, uint64_t write_address,
|
||||
uint64_t write_size);
|
||||
void SynchronizeDepthImageToBufferLocked(CachedImage& cached, uint64_t write_address,
|
||||
uint64_t write_size);
|
||||
enum class TransferDirection { Upload, Download };
|
||||
struct ColorTransferPlan;
|
||||
struct DownloadPlan;
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
std::unique_ptr<DummyTextureCache> m_dummy_textures;
|
||||
TrackingSpinLock m_lock;
|
||||
std::mutex m_fault_mutex;
|
||||
MemoryTracker m_memory_tracker;
|
||||
MemoryTracker m_metadata_tracker;
|
||||
Tiler m_tiler;
|
||||
BufferCache& m_buffer_cache;
|
||||
ResourceMutex& m_resource_mutex;
|
||||
std::vector<std::shared_ptr<CachedImage>> m_images;
|
||||
ImageOwnerIndex m_image_owner_index;
|
||||
std::map<uint64_t, MetaDataInfo> m_surface_metas;
|
||||
std::unique_ptr<ReadbackWorker> m_readback;
|
||||
std::vector<uint8_t> m_buffer_transition_guest;
|
||||
struct Slot {
|
||||
std::shared_ptr<Image> image;
|
||||
uint32_t generation = 1;
|
||||
};
|
||||
|
||||
struct MetaDataInfo {
|
||||
uint32_t clear_mask = 0;
|
||||
};
|
||||
|
||||
struct OverlapResult {
|
||||
ImageId image;
|
||||
int32_t mip = -1;
|
||||
int32_t layer = -1;
|
||||
};
|
||||
|
||||
using ImageOwnerIndex = MultiRangePageOwnerIndex<ImageId>;
|
||||
|
||||
[[nodiscard]] Image& ResolveImage(ImageId id);
|
||||
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
|
||||
[[nodiscard]] std::shared_ptr<Image> ResolveOwner(ImageId id) const;
|
||||
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
|
||||
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
|
||||
void RegisterImage(ImageId id);
|
||||
void UnregisterImage(ImageId id, bool release_tracking);
|
||||
void DeleteImage(ImageId id, bool release_tracking = true);
|
||||
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
|
||||
void RetainImage(CommandBuffer& command, ImageId id);
|
||||
void TouchImage(Image& image);
|
||||
void TrackImageDownload(ImageId id);
|
||||
void TrackImageDownloadLocked(ImageId id, Image& image);
|
||||
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
|
||||
bool exact_format);
|
||||
[[nodiscard]] static BindingType UploadBinding(const Image& image);
|
||||
[[nodiscard]] bool SafeToDownload(const Image& image);
|
||||
|
||||
[[nodiscard]] std::vector<ImageId> FindImagesInRegion(uint64_t address, uint64_t size,
|
||||
bool page_overlap) const;
|
||||
[[nodiscard]] OverlapResult ResolveOverlap(const ImageInfo& requested, BindingType binding,
|
||||
ImageId cached, ImageId merged);
|
||||
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
|
||||
ImageId cached);
|
||||
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
|
||||
void RefreshImage(ImageId id, const ImageDesc& desc);
|
||||
void InitializeImage(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] ColorTransferPlan BuildColorTransfer(const Image& image, BindingType binding,
|
||||
TransferDirection direction) const;
|
||||
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
|
||||
void UploadImage(Image& image, const ImageDesc& desc, Buffer& source, uint64_t source_offset);
|
||||
void DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
|
||||
DownloadPlan plan);
|
||||
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
|
||||
void CommitGpuWrite(Image& image);
|
||||
void PrepareImageCopy(Image& image);
|
||||
void RefreshCopySource(ImageId id);
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
void CopyImage(ImageId destination, ImageId source);
|
||||
void AssociateStencil(ImageId depth, GuestRange stencil);
|
||||
void AssociateStencilLocked(ImageId depth, GuestRange stencil);
|
||||
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
|
||||
void ValidateImageDesc(const ImageDesc& desc) const;
|
||||
|
||||
void InvalidateCpuAliases(uint64_t address, uint64_t size);
|
||||
void RestoreGpuTracking(const Image& image);
|
||||
void ReleaseGpuTracking(ImageId id);
|
||||
|
||||
[[nodiscard]] bool SynchronizeImageToBuffer(ImageId id);
|
||||
void DownloadImage(ImageId id);
|
||||
[[nodiscard]] bool TryDownloadImage(ImageId id);
|
||||
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
|
||||
void QueueDownload(GuestRange range, StreamBuffer& download, uint8_t* mapped, uint64_t offset);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
TrackingSpinLock m_lock;
|
||||
MemoryTracker m_memory_tracker;
|
||||
BlitHelper m_blit_helper;
|
||||
std::unique_ptr<TileManager> m_tiler;
|
||||
BufferCache& m_buffer_cache;
|
||||
ResourceMutex& m_resource_mutex;
|
||||
std::vector<Slot> m_slots;
|
||||
std::vector<uint32_t> m_free_slots;
|
||||
ImageOwnerIndex m_image_owner_index;
|
||||
std::map<vk::Format, ImageId> m_null_images;
|
||||
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
|
||||
std::set<ImageId> m_download_images;
|
||||
std::map<uint64_t, MetaDataInfo> m_surface_metas;
|
||||
uint64_t m_total_used_memory = 0;
|
||||
uint64_t m_trigger_gc_memory = 0;
|
||||
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
|
||||
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_gc_tick = 0;
|
||||
bool m_readback_linear_images = false;
|
||||
|
||||
friend struct TextureCacheTestAccess;
|
||||
friend class RenderExecutor;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -1,161 +1,718 @@
|
||||
#include "graphics/host_gpu/renderer/tiler.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/gpuTiler.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_demote_d16_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_depth_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_promote_d16_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_prt_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_prt_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_render_target_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard256_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard4_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard4_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_swap_bgra16_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/image.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/renderer/streamBuffer.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
struct DepthTransfer {
|
||||
std::vector<GpuTileInfo> infos;
|
||||
std::vector<BufferImageCopy> regions;
|
||||
};
|
||||
|
||||
DepthTransfer MakeDepthTransfer(uint64_t size, uint32_t layers, uint32_t format,
|
||||
uint32_t bytes_per_element, uint32_t width, uint32_t height,
|
||||
uint32_t pitch, uint32_t base_layer, vk::ImageAspectFlags aspect) {
|
||||
EXIT_IF(size == 0 || layers == 0 || size % layers != 0);
|
||||
TileBlockLayout block {};
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetBlockLayout(TileBlockFamily::Depth64KB, bytes_per_element, block) ||
|
||||
Prospero::NumBytesPerElement(format) != bytes_per_element);
|
||||
|
||||
const uint64_t slice_size = size / layers;
|
||||
DepthTransfer transfer;
|
||||
transfer.infos.reserve(layers);
|
||||
transfer.regions.reserve(layers);
|
||||
for (uint32_t layer = 0; layer < layers; layer++) {
|
||||
const uint64_t offset = slice_size * layer;
|
||||
GpuTileInfo info {block.family,
|
||||
block.bytes_per_element,
|
||||
offset,
|
||||
slice_size,
|
||||
offset,
|
||||
slice_size,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
pitch};
|
||||
info.surface_z = base_layer + layer;
|
||||
transfer.infos.push_back(info);
|
||||
|
||||
BufferImageCopy region {};
|
||||
region.offset = static_cast<uint32_t>(offset);
|
||||
region.pitch = pitch;
|
||||
region.width = width;
|
||||
region.height = height;
|
||||
region.dst_layer = base_layer + layer;
|
||||
region.aspect = aspect;
|
||||
transfer.regions.push_back(region);
|
||||
TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
StreamBuffer& stream_buffer)
|
||||
: m_graphics(graphics), m_scheduler(scheduler), m_stream_buffer(stream_buffer) {
|
||||
static_assert(FamilyCount == 9);
|
||||
static_assert(sizeof(Push) == 52);
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> bindings {};
|
||||
for (uint32_t index = 0; index < 2; index++) {
|
||||
bindings[index] = {index, vk::DescriptorType::eStorageBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eCompute, nullptr};
|
||||
}
|
||||
return transfer;
|
||||
bindings[2] = {2, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute,
|
||||
nullptr};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
descriptor_info.pBindings = bindings.data();
|
||||
RequireVulkanSuccess(m_graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
|
||||
&m_descriptor_layout),
|
||||
"create TileManager descriptor layout");
|
||||
|
||||
const vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0, sizeof(Push)};
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_descriptor_layout;
|
||||
layout_info.pushConstantRangeCount = 1;
|
||||
layout_info.pPushConstantRanges = &push_range;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
|
||||
"create TileManager pipeline layout");
|
||||
}
|
||||
|
||||
void UploadDepth(DepthStencilVulkanImage& image, uint64_t source_address, uint64_t size,
|
||||
uint32_t layers, uint32_t format, uint32_t bytes_per_element, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint32_t base_layer,
|
||||
vk::ImageAspectFlags aspect) {
|
||||
auto transfer = MakeDepthTransfer(size, layers, format, bytes_per_element, width, height, pitch,
|
||||
base_layer, aspect);
|
||||
Transfer::UploadTiledImage(image, reinterpret_cast<const void*>(source_address), size, size,
|
||||
transfer.infos, transfer.regions,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
||||
}
|
||||
|
||||
template <uint32_t (*Encode)(uint16_t)>
|
||||
void UploadPromotedD16Depth(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
|
||||
const BufferImageCopySource& source, uint32_t base_layer) {
|
||||
const uint64_t guest_slice_size = info.size / info.layers;
|
||||
const uint64_t texels = static_cast<uint64_t>(info.pitch) * info.height;
|
||||
const uint64_t host_slice_size = texels * sizeof(uint32_t);
|
||||
const uint64_t host_upload_size = host_slice_size * info.layers;
|
||||
EXIT_IF(host_upload_size > UINT32_MAX);
|
||||
|
||||
auto transfer = MakeDepthTransfer(info.size, info.layers, info.guest_format,
|
||||
info.bytes_per_element, info.width, info.height, info.pitch,
|
||||
base_layer, vk::ImageAspectFlagBits::eDepth);
|
||||
std::vector<uint16_t> guest_linear(info.size / sizeof(uint16_t));
|
||||
GpuDetile(reinterpret_cast<const void*>(source.address), guest_linear.data(), info.size,
|
||||
info.size, transfer.infos);
|
||||
|
||||
Transfer::ScratchBuffer host_linear(host_upload_size);
|
||||
for (uint32_t layer = 0; layer < info.layers; layer++) {
|
||||
const auto* guest = guest_linear.data() + guest_slice_size / sizeof(uint16_t) * layer;
|
||||
auto* host = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(host_linear.Data()) +
|
||||
host_slice_size * layer);
|
||||
for (uint64_t texel = 0; texel < texels; texel++) {
|
||||
host[texel] = Encode(guest[texel]);
|
||||
}
|
||||
transfer.regions[layer].offset = static_cast<uint32_t>(host_slice_size * layer);
|
||||
}
|
||||
Transfer::UploadImage(image, host_linear.Data(), host_upload_size, transfer.regions,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Tiler::DetileImage(GpuTextureVulkanImage& image, const ImageInfo& info,
|
||||
const BufferImageCopySource& source, bool refresh, bool storage) const {
|
||||
if (refresh) Transfer::WaitForQueueIdle();
|
||||
|
||||
const bool array_texture = TextureIsLayeredTexture(info.type);
|
||||
const bool volume_texture = TextureIs3DTexture(info.type);
|
||||
auto layout = TextureCalcUploadLayout(
|
||||
info.format, info.width, info.height, info.levels, info.depth, info.pitch, info.tile,
|
||||
info.size, true, volume_texture, storage ? "StorageTextureCache" : "TextureCache");
|
||||
auto regions = TextureBuildUploadRegions(layout, image.format, info.width, info.height,
|
||||
info.depth, info.levels, array_texture, volume_texture,
|
||||
TextureUploadDestination::MipLevels);
|
||||
TextureUploadGuestImage(image, reinterpret_cast<const void*>(source.address), info.size,
|
||||
regions, layout, info.format, info.width, info.height, info.depth,
|
||||
info.levels, storage ? "StorageTextureCache" : "TextureCache",
|
||||
storage ? vk::ImageLayout::eGeneral
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
||||
|
||||
void Tiler::DetileImage(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
|
||||
const BufferImageCopySource& source, bool refresh,
|
||||
uint32_t base_layer) const {
|
||||
EXIT_NOT_IMPLEMENTED(info.samples != 1 || image.samples != 1);
|
||||
if (refresh) Transfer::WaitForQueueIdle();
|
||||
|
||||
if (DepthAspectTransferBytes(info.format) != info.bytes_per_element) {
|
||||
switch (info.format) {
|
||||
case vk::Format::eD24UnormS8Uint:
|
||||
UploadPromotedD16Depth<EncodeD16AsD24>(image, info, source, base_layer);
|
||||
return;
|
||||
case vk::Format::eD32SfloatS8Uint:
|
||||
UploadPromotedD16Depth<EncodeD16AsD32>(image, info, source, base_layer);
|
||||
return;
|
||||
default: EXIT_NOT_IMPLEMENTED(true);
|
||||
TileManager::~TileManager() {
|
||||
for (auto pipeline: m_pipelines) {
|
||||
if (pipeline != nullptr) {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
UploadDepth(image, source.address, info.size, info.layers, info.guest_format,
|
||||
info.bytes_per_element, info.width, info.height, info.pitch, base_layer,
|
||||
vk::ImageAspectFlagBits::eDepth);
|
||||
if (m_d16_to_d24 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d24, nullptr);
|
||||
}
|
||||
if (m_d16_to_d32 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d32, nullptr);
|
||||
}
|
||||
if (m_d24_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d24_to_d16, nullptr);
|
||||
}
|
||||
if (m_d32_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d32_to_d16, nullptr);
|
||||
}
|
||||
if (m_swap_bgra16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_swap_bgra16, nullptr);
|
||||
}
|
||||
if (m_pipeline_layout != nullptr) {
|
||||
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
|
||||
}
|
||||
if (m_descriptor_layout != nullptr) {
|
||||
m_graphics.device.destroyDescriptorSetLayout(m_descriptor_layout, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Tiler::DetileStencil(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
|
||||
const BufferImageCopySource& source, bool refresh,
|
||||
uint32_t base_layer) const {
|
||||
EXIT_NOT_IMPLEMENTED(info.samples != 1 || image.samples != 1);
|
||||
if (refresh) Transfer::WaitForQueueIdle();
|
||||
TileManager::Scratch TileManager::AllocateScratch(uint64_t size) {
|
||||
EXIT_IF(size == 0);
|
||||
vk::BufferCreateInfo create {};
|
||||
create.sType = vk::StructureType::eBufferCreateInfo;
|
||||
create.size = size;
|
||||
create.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc |
|
||||
vk::BufferUsageFlagBits::eTransferDst;
|
||||
create.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
const auto format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt);
|
||||
const auto pitch = TileGetTexturePitch(format, info.width, 1,
|
||||
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
|
||||
UploadDepth(image, source.address, info.stencil_size, info.layers, format, 1, info.width,
|
||||
info.height, pitch, base_layer, vk::ImageAspectFlagBits::eStencil);
|
||||
VmaAllocationCreateInfo allocate {};
|
||||
allocate.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VmaAllocation memory = nullptr;
|
||||
const auto raw = static_cast<VkBufferCreateInfo>(create);
|
||||
RequireVulkanSuccess(static_cast<vk::Result>(vmaCreateBuffer(
|
||||
m_graphics.allocator, &raw, &allocate, &buffer, &memory, nullptr)),
|
||||
"allocate TileManager scratch buffer");
|
||||
return {buffer, memory, size};
|
||||
}
|
||||
|
||||
void TileManager::DeferDestroy(Scratch scratch) {
|
||||
auto allocator = m_graphics.allocator;
|
||||
m_scheduler.DeferOperation(
|
||||
[allocator, scratch] { vmaDestroyBuffer(allocator, scratch.buffer, scratch.allocation); });
|
||||
}
|
||||
|
||||
void TileManager::Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, uint64_t source_base,
|
||||
uint64_t target_base, std::vector<Dispatch>& dispatches) {
|
||||
EXIT_IF(infos.empty() || tiled_capacity == 0 || linear_capacity == 0);
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
EXIT_NOT_IMPLEMENTED(tiled_capacity > UINT32_MAX || linear_capacity > UINT32_MAX);
|
||||
|
||||
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
|
||||
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
|
||||
};
|
||||
const auto checked_add = [](uint64_t left, uint64_t right, uint64_t& result) {
|
||||
return right <= UINT64_MAX - left && (result = left + right, true);
|
||||
};
|
||||
const auto valid_range = [](uint64_t offset, uint64_t size, uint64_t capacity) {
|
||||
return size != 0 && offset <= capacity && size <= capacity - offset;
|
||||
};
|
||||
|
||||
dispatches.clear();
|
||||
dispatches.reserve(infos.size());
|
||||
for (const auto& info: infos) {
|
||||
TileBlockLayout block {};
|
||||
const uint32_t tiled_width = info.tiled_width != 0 ? info.tiled_width : info.pitch;
|
||||
const uint32_t tiled_height = info.tiled_height != 0 ? info.tiled_height : info.height;
|
||||
const uint64_t groups_x = (static_cast<uint64_t>(info.width) + 7u) / 8u;
|
||||
const uint64_t groups_y = (static_cast<uint64_t>(info.height) + 7u) / 8u;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetBlockLayout(info.family, info.bytes_per_element, block) || info.width == 0 ||
|
||||
info.height == 0 || info.depth == 0 || info.pitch < info.width ||
|
||||
groups_x > limits.maxComputeWorkGroupCount[0] ||
|
||||
groups_y > limits.maxComputeWorkGroupCount[1] ||
|
||||
info.depth > limits.maxComputeWorkGroupCount[2] ||
|
||||
(!info.tail && (tiled_width < info.width || tiled_height < info.height)) ||
|
||||
!valid_range(info.linear_offset, info.linear_size, linear_capacity) ||
|
||||
!valid_range(info.tiled_offset, info.tiled_size, tiled_capacity) ||
|
||||
(block.block_depth == 1 && info.depth != 1));
|
||||
|
||||
uint64_t pitch_bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(info.pitch, info.bytes_per_element, pitch_bytes) ||
|
||||
pitch_bytes > UINT32_MAX);
|
||||
uint64_t slice_bytes = info.linear_slice_stride;
|
||||
uint64_t minimum_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(pitch_bytes, info.height, minimum_slice));
|
||||
if (slice_bytes == 0) {
|
||||
slice_bytes = minimum_slice;
|
||||
}
|
||||
uint64_t linear_used = 0;
|
||||
uint64_t bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED((info.depth > 1 && slice_bytes < minimum_slice) ||
|
||||
!checked_multiply(info.depth - 1u, slice_bytes, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
!checked_multiply(info.height - 1u, pitch_bytes, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
!checked_multiply(info.width, info.bytes_per_element, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
linear_used > info.linear_size || slice_bytes > UINT32_MAX);
|
||||
|
||||
const uint64_t columns =
|
||||
(static_cast<uint64_t>(tiled_width) + block.block_width - 1u) / block.block_width;
|
||||
const uint64_t rows =
|
||||
(static_cast<uint64_t>(tiled_height) + block.block_height - 1u) / block.block_height;
|
||||
uint64_t blocks_per_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(columns, rows, blocks_per_slice) ||
|
||||
columns > UINT32_MAX || blocks_per_slice > UINT32_MAX);
|
||||
if (info.tail) {
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
info.family == TileBlockFamily::Standard256B || info.depth > block.block_depth ||
|
||||
info.tail_x >= block.block_width || info.width > block.block_width - info.tail_x ||
|
||||
info.tail_y >= block.block_height ||
|
||||
info.height > block.block_height - info.tail_y ||
|
||||
info.tiled_size < block.block_size);
|
||||
} else {
|
||||
const uint64_t slices =
|
||||
(static_cast<uint64_t>(info.depth) + block.block_depth - 1u) / block.block_depth;
|
||||
uint64_t tiled_used = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(blocks_per_slice, slices, tiled_used) ||
|
||||
!checked_multiply(tiled_used, block.block_size, tiled_used) ||
|
||||
tiled_used > info.tiled_size);
|
||||
}
|
||||
|
||||
const uint32_t alignment = std::min(info.bytes_per_element, 4u);
|
||||
EXIT_NOT_IMPLEMENTED(((info.linear_offset | info.tiled_offset | pitch_bytes | slice_bytes) &
|
||||
(alignment - 1u)) != 0);
|
||||
const uint64_t src = source_base + (tile ? info.linear_offset : info.tiled_offset);
|
||||
const uint64_t dst = target_base + (tile ? info.tiled_offset : info.linear_offset);
|
||||
EXIT_NOT_IMPLEMENTED(src > UINT32_MAX || dst > UINT32_MAX);
|
||||
|
||||
const uint32_t family_index = static_cast<uint32_t>(info.family);
|
||||
const uint32_t element_index = std::countr_zero(info.bytes_per_element);
|
||||
EXIT_NOT_IMPLEMENTED(family_index >= FamilyCount || element_index >= BytesPerElementCount);
|
||||
Dispatch dispatch {};
|
||||
dispatch.pipeline_slot =
|
||||
((tile ? FamilyCount : 0u) + family_index) * BytesPerElementCount + element_index;
|
||||
dispatch.push.src_base = static_cast<uint32_t>(src);
|
||||
dispatch.push.dst_base = static_cast<uint32_t>(dst);
|
||||
dispatch.push.width = info.width;
|
||||
dispatch.push.height = info.height;
|
||||
dispatch.push.depth = info.depth;
|
||||
dispatch.push.surface_z = info.surface_z;
|
||||
dispatch.push.pitch_bytes = static_cast<uint32_t>(pitch_bytes);
|
||||
dispatch.push.slice_bytes = static_cast<uint32_t>(slice_bytes);
|
||||
dispatch.push.blocks_per_row = static_cast<uint32_t>(columns);
|
||||
dispatch.push.blocks_per_slice = static_cast<uint32_t>(blocks_per_slice);
|
||||
dispatch.push.tail_x = info.tail_x;
|
||||
dispatch.push.tail_y = info.tail_y;
|
||||
dispatch.push.tail = info.tail;
|
||||
dispatches.push_back(dispatch);
|
||||
}
|
||||
|
||||
const uint64_t uniform_alignment =
|
||||
std::max<uint64_t>(limits.minUniformBufferOffsetAlignment, 1);
|
||||
const uint64_t stride = (sizeof(Push) + uniform_alignment - 1) & ~(uniform_alignment - 1);
|
||||
EXIT_NOT_IMPLEMENTED(dispatches.size() > UINT64_MAX / stride);
|
||||
const uint64_t bytes = dispatches.size() * stride;
|
||||
auto [mapped, offset] = m_stream_buffer.Map(bytes, uniform_alignment);
|
||||
EXIT_IF(mapped == nullptr);
|
||||
for (size_t index = 0; index < dispatches.size(); index++) {
|
||||
std::memcpy(mapped + index * stride, &dispatches[index].push, sizeof(Push));
|
||||
dispatches[index].params_offset = offset + index * stride;
|
||||
}
|
||||
m_stream_buffer.Commit();
|
||||
}
|
||||
|
||||
vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
EXIT_IF(slot >= m_pipelines.size());
|
||||
if (m_pipelines[slot] != nullptr) {
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
struct Shader {
|
||||
const uint32_t* code;
|
||||
size_t words;
|
||||
};
|
||||
static constexpr std::array<Shader, FamilyCount> shaders {{
|
||||
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
|
||||
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
|
||||
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
|
||||
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
|
||||
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
|
||||
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
|
||||
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
|
||||
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
|
||||
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
|
||||
}};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
const uint32_t direction_index = slot / (FamilyCount * BytesPerElementCount);
|
||||
const uint32_t family_index = (slot / BytesPerElementCount) % FamilyCount;
|
||||
const uint32_t values[] {1u << element_index, direction_index};
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
|
||||
module_info.pCode = shaders[family_index].code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create TileManager shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_pipelines[slot]);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create TileManager pipeline");
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
|
||||
void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
|
||||
uint64_t source_capacity, vk::Buffer target, uint64_t target_offset,
|
||||
uint64_t target_capacity, std::span<Dispatch> dispatches,
|
||||
bool clear_target) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_descriptor_offset = source_offset & ~(descriptor_alignment - 1);
|
||||
const uint64_t target_descriptor_offset = target_offset & ~(descriptor_alignment - 1);
|
||||
const uint64_t source_base = source_offset - source_descriptor_offset;
|
||||
const uint64_t target_base = target_offset - target_descriptor_offset;
|
||||
const uint64_t source_range = (source_base + source_capacity + 3u) & ~uint64_t {3};
|
||||
const uint64_t target_range = (target_base + target_capacity + 3u) & ~uint64_t {3};
|
||||
EXIT_NOT_IMPLEMENTED(source_range > limits.maxStorageBufferRange ||
|
||||
target_range > limits.maxStorageBufferRange || target_offset % 4 != 0 ||
|
||||
target_capacity % 4 != 0);
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::BufferMemoryBarrier barriers[3] {};
|
||||
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].buffer = source;
|
||||
barriers[0].offset = source_offset;
|
||||
barriers[0].size = source_capacity;
|
||||
barriers[1] = barriers[0];
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
|
||||
barriers[1].dstAccessMask =
|
||||
clear_target ? vk::AccessFlagBits::eTransferWrite
|
||||
: vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].buffer = target;
|
||||
barriers[1].offset = target_offset;
|
||||
barriers[1].size = target_capacity;
|
||||
barriers[2] = barriers[0];
|
||||
barriers[2].srcAccessMask = vk::AccessFlagBits::eHostWrite;
|
||||
barriers[2].dstAccessMask = vk::AccessFlagBits::eUniformRead;
|
||||
barriers[2].buffer = m_stream_buffer.Handle();
|
||||
barriers[2].offset = dispatches.front().params_offset;
|
||||
barriers[2].size =
|
||||
dispatches.back().params_offset - dispatches.front().params_offset + sizeof(Push);
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer, {}, 0,
|
||||
nullptr, 3, barriers, 0, nullptr);
|
||||
if (clear_target) {
|
||||
command.fillBuffer(target, target_offset, target_capacity, 0);
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barriers[1].dstAccessMask =
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 1,
|
||||
&barriers[1], 0, nullptr);
|
||||
}
|
||||
|
||||
const vk::DescriptorBufferInfo source_info {source, source_descriptor_offset, source_range};
|
||||
const vk::DescriptorBufferInfo target_info {target, target_descriptor_offset, target_range};
|
||||
for (auto& dispatch: dispatches) {
|
||||
const vk::DescriptorBufferInfo params_info {m_stream_buffer.Handle(),
|
||||
dispatch.params_offset, sizeof(Push)};
|
||||
const vk::DescriptorBufferInfo infos[] {source_info, target_info, params_info};
|
||||
std::array<vk::WriteDescriptorSet, 3> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = index == 2 ? vk::DescriptorType::eUniformBuffer
|
||||
: vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, GetPipeline(dispatch.pipeline_slot));
|
||||
command.dispatch((dispatch.push.width + 7u) / 8u, (dispatch.push.height + 7u) / 8u,
|
||||
dispatch.push.depth);
|
||||
}
|
||||
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eMemoryRead;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllCommands, {}, 0, nullptr, 1,
|
||||
&barriers[1], 0, nullptr);
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::Detile(vk::Buffer tiled, uint64_t tiled_offset,
|
||||
uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(false, tiled_capacity, linear_capacity, infos, source_base, 0, dispatches);
|
||||
auto scratch = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
|
||||
DeferDestroy(scratch);
|
||||
Record(false, tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
|
||||
true);
|
||||
return {scratch.buffer, 0, linear_capacity};
|
||||
}
|
||||
|
||||
void TileManager::Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity,
|
||||
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
std::span<const GpuTileInfo> infos) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_base = linear_offset & (descriptor_alignment - 1);
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(true, tiled_capacity, linear_capacity, infos, source_base, target_base, dispatches);
|
||||
Record(true, linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
}
|
||||
|
||||
void TileManager::TileImage(Image& image, std::span<const vk::BufferImageCopy> regions,
|
||||
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform) {
|
||||
EXIT_IF(regions.empty());
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
// Reserve all stream parameters before creating a scheduler-lived scratch dependency:
|
||||
// StreamBuffer::Map is allowed to submit the current tick when it wraps.
|
||||
Prepare(true, tiled_capacity, linear_capacity, infos, 0, target_base, dispatches);
|
||||
auto linear = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
|
||||
DeferDestroy(linear);
|
||||
image.Download(regions, linear.buffer, 0, linear.size);
|
||||
Result source {linear.buffer, 0, linear.size};
|
||||
if (transform == ColorTransform::SwapBgra16) {
|
||||
source = SwapBgra16(source);
|
||||
}
|
||||
Record(true, source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::GetScratchBuffer(uint64_t size) {
|
||||
auto scratch = AllocateScratch((size + 3u) & ~uint64_t {3});
|
||||
DeferDestroy(scratch);
|
||||
return {scratch.buffer, 0, scratch.size};
|
||||
}
|
||||
|
||||
TileManager::StorageBinding TileManager::BindStorage(Result buffer, uint64_t size) const {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const auto alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const auto descriptor_offset = buffer.offset - buffer.offset % alignment;
|
||||
const auto base = buffer.offset - descriptor_offset;
|
||||
EXIT_IF(buffer.buffer == nullptr || size == 0 || buffer.size < size || base > UINT32_MAX ||
|
||||
size > UINT64_MAX - base || base + size > UINT64_MAX - 3);
|
||||
const auto range = (base + size + 3) & ~uint64_t {3};
|
||||
EXIT_IF(range > limits.maxStorageBufferRange || range > UINT32_MAX);
|
||||
return {{buffer.buffer, descriptor_offset, range}, static_cast<uint32_t>(base)};
|
||||
}
|
||||
|
||||
uint32_t TileManager::ConversionRows(uint64_t offset, uint64_t row_stride, uint64_t active,
|
||||
uint32_t remaining, uint64_t alignment, uint64_t max_range,
|
||||
uint32_t max_groups) noexcept {
|
||||
if (row_stride == 0 || active == 0 || remaining == 0 || alignment == 0 || max_groups == 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto prefix = offset % alignment;
|
||||
if (prefix >= max_range || active > max_range - prefix) {
|
||||
return 0;
|
||||
}
|
||||
const auto descriptor_rows = 1 + (max_range - prefix - active) / row_stride;
|
||||
return static_cast<uint32_t>(std::min<uint64_t>({remaining, descriptor_rows, max_groups}));
|
||||
}
|
||||
|
||||
void TileManager::ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout) {
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
if (direction == D16Direction::Promote) {
|
||||
pipeline_pointer = d32 ? &m_d16_to_d32 : &m_d16_to_d24;
|
||||
} else {
|
||||
pipeline_pointer = d32 ? &m_d32_to_d16 : &m_d24_to_d16;
|
||||
}
|
||||
auto& pipeline = *pipeline_pointer;
|
||||
if (pipeline == nullptr) {
|
||||
const uint32_t value = d32 ? 1u : 0u;
|
||||
const vk::SpecializationMapEntry entry {0, 0, sizeof(value)};
|
||||
const vk::SpecializationInfo specialization {1, &entry, sizeof(value), &value};
|
||||
const uint32_t* code = nullptr;
|
||||
size_t words = 0;
|
||||
if (direction == D16Direction::Promote) {
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_PROMOTE_D16_SPV);
|
||||
} else {
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
|
||||
}
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = words * sizeof(uint32_t);
|
||||
module_info.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create D16 conversion shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create D16 conversion pipeline");
|
||||
}
|
||||
|
||||
const uint64_t source_element =
|
||||
direction == D16Direction::Promote ? sizeof(uint16_t) : sizeof(uint32_t);
|
||||
const uint64_t target_element =
|
||||
direction == D16Direction::Promote ? sizeof(uint32_t) : sizeof(uint16_t);
|
||||
const uint64_t source_active = static_cast<uint64_t>(layout.width) * source_element;
|
||||
const uint64_t target_active = static_cast<uint64_t>(layout.width) * target_element;
|
||||
const auto required = [](uint32_t height, uint32_t layers, uint64_t row_stride,
|
||||
uint64_t slice_stride, uint64_t active) {
|
||||
EXIT_IF(height == 0 || layers == 0 || row_stride < active ||
|
||||
(height - 1) > (UINT64_MAX - active) / row_stride);
|
||||
const auto slice = static_cast<uint64_t>(height - 1) * row_stride + active;
|
||||
EXIT_IF(slice_stride < slice || (layers - 1) > (UINT64_MAX - slice) / slice_stride);
|
||||
return static_cast<uint64_t>(layers - 1) * slice_stride + slice;
|
||||
};
|
||||
EXIT_IF(layout.width == 0 || layout.source_row_stride > UINT32_MAX ||
|
||||
layout.target_row_stride > UINT32_MAX);
|
||||
const auto source_required = required(layout.height, layout.layers, layout.source_row_stride,
|
||||
layout.source_slice_stride, source_active);
|
||||
const auto target_required = required(layout.height, layout.layers, layout.target_row_stride,
|
||||
layout.target_slice_stride, target_active);
|
||||
EXIT_IF(source_required > UINT64_MAX - 3 || target_required > UINT64_MAX - 3);
|
||||
const auto source_barrier_size = (source_required + 3) & ~uint64_t {3};
|
||||
const auto target_barrier_size = (target_required + 3) & ~uint64_t {3};
|
||||
EXIT_IF(source.size < source_barrier_size || target.size < target_barrier_size);
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::BufferMemoryBarrier barriers[2] {};
|
||||
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].buffer = source.buffer;
|
||||
barriers[0].offset = source.offset;
|
||||
barriers[0].size = source_barrier_size;
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[1].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[1].buffer = target.buffer;
|
||||
barriers[1].offset = target.offset;
|
||||
barriers[1].size = target_barrier_size;
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite |
|
||||
vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline);
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const auto descriptor_alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const auto rows_for = [&](Result buffer, uint64_t relative, uint64_t stride, uint64_t active,
|
||||
uint32_t remaining) {
|
||||
EXIT_IF(relative > buffer.size || buffer.offset > UINT64_MAX - relative);
|
||||
const auto offset = buffer.offset + relative;
|
||||
return ConversionRows(offset, stride, active, remaining, descriptor_alignment,
|
||||
limits.maxStorageBufferRange, limits.maxComputeWorkGroupCount[1]);
|
||||
};
|
||||
const auto groups_x = (static_cast<uint64_t>(layout.width) + 63u) / 64u;
|
||||
EXIT_IF(groups_x == 0 || groups_x > limits.maxComputeWorkGroupCount[0]);
|
||||
for (uint32_t layer = 0; layer < layout.layers; layer++) {
|
||||
for (uint32_t row = 0; row < layout.height;) {
|
||||
const auto source_relative =
|
||||
layout.source_slice_stride * layer + layout.source_row_stride * row;
|
||||
const auto target_relative =
|
||||
layout.target_slice_stride * layer + layout.target_row_stride * row;
|
||||
const auto remaining = layout.height - row;
|
||||
const auto rows = std::min(rows_for(source, source_relative, layout.source_row_stride,
|
||||
source_active, remaining),
|
||||
rows_for(target, target_relative, layout.target_row_stride,
|
||||
target_active, remaining));
|
||||
EXIT_IF(rows == 0);
|
||||
const auto source_span =
|
||||
static_cast<uint64_t>(rows - 1) * layout.source_row_stride + source_active;
|
||||
const auto target_span =
|
||||
static_cast<uint64_t>(rows - 1) * layout.target_row_stride + target_active;
|
||||
const auto source_binding = BindStorage(
|
||||
{source.buffer, source.offset + source_relative, source.size - source_relative},
|
||||
source_span);
|
||||
const auto target_binding = BindStorage(
|
||||
{target.buffer, target.offset + target_relative, target.size - target_relative},
|
||||
target_span);
|
||||
const vk::DescriptorBufferInfo infos[] {
|
||||
source_binding.info,
|
||||
target_binding.info,
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
Push push {};
|
||||
push.src_base = source_binding.base;
|
||||
push.dst_base = target_binding.base;
|
||||
push.width = layout.width;
|
||||
push.height = rows;
|
||||
push.pitch_bytes = static_cast<uint32_t>(layout.source_row_stride);
|
||||
push.slice_bytes = static_cast<uint32_t>(layout.target_row_stride);
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(push), &push);
|
||||
command.dispatch(static_cast<uint32_t>(groups_x), rows, 1);
|
||||
row += rows;
|
||||
}
|
||||
}
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eMemoryRead;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllCommands, {}, 0, nullptr, 1,
|
||||
&barriers[1], 0, nullptr);
|
||||
}
|
||||
|
||||
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
|
||||
if (m_swap_bgra16 == nullptr) {
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
|
||||
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create BGRA16 swap shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_swap_bgra16);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create BGRA16 swap pipeline");
|
||||
}
|
||||
const uint64_t bytes = static_cast<uint64_t>(pixels) * 8u;
|
||||
EXIT_IF(pixels == 0);
|
||||
const auto input_binding = BindStorage(input, bytes);
|
||||
const auto output_binding = BindStorage(output, bytes);
|
||||
|
||||
const vk::DescriptorBufferInfo infos[] {
|
||||
input_binding.info,
|
||||
output_binding.info,
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
vk::BufferMemoryBarrier barriers[2] {};
|
||||
for (uint32_t index = 0; index < 2; index++) {
|
||||
barriers[index].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[index].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[index].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[index].buffer = infos[index].buffer;
|
||||
barriers[index].offset = infos[index].offset;
|
||||
barriers[index].size = infos[index].range;
|
||||
}
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryRead;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_swap_bgra16);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
Push push {};
|
||||
push.src_base = input_binding.base;
|
||||
push.dst_base = output_binding.base;
|
||||
push.width = pixels;
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||
&push);
|
||||
command.dispatch((pixels + 63u) / 64u, 1, 1);
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 1, &barriers[1],
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::SwapBgra16(Result input) {
|
||||
EXIT_NOT_IMPLEMENTED(input.size == 0 || input.size % 8u != 0 || input.size / 8u > UINT32_MAX);
|
||||
auto output = AllocateScratch(input.size);
|
||||
DeferDestroy(output);
|
||||
Result result {output.buffer, 0, output.size};
|
||||
SwapBgra16(input, result, static_cast<uint32_t>(input.size / 8u));
|
||||
return result;
|
||||
}
|
||||
|
||||
void TileManager::SwapBgra16(Result input, Result output) {
|
||||
EXIT_NOT_IMPLEMENTED(input.size == 0 || input.size % 8u != 0 || input.size / 8u > UINT32_MAX ||
|
||||
output.size < input.size);
|
||||
SwapBgra16(input, output, static_cast<uint32_t>(input.size / 8u));
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -2,26 +2,146 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/renderer/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageInfo.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct DepthStencilVulkanImage;
|
||||
struct GpuTextureVulkanImage;
|
||||
class Tiler final {
|
||||
public:
|
||||
Tiler() = default;
|
||||
KYTY_CLASS_NO_COPY(Tiler);
|
||||
class CommandScheduler;
|
||||
class Image;
|
||||
class StreamBuffer;
|
||||
struct GraphicContext;
|
||||
struct TileManagerTestAccess;
|
||||
|
||||
void DetileImage(GpuTextureVulkanImage& image, const ImageInfo& info,
|
||||
const BufferImageCopySource& source, bool refresh, bool storage) const;
|
||||
void DetileImage(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
|
||||
const BufferImageCopySource& source, bool refresh,
|
||||
uint32_t base_layer = 0) const;
|
||||
void DetileStencil(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
|
||||
const BufferImageCopySource& source, bool refresh,
|
||||
uint32_t base_layer = 0) const;
|
||||
struct GpuTileInfo {
|
||||
TileBlockFamily family = TileBlockFamily::Count;
|
||||
uint32_t bytes_per_element = 0;
|
||||
uint64_t linear_offset = 0;
|
||||
uint64_t linear_size = 0;
|
||||
uint64_t tiled_offset = 0;
|
||||
uint64_t tiled_size = 0;
|
||||
uint64_t linear_slice_stride = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 1;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t tail_x = 0;
|
||||
uint32_t tail_y = 0;
|
||||
bool tail = false;
|
||||
uint32_t tiled_width = 0;
|
||||
uint32_t tiled_height = 0;
|
||||
uint32_t surface_z = 0;
|
||||
};
|
||||
|
||||
class TileManager final {
|
||||
public:
|
||||
enum class D16Direction { Promote, Demote };
|
||||
enum class ColorTransform { None, SwapBgra16 };
|
||||
|
||||
struct Result {
|
||||
vk::Buffer buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct D16Layout {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t layers = 0;
|
||||
uint64_t source_row_stride = 0;
|
||||
uint64_t target_row_stride = 0;
|
||||
uint64_t source_slice_stride = 0;
|
||||
uint64_t target_slice_stride = 0;
|
||||
};
|
||||
|
||||
TileManager(GraphicContext& graphics, CommandScheduler& scheduler, StreamBuffer& stream_buffer);
|
||||
~TileManager();
|
||||
KYTY_CLASS_NO_COPY(TileManager);
|
||||
|
||||
// The returned device-local buffer remains alive through the current scheduler tick.
|
||||
[[nodiscard]] Result Detile(vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos);
|
||||
void Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity, vk::Buffer tiled,
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, std::span<const GpuTileInfo> infos);
|
||||
void TileImage(Image& image, std::span<const vk::BufferImageCopy> regions, vk::Buffer tiled,
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform = ColorTransform::None);
|
||||
[[nodiscard]] Result GetScratchBuffer(uint64_t size);
|
||||
void ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout);
|
||||
[[nodiscard]] Result SwapBgra16(Result input);
|
||||
void SwapBgra16(Result input, Result output);
|
||||
|
||||
private:
|
||||
friend struct TileManagerTestAccess;
|
||||
|
||||
static constexpr uint32_t FamilyCount = static_cast<uint32_t>(TileBlockFamily::Count);
|
||||
static constexpr uint32_t BytesPerElementCount = 5;
|
||||
static constexpr uint32_t DirectionCount = 2;
|
||||
static constexpr uint32_t PipelineCount = FamilyCount * BytesPerElementCount * DirectionCount;
|
||||
|
||||
struct Push {
|
||||
uint32_t src_base;
|
||||
uint32_t dst_base;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
uint32_t surface_z;
|
||||
uint32_t pitch_bytes;
|
||||
uint32_t slice_bytes;
|
||||
uint32_t blocks_per_row;
|
||||
uint32_t blocks_per_slice;
|
||||
uint32_t tail_x;
|
||||
uint32_t tail_y;
|
||||
uint32_t tail;
|
||||
};
|
||||
struct Dispatch {
|
||||
Push push {};
|
||||
uint32_t pipeline_slot = 0;
|
||||
uint64_t params_offset = 0;
|
||||
};
|
||||
struct Scratch {
|
||||
vk::Buffer buffer = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct StorageBinding {
|
||||
vk::DescriptorBufferInfo info;
|
||||
uint32_t base = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] Scratch AllocateScratch(uint64_t size);
|
||||
[[nodiscard]] StorageBinding BindStorage(Result buffer, uint64_t size) const;
|
||||
[[nodiscard]] static uint32_t ConversionRows(uint64_t offset, uint64_t row_stride,
|
||||
uint64_t active, uint32_t remaining,
|
||||
uint64_t alignment, uint64_t max_range,
|
||||
uint32_t max_groups) noexcept;
|
||||
void DeferDestroy(Scratch scratch);
|
||||
void Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, uint64_t source_base, uint64_t target_base,
|
||||
std::vector<Dispatch>& dispatches);
|
||||
void Record(bool tile, vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
std::span<Dispatch> dispatches, bool clear_target);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
|
||||
void SwapBgra16(Result input, Result output, uint32_t pixels);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
StreamBuffer& m_stream_buffer;
|
||||
vk::DescriptorSetLayout m_descriptor_layout = nullptr;
|
||||
vk::PipelineLayout m_pipeline_layout = nullptr;
|
||||
std::array<vk::Pipeline, PipelineCount> m_pipelines {};
|
||||
vk::Pipeline m_d16_to_d24 = nullptr;
|
||||
vk::Pipeline m_d16_to_d32 = nullptr;
|
||||
vk::Pipeline m_d24_to_d16 = nullptr;
|
||||
vk::Pipeline m_d32_to_d16 = nullptr;
|
||||
vk::Pipeline m_swap_bgra16 = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user