mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
renderer: Add Vulkan renderer
This commit is contained in:
+98
-95
@@ -1,95 +1,98 @@
|
||||
[submodule "external/better-enums"]
|
||||
path = external/better-enums
|
||||
url = https://github.com/aantron/better-enums
|
||||
[submodule "external/boost"]
|
||||
path = external/boost
|
||||
url = https://github.com/Vita3K/ext-boost
|
||||
[submodule "external/capstone"]
|
||||
path = external/capstone
|
||||
url = https://github.com/aquynh/capstone.git
|
||||
[submodule "external/crypto-algorithms"]
|
||||
path = external/crypto-algorithms
|
||||
url = https://github.com/KorewaWatchful/crypto-algorithms
|
||||
[submodule "external/dirent"]
|
||||
path = external/dirent
|
||||
url = https://github.com/tronkko/dirent
|
||||
branch = v1.23
|
||||
[submodule "external/dlmalloc"]
|
||||
path = external/dlmalloc
|
||||
url = https://github.com/Vita3K/dlmalloc
|
||||
[submodule "external/elfio"]
|
||||
path = external/elfio
|
||||
url = https://github.com/serge1/ELFIO
|
||||
[submodule "external/ffmpeg"]
|
||||
path = external/ffmpeg
|
||||
url = https://github.com/Vita3K/ffmpeg-core.git
|
||||
[submodule "external/glslang"]
|
||||
path = external/glslang
|
||||
url = https://github.com/KhronosGroup/glslang
|
||||
[submodule "external/googletest"]
|
||||
path = external/googletest
|
||||
url = https://github.com/google/googletest
|
||||
[submodule "external/imgui"]
|
||||
path = external/imgui
|
||||
url = https://github.com/ocornut/imgui
|
||||
[submodule "external/imgui_club"]
|
||||
path = external/imgui_club
|
||||
url = https://github.com/ocornut/imgui_club
|
||||
[submodule "external/libfat16"]
|
||||
path = external/libfat16
|
||||
url = https://github.com/Vita3K/libfat16
|
||||
[submodule "external/nativefiledialog-cmake"]
|
||||
path = external/nativefiledialog-cmake
|
||||
url = https://github.com/Vita3K/nativefiledialog-cmake
|
||||
[submodule "external/printf"]
|
||||
path = external/printf
|
||||
url = https://github.com/Vita3K/printf
|
||||
[submodule "external/pugixml"]
|
||||
path = external/pugixml
|
||||
url = https://github.com/zeux/pugixml
|
||||
[submodule "external/sdl"]
|
||||
path = external/sdl
|
||||
url = https://github.com/Vita3K/sdl
|
||||
[submodule "external/sdl2-cmake-scripts"]
|
||||
path = external/sdl2-cmake-scripts
|
||||
url = https://github.com/tcbrindle/sdl2-cmake-scripts
|
||||
[submodule "external/spdlog"]
|
||||
path = external/spdlog
|
||||
url = https://github.com/gabime/spdlog
|
||||
branch = v1.x
|
||||
[submodule "external/SPIRV-Cross"]
|
||||
path = external/SPIRV-Cross
|
||||
url = https://github.com/KhronosGroup/SPIRV-Cross.git
|
||||
[submodule "external/stb"]
|
||||
path = external/stb
|
||||
url = https://github.com/nothings/stb
|
||||
[submodule "external/unicorn"]
|
||||
path = external/unicorn
|
||||
url = https://github.com/Vita3K/unicorn.git
|
||||
[submodule "external/vita-toolchain"]
|
||||
path = external/vita-toolchain
|
||||
url = https://github.com/vitasdk/vita-toolchain.git
|
||||
[submodule "external/VulkanMemoryAllocator"]
|
||||
path = external/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "external/yaml-cpp"]
|
||||
path = external/yaml-cpp
|
||||
url = https://github.com/jbeder/yaml-cpp
|
||||
[submodule "external/psvpfstools"]
|
||||
path = external/psvpfstools
|
||||
url = https://github.com/Vita3K/psvpfstools.git
|
||||
[submodule "external/xxHash"]
|
||||
path = external/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash
|
||||
[submodule "external/dynarmic"]
|
||||
path = external/dynarmic
|
||||
url = https://github.com/Vita3K/dynarmic.git
|
||||
[submodule "external/fmt"]
|
||||
path = external/fmt
|
||||
url = https://github.com/fmtlib/fmt.git
|
||||
[submodule "external/LibAtrac9"]
|
||||
path = external/LibAtrac9
|
||||
url = https://github.com/Thealexbarney/LibAtrac9/
|
||||
[submodule "external/tracy"]
|
||||
path = external/tracy
|
||||
url = https://github.com/wolfpld/tracy.git
|
||||
[submodule "external/better-enums"]
|
||||
path = external/better-enums
|
||||
url = https://github.com/aantron/better-enums
|
||||
[submodule "external/boost"]
|
||||
path = external/boost
|
||||
url = https://github.com/Vita3K/ext-boost
|
||||
[submodule "external/capstone"]
|
||||
path = external/capstone
|
||||
url = https://github.com/aquynh/capstone.git
|
||||
[submodule "external/crypto-algorithms"]
|
||||
path = external/crypto-algorithms
|
||||
url = https://github.com/KorewaWatchful/crypto-algorithms
|
||||
[submodule "external/dirent"]
|
||||
path = external/dirent
|
||||
url = https://github.com/tronkko/dirent
|
||||
branch = v1.23
|
||||
[submodule "external/dlmalloc"]
|
||||
path = external/dlmalloc
|
||||
url = https://github.com/Vita3K/dlmalloc
|
||||
[submodule "external/elfio"]
|
||||
path = external/elfio
|
||||
url = https://github.com/serge1/ELFIO
|
||||
[submodule "external/ffmpeg"]
|
||||
path = external/ffmpeg
|
||||
url = https://github.com/Vita3K/ffmpeg-core.git
|
||||
[submodule "external/glslang"]
|
||||
path = external/glslang
|
||||
url = https://github.com/KhronosGroup/glslang
|
||||
[submodule "external/googletest"]
|
||||
path = external/googletest
|
||||
url = https://github.com/google/googletest
|
||||
[submodule "external/imgui"]
|
||||
path = external/imgui
|
||||
url = https://github.com/ocornut/imgui
|
||||
[submodule "external/imgui_club"]
|
||||
path = external/imgui_club
|
||||
url = https://github.com/ocornut/imgui_club
|
||||
[submodule "external/libfat16"]
|
||||
path = external/libfat16
|
||||
url = https://github.com/Vita3K/libfat16
|
||||
[submodule "external/nativefiledialog-cmake"]
|
||||
path = external/nativefiledialog-cmake
|
||||
url = https://github.com/Vita3K/nativefiledialog-cmake
|
||||
[submodule "external/printf"]
|
||||
path = external/printf
|
||||
url = https://github.com/Vita3K/printf
|
||||
[submodule "external/pugixml"]
|
||||
path = external/pugixml
|
||||
url = https://github.com/zeux/pugixml
|
||||
[submodule "external/sdl"]
|
||||
path = external/sdl
|
||||
url = https://github.com/Vita3K/sdl
|
||||
[submodule "external/sdl2-cmake-scripts"]
|
||||
path = external/sdl2-cmake-scripts
|
||||
url = https://github.com/tcbrindle/sdl2-cmake-scripts
|
||||
[submodule "external/spdlog"]
|
||||
path = external/spdlog
|
||||
url = https://github.com/gabime/spdlog
|
||||
branch = v1.x
|
||||
[submodule "external/SPIRV-Cross"]
|
||||
path = external/SPIRV-Cross
|
||||
url = https://github.com/KhronosGroup/SPIRV-Cross.git
|
||||
[submodule "external/stb"]
|
||||
path = external/stb
|
||||
url = https://github.com/nothings/stb
|
||||
[submodule "external/unicorn"]
|
||||
path = external/unicorn
|
||||
url = https://github.com/Vita3K/unicorn.git
|
||||
[submodule "external/vita-toolchain"]
|
||||
path = external/vita-toolchain
|
||||
url = https://github.com/vitasdk/vita-toolchain.git
|
||||
[submodule "external/VulkanMemoryAllocator-Hpp"]
|
||||
path = external/VulkanMemoryAllocator-Hpp
|
||||
url = https://github.com/Macdu/VulkanMemoryAllocator-Hpp
|
||||
[submodule "external/Vulkan-Headers"]
|
||||
path = external/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "external/yaml-cpp"]
|
||||
path = external/yaml-cpp
|
||||
url = https://github.com/jbeder/yaml-cpp
|
||||
[submodule "external/psvpfstools"]
|
||||
path = external/psvpfstools
|
||||
url = https://github.com/Vita3K/psvpfstools.git
|
||||
[submodule "external/xxHash"]
|
||||
path = external/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash
|
||||
[submodule "external/dynarmic"]
|
||||
path = external/dynarmic
|
||||
url = https://github.com/Vita3K/dynarmic.git
|
||||
[submodule "external/fmt"]
|
||||
path = external/fmt
|
||||
url = https://github.com/fmtlib/fmt.git
|
||||
[submodule "external/LibAtrac9"]
|
||||
path = external/LibAtrac9
|
||||
url = https://github.com/Thealexbarney/LibAtrac9/
|
||||
[submodule "external/tracy"]
|
||||
path = external/tracy
|
||||
url = https://github.com/wolfpld/tracy.git
|
||||
|
||||
@@ -10,7 +10,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
option(USE_DISCORD_RICH_PRESENCE "Build Vita3K with Discord Rich Presence" ON)
|
||||
option(USE_VULKAN "Build Vita3K with Vulkan backend." OFF)
|
||||
option(USE_VITA3K_UPDATE "Build Vita3K with updater." ON)
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_LAUNCHER}" STREQUAL "")
|
||||
|
||||
Vendored
+6
-8
@@ -260,15 +260,13 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_
|
||||
endif()
|
||||
target_include_directories(CLI11 INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/cli11")
|
||||
|
||||
if (USE_VULKAN)
|
||||
find_package(Vulkan REQUIRED)
|
||||
add_library(vulkan INTERFACE)
|
||||
target_include_directories(vulkan INTERFACE ${Vulkan_INCLUDE_DIRS})
|
||||
target_link_libraries(vulkan INTERFACE ${Vulkan_LIBRARIES})
|
||||
add_library(vulkan INTERFACE)
|
||||
target_include_directories(vulkan INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include")
|
||||
|
||||
add_library(vma INTERFACE)
|
||||
target_include_directories(vma INTERFACE VulkanMemoryAllocator/src)
|
||||
endif()
|
||||
add_library(vma INTERFACE)
|
||||
target_include_directories(vma INTERFACE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/VulkanMemoryAllocator-Hpp/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/VulkanMemoryAllocator-Hpp/VulkanMemoryAllocator/include")
|
||||
|
||||
add_subdirectory(ffmpeg)
|
||||
add_subdirectory(psvpfstools)
|
||||
|
||||
+1
Submodule external/Vulkan-Headers added at 3be1df310b
Vendored
-1
Submodule external/VulkanMemoryAllocator deleted from 188a365ed4
+1
Submodule external/VulkanMemoryAllocator-Hpp added at 59b4757e69
Vendored
+1
-1
Submodule external/imgui updated: 55d35d8387...9aae45eb4a
Vendored
+1
-1
Submodule external/imgui_club updated: 02e679b7f4...d4cd9896e1
@@ -85,10 +85,6 @@ if (USE_DISCORD_RICH_PRESENCE)
|
||||
add_definitions(-DUSE_DISCORD)
|
||||
endif()
|
||||
|
||||
if (USE_VULKAN)
|
||||
add_definitions(-DUSE_VULKAN)
|
||||
endif()
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
@@ -129,6 +125,7 @@ add_subdirectory(touch)
|
||||
add_subdirectory(util)
|
||||
add_subdirectory(gdbstub)
|
||||
add_subdirectory(packages)
|
||||
add_subdirectory(vkutil)
|
||||
|
||||
add_executable(vita3k MACOSX_BUNDLE main.cpp interface.cpp interface.h performance.cpp)
|
||||
|
||||
|
||||
@@ -43,10 +43,8 @@
|
||||
|
||||
#include <gdbstub/functions.h>
|
||||
|
||||
#ifdef USE_VULKAN
|
||||
#include <renderer/vulkan/functions.h>
|
||||
#include <util/string_utils.h>
|
||||
#endif
|
||||
|
||||
#include <SDL_video.h>
|
||||
#include <SDL_vulkan.h>
|
||||
@@ -60,11 +58,11 @@ void update_viewport(EmuEnvState &state) {
|
||||
case renderer::Backend::OpenGL:
|
||||
SDL_GL_GetDrawableSize(state.window.get(), &w, &h);
|
||||
break;
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
SDL_Vulkan_GetDrawableSize(state.window.get(), &w, &h);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unimplemented backend render: {}.", static_cast<int>(state.renderer->current_backend));
|
||||
break;
|
||||
@@ -120,11 +118,9 @@ bool init(EmuEnvState &state, Config &cfg, const Root &root_paths) {
|
||||
state.pref_path = string_utils::utf_to_wide(state.cfg.pref_path);
|
||||
}
|
||||
|
||||
#ifdef USE_VULKAN
|
||||
if (string_utils::toupper(state.cfg.backend_renderer) == "VULKAN")
|
||||
state.backend_renderer = renderer::Backend::Vulkan;
|
||||
else
|
||||
#endif
|
||||
state.backend_renderer = renderer::Backend::OpenGL;
|
||||
|
||||
int window_type = 0;
|
||||
@@ -132,11 +128,11 @@ bool init(EmuEnvState &state, Config &cfg, const Root &root_paths) {
|
||||
case renderer::Backend::OpenGL:
|
||||
window_type = SDL_WINDOW_OPENGL;
|
||||
break;
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
window_type = SDL_WINDOW_VULKAN;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unimplemented backend render: {}.", state.cfg.backend_renderer);
|
||||
break;
|
||||
@@ -194,11 +190,11 @@ bool init(EmuEnvState &state, Config &cfg, const Root &root_paths) {
|
||||
case renderer::Backend::OpenGL:
|
||||
error_dialog("Could not create OpenGL context!\nDoes your GPU at least support OpenGL 4.4?", nullptr);
|
||||
break;
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
error_dialog("Could not create Vulkan context!");
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
error_dialog(fmt::format("Unknown backend render: {}.", state.cfg.backend_renderer));
|
||||
break;
|
||||
@@ -212,13 +208,6 @@ bool init(EmuEnvState &state, Config &cfg, const Root &root_paths) {
|
||||
|
||||
void destroy(EmuEnvState &emuenv, ImGui_State *imgui) {
|
||||
ImGui_ImplSdl_Shutdown(imgui);
|
||||
#ifdef USE_VULKAN
|
||||
// I'm explicitly destroying VulkanState in app::destroy instead of a destructor because I want to ensure an order.
|
||||
// Objects in Vulkan should be destroyed in reverse order than they were created.
|
||||
if (emuenv.renderer->current_backend == renderer::Backend::Vulkan) {
|
||||
renderer::vulkan::close(emuenv.renderer);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DISCORD
|
||||
discordrpc::shutdown();
|
||||
|
||||
@@ -58,6 +58,8 @@ enum PerfomanceOverleyPosition {
|
||||
code(bool, "show-live-area-screen", true, show_live_area_screen) \
|
||||
code(int, "icon-size", 64, icon_size) \
|
||||
code(bool, "archive-log", false, archive_log) \
|
||||
code(std::string, "backend-renderer", "OpenGL", backend_renderer) \
|
||||
code(int, "gpu-idx", 0, gpu_idx) \
|
||||
code(int, "resolution-multiplier", 1, resolution_multiplier) \
|
||||
code(bool, "disable-surface-sync", false, disable_surface_sync) \
|
||||
code(bool, "enable-fxaa", false, enable_fxaa) \
|
||||
@@ -86,7 +88,6 @@ enum PerfomanceOverleyPosition {
|
||||
code(bool, "performance-overlay", false, performance_overlay) \
|
||||
code(int, "perfomance-overlay-detail", static_cast<int>(MINIMUM), performance_overlay_detail) \
|
||||
code(int, "perfomance-overlay-position", static_cast<int>(TOP_LEFT), performance_overlay_position) \
|
||||
code(std::string, "backend-renderer", "OpenGL", backend_renderer) \
|
||||
code(int, "keyboard-button-select", 229, keyboard_button_select) \
|
||||
code(int, "keyboard-button-start", 40, keyboard_button_start) \
|
||||
code(int, "keyboard-button-up", 82, keyboard_button_up) \
|
||||
|
||||
@@ -28,11 +28,9 @@ typedef std::array<char, 65> Sha256HashText;
|
||||
void hex_buf(const std::uint8_t *hash, char *dst, const std::size_t source_size);
|
||||
|
||||
template <size_t N>
|
||||
constexpr std::array<char, (N * 2) + 1> hex(const std::array<uint8_t, N> &hash) {
|
||||
std::array<char, (N * 2) + 1> dst;
|
||||
hex_buf(hash.data(), &dst[0], N);
|
||||
const std::string hex_string(const std::array<uint8_t, N> &hash) {
|
||||
std::string dst(2 * N + 1, 0);
|
||||
hex_buf(hash.data(), dst.data(), N);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
std::string hex_string(const std::string &hash);
|
||||
|
||||
@@ -45,11 +45,3 @@ void hex_buf(const std::uint8_t *hash, char *dst, const std::size_t source_size)
|
||||
|
||||
dst[j] = '\0';
|
||||
}
|
||||
|
||||
std::string hex_string(const std::string &hash) {
|
||||
std::string dst = "";
|
||||
dst.resize(hash.size() * 2 + 1);
|
||||
hex_buf(reinterpret_cast<const std::uint8_t *>(hash.data()), &dst[0], hash.size());
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ struct FeatureState {
|
||||
bool support_texture_barrier = false; ///< Second option for blending. Slower but work on 3 vendors.
|
||||
bool direct_fragcolor = false;
|
||||
bool spirv_shader = false;
|
||||
bool preserve_f16_nan_as_u16 = false; ///< Emit store of 4xU16 to draw buffer 1. This buffer is expected to be U16U16U16U16, which can be casted to F16F16F16F16. This is to preserve some drivers's behaviour of casting NaN to default value when store in framebuffer, not keeping its original value.
|
||||
bool support_get_texture_sub_image = false;
|
||||
bool preserve_f16_nan_as_u16 = false; ///< Emit store of 4xU16 to draw buffer 1. This buffer is expected to be U16U16U16U16, which can be casted to F16F16F16F16. This is to preserve some drivers's behaviour of casting NaN to default value when store in framebuffer, not keeping its original value.
|
||||
bool support_unknown_format = false;
|
||||
bool use_mask_bit = false; ///< Is the mask bit (1 per sample) emulated ? It is only used in homebrews afaik
|
||||
|
||||
bool is_programmable_blending_supported() const {
|
||||
return support_shader_interlock || support_texture_barrier || direct_fragcolor;
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
if (USE_VULKAN)
|
||||
set(IMGUI_IMPL_VULKAN_SOURCES
|
||||
include/gui/imgui_impl_sdl_vulkan.h
|
||||
|
||||
src/imgui_impl_sdl_vulkan.cpp
|
||||
)
|
||||
else()
|
||||
set(IMGUI_IMPL_VULKAN_SOURCES "")
|
||||
endif()
|
||||
|
||||
add_library(
|
||||
gui
|
||||
STATIC
|
||||
include/gui/functions.h
|
||||
include/gui/imgui_impl_sdl_gl3.h
|
||||
include/gui/imgui_impl_sdl_state.h
|
||||
include/gui/imgui_impl_sdl_vulkan.h
|
||||
include/gui/imgui_impl_sdl.h
|
||||
include/gui/state.h
|
||||
src/app_context_menu.cpp
|
||||
@@ -28,7 +19,7 @@ add_library(
|
||||
src/home_screen.cpp
|
||||
src/ime.cpp
|
||||
src/imgui_impl_sdl_gl3.cpp
|
||||
${IMGUI_IMPL_VULKAN_SOURCES}
|
||||
src/imgui_impl_sdl_vulkan.cpp
|
||||
src/imgui_impl_sdl.cpp
|
||||
src/information_bar.cpp
|
||||
src/initial_setup.cpp
|
||||
@@ -60,5 +51,5 @@ add_library(
|
||||
|
||||
target_include_directories(gui PUBLIC include ${CMAKE_SOURCE_DIR}/vita3k)
|
||||
target_link_libraries(gui PUBLIC app config dialog emuenv ime imgui glutil lang np)
|
||||
target_link_libraries(gui PRIVATE ctrl kernel miniz nativefiledialog psvpfsparser pugixml::pugixml stb renderer packages sdl2)
|
||||
target_link_libraries(gui PRIVATE ctrl kernel miniz nativefiledialog psvpfsparser pugixml::pugixml stb renderer packages sdl2 vkutil)
|
||||
target_link_libraries(gui PUBLIC tracy)
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
// dear imgui: Renderer Backend for Vulkan
|
||||
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
|
||||
|
||||
// Temporary Vulkan ImGui Implementation
|
||||
// Implemented features:
|
||||
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices.
|
||||
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions.
|
||||
|
||||
// Important: on 32-bit systems, user texture binding is only supported if your imconfig file has '#define ImTextureID ImU64'.
|
||||
// See imgui_impl_vulkan.cpp file for details.
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
|
||||
// Read online: https://github.com/ocornut/imgui/tree/master/docs
|
||||
|
||||
// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification.
|
||||
// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
|
||||
|
||||
// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app.
|
||||
// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h.
|
||||
// You will use those if you want to use this rendering backend in your engine/app.
|
||||
// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by
|
||||
// the backend itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code.
|
||||
// Read comments in imgui_impl_vulkan.h.
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -27,41 +33,69 @@
|
||||
|
||||
typedef union SDL_Event SDL_Event;
|
||||
|
||||
// Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData()
|
||||
// [Please zero-clear before use!]
|
||||
struct ImGui_ImplVulkanH_FrameRenderBuffers {
|
||||
vma::Allocation VertexBufferAllocation;
|
||||
vma::Allocation IndexBufferAllocation;
|
||||
vk::DeviceSize VertexBufferSize;
|
||||
vk::DeviceSize IndexBufferSize;
|
||||
vk::Buffer VertexBuffer;
|
||||
vk::Buffer IndexBuffer;
|
||||
};
|
||||
|
||||
// Each viewport will hold 1 ImGui_ImplVulkanH_WindowRenderBuffers
|
||||
// [Please zero-clear before use!]
|
||||
struct ImGui_ImplVulkanH_WindowRenderBuffers {
|
||||
uint32_t Index;
|
||||
uint32_t Count;
|
||||
ImGui_ImplVulkanH_FrameRenderBuffers *FrameRenderBuffers = nullptr;
|
||||
};
|
||||
|
||||
struct TextureState {
|
||||
static constexpr uint32_t nb_descriptor_sets = 1024;
|
||||
|
||||
vma::Allocation allocation;
|
||||
vk::Image image;
|
||||
vk::ImageView image_view;
|
||||
vk::DescriptorSet descriptor_set;
|
||||
uint64_t last_frame_used = 0;
|
||||
};
|
||||
|
||||
struct ImGui_VulkanState : public ImGui_State {
|
||||
vk::ShaderModule vertex_module;
|
||||
vk::ShaderModule fragment_module;
|
||||
vk::RenderPass RenderPass{};
|
||||
vk::DeviceSize BufferMemoryAlignment = 256;
|
||||
vk::PipelineCreateFlags PipelineCreateFlags{};
|
||||
vk::DescriptorSetLayout DescriptorSetLayout{};
|
||||
vk::PipelineLayout PipelineLayout{};
|
||||
vk::Pipeline Pipeline{};
|
||||
uint32_t Subpass{};
|
||||
vk::ShaderModule ShaderModuleVert{};
|
||||
vk::ShaderModule ShaderModuleFrag{};
|
||||
|
||||
vk::RenderPass renderpass;
|
||||
vk::Framebuffer framebuffers[2];
|
||||
vk::DescriptorSetLayout matrix_layout;
|
||||
vk::DescriptorSetLayout sampler_layout;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
vk::DescriptorSet matrix_set;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipeline;
|
||||
// Font data
|
||||
vk::Sampler FontSampler{};
|
||||
TextureState *Font;
|
||||
vma::Allocation UploadBufferAllocation{};
|
||||
vk::Buffer UploadBuffer{};
|
||||
vk::CommandBuffer CommandBuffer{};
|
||||
|
||||
vk::Sampler sampler;
|
||||
ImTextureID font_texture{};
|
||||
VmaAllocation draw_allocation = VK_NULL_HANDLE;
|
||||
vk::Buffer draw_buffer;
|
||||
size_t draw_buffer_vertices = 0;
|
||||
VmaAllocation index_allocation = VK_NULL_HANDLE;
|
||||
vk::Buffer index_buffer;
|
||||
size_t index_buffer_indices = 0;
|
||||
VmaAllocation transformation_allocation = VK_NULL_HANDLE;
|
||||
vk::Buffer transformation_buffer;
|
||||
vk::DescriptorPool DescriptorPool;
|
||||
std::vector<vk::DescriptorSet> DescriptorSets;
|
||||
uint32_t DescriptorIdx = 0;
|
||||
|
||||
vk::CommandBuffer command_buffer;
|
||||
uint64_t frame_timestamp = 1;
|
||||
|
||||
vk::Semaphore image_acquired_semaphore;
|
||||
vk::Semaphore render_complete_semaphore;
|
||||
// Render buffers
|
||||
ImGui_ImplVulkanH_WindowRenderBuffers MainWindowRenderBuffers;
|
||||
};
|
||||
|
||||
IMGUI_API ImGui_VulkanState *ImGui_ImplSdlVulkan_Init(renderer::State *renderer, SDL_Window *window, const std::string &base_path);
|
||||
IMGUI_API void ImGui_ImplSdlVulkan_Shutdown(ImGui_VulkanState &state);
|
||||
IMGUI_API void ImGui_ImplSdlVulkan_RenderDrawData(ImGui_VulkanState &state);
|
||||
|
||||
IMGUI_API ImTextureID ImGui_ImplSdlVulkan_CreateTexture(ImGui_VulkanState &state, void *data, int width, int height);
|
||||
// if is_alpha is set to true, the texture only has one alpha component, the other channels map to 1
|
||||
IMGUI_API ImTextureID ImGui_ImplSdlVulkan_CreateTexture(ImGui_VulkanState &state, void *data, int width, int height, bool is_alpha = false);
|
||||
IMGUI_API void ImGui_ImplSdlVulkan_DeleteTexture(ImGui_VulkanState &state, ImTextureID texture);
|
||||
|
||||
// Use if you want to reset your rendering device without losing ImGui state.
|
||||
|
||||
@@ -670,7 +670,6 @@ void draw_begin(GuiState &gui, EmuEnvState &emuenv) {
|
||||
void draw_end(GuiState &gui, SDL_Window *window) {
|
||||
ImGui::Render();
|
||||
ImGui_ImplSdl_RenderDrawData(gui.imgui_state.get());
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
void draw_live_area(GuiState &gui, EmuEnvState &emuenv) {
|
||||
|
||||
@@ -17,39 +17,97 @@
|
||||
|
||||
#include <gui/imgui_impl_sdl.h>
|
||||
#include <gui/imgui_impl_sdl_gl3.h>
|
||||
#ifdef USE_VULKAN
|
||||
#include <gui/imgui_impl_sdl_vulkan.h>
|
||||
#endif
|
||||
|
||||
#include <renderer/state.h>
|
||||
#include <util/log.h>
|
||||
|
||||
#include <SDL.h>
|
||||
#ifdef USE_VULKAN
|
||||
#include <SDL_syswm.h>
|
||||
#include <SDL_vulkan.h>
|
||||
#endif
|
||||
|
||||
static const char *ImGui_ImplSdl_GetClipboardText(void *) {
|
||||
return SDL_GetClipboardText();
|
||||
}
|
||||
|
||||
static void ImGui_ImplSdl_SetClipboardText(void *, const char *text) {
|
||||
SDL_SetClipboardText(text);
|
||||
}
|
||||
|
||||
IMGUI_API ImGui_State *ImGui_ImplSdl_Init(renderer::State *renderer, SDL_Window *window, const std::string &base_path) {
|
||||
ImGui_State *state;
|
||||
|
||||
switch (renderer->current_backend) {
|
||||
case renderer::Backend::OpenGL:
|
||||
return dynamic_cast<ImGui_State *>(ImGui_ImplSdlGL3_Init(renderer, window, nullptr));
|
||||
#ifdef USE_VULKAN
|
||||
state = reinterpret_cast<ImGui_State *>(ImGui_ImplSdlGL3_Init(renderer, window, nullptr));
|
||||
break;
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
return dynamic_cast<ImGui_State *>(ImGui_ImplSdlVulkan_Init(renderer, window, base_path));
|
||||
#endif
|
||||
state = reinterpret_cast<ImGui_State *>(ImGui_ImplSdlVulkan_Init(renderer, window, base_path));
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(renderer->current_backend));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Setup back-end capabilities flags
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
|
||||
|
||||
// Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array.
|
||||
io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;
|
||||
io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;
|
||||
io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT;
|
||||
io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP;
|
||||
io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN;
|
||||
io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP;
|
||||
io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN;
|
||||
io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME;
|
||||
io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END;
|
||||
io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT;
|
||||
io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE;
|
||||
io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE;
|
||||
io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE;
|
||||
io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN;
|
||||
io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE;
|
||||
io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A;
|
||||
io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C;
|
||||
io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V;
|
||||
io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X;
|
||||
io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y;
|
||||
io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z;
|
||||
|
||||
io.SetClipboardTextFn = ImGui_ImplSdl_SetClipboardText;
|
||||
io.GetClipboardTextFn = ImGui_ImplSdl_GetClipboardText;
|
||||
io.ClipboardUserData = NULL;
|
||||
|
||||
state->mouse_cursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
|
||||
state->mouse_cursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
|
||||
|
||||
// TODO: is this needed/useful ?
|
||||
#ifdef _WIN32
|
||||
SDL_SysWMinfo wmInfo;
|
||||
SDL_VERSION(&wmInfo.version);
|
||||
SDL_GetWindowWMInfo(state->window, &wmInfo);
|
||||
io.ImeWindowHandle = wmInfo.info.win.window;
|
||||
#endif
|
||||
|
||||
return state;
|
||||
}
|
||||
IMGUI_API void ImGui_ImplSdl_Shutdown(ImGui_State *state) {
|
||||
switch (state->renderer->current_backend) {
|
||||
case renderer::Backend::OpenGL:
|
||||
return ImGui_ImplSdlGL3_Shutdown(dynamic_cast<ImGui_GLState &>(*state));
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
return ImGui_ImplSdlVulkan_Shutdown(dynamic_cast<ImGui_VulkanState &>(*state));
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(state->renderer->current_backend));
|
||||
}
|
||||
@@ -114,10 +172,9 @@ IMGUI_API void ImGui_ImplSdl_RenderDrawData(ImGui_State *state) {
|
||||
switch (state->renderer->current_backend) {
|
||||
case renderer::Backend::OpenGL:
|
||||
return ImGui_ImplSdlGL3_RenderDrawData(dynamic_cast<ImGui_GLState &>(*state));
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
return ImGui_ImplSdlVulkan_RenderDrawData(dynamic_cast<ImGui_VulkanState &>(*state));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,11 +230,11 @@ IMGUI_API void ImGui_ImplSdl_GetDrawableSize(ImGui_State *state, int &width, int
|
||||
case renderer::Backend::OpenGL:
|
||||
SDL_GL_GetDrawableSize(state->window, &width, &height);
|
||||
break;
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
SDL_Vulkan_GetDrawableSize(state->window, &width, &height);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(state->renderer->current_backend));
|
||||
}
|
||||
@@ -187,10 +244,10 @@ IMGUI_API ImTextureID ImGui_ImplSdl_CreateTexture(ImGui_State *state, void *data
|
||||
switch (state->renderer->current_backend) {
|
||||
case renderer::Backend::OpenGL:
|
||||
return ImGui_ImplSdlGL3_CreateTexture(data, width, height);
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
return ImGui_ImplSdlVulkan_CreateTexture(dynamic_cast<ImGui_VulkanState &>(*state), data, width, height);
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(state->renderer->current_backend));
|
||||
return (void *)0;
|
||||
@@ -201,10 +258,10 @@ IMGUI_API void ImGui_ImplSdl_DeleteTexture(ImGui_State *state, ImTextureID textu
|
||||
switch (state->renderer->current_backend) {
|
||||
case renderer::Backend::OpenGL:
|
||||
return ImGui_ImplSdlGL3_DeleteTexture(texture);
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
return ImGui_ImplSdlVulkan_DeleteTexture(dynamic_cast<ImGui_VulkanState &>(*state), texture);
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(state->renderer->current_backend));
|
||||
}
|
||||
@@ -215,10 +272,10 @@ IMGUI_API void ImGui_ImplSdl_InvalidateDeviceObjects(ImGui_State *state) {
|
||||
switch (state->renderer->current_backend) {
|
||||
case renderer::Backend::OpenGL:
|
||||
return ImGui_ImplSdlGL3_InvalidateDeviceObjects(dynamic_cast<ImGui_GLState &>(*state));
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
return ImGui_ImplSdlVulkan_InvalidateDeviceObjects(dynamic_cast<ImGui_VulkanState &>(*state));
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(state->renderer->current_backend));
|
||||
}
|
||||
@@ -227,10 +284,10 @@ IMGUI_API bool ImGui_ImplSdl_CreateDeviceObjects(ImGui_State *state) {
|
||||
switch (state->renderer->current_backend) {
|
||||
case renderer::Backend::OpenGL:
|
||||
return ImGui_ImplSdlGL3_CreateDeviceObjects(dynamic_cast<ImGui_GLState &>(*state));
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case renderer::Backend::Vulkan:
|
||||
return ImGui_ImplSdlVulkan_CreateDeviceObjects(dynamic_cast<ImGui_VulkanState &>(*state));
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(state->renderer->current_backend));
|
||||
return false;
|
||||
|
||||
@@ -207,14 +207,6 @@ void ImGui_ImplSdlGL3_RenderDrawData(ImGui_GLState &state) {
|
||||
glColorMask(last_color_mask[0], last_color_mask[1], last_color_mask[2], last_color_mask[3]);
|
||||
}
|
||||
|
||||
static const char *ImGui_ImplSdlGL3_GetClipboardText(void *) {
|
||||
return SDL_GetClipboardText();
|
||||
}
|
||||
|
||||
static void ImGui_ImplSdlGL3_SetClipboardText(void *, const char *text) {
|
||||
SDL_SetClipboardText(text);
|
||||
}
|
||||
|
||||
void ImGui_ImplSdlGL3_CreateFontsTexture(ImGui_GLState &state) {
|
||||
// Build texture atlas
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
@@ -345,52 +337,6 @@ IMGUI_API ImGui_GLState *ImGui_ImplSdlGL3_Init(renderer::State *renderer, SDL_Wi
|
||||
strcpy(state->glsl_version, glsl_version);
|
||||
strcat(state->glsl_version, "\n");
|
||||
|
||||
// Setup back-end capabilities flags
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
|
||||
|
||||
// Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array.
|
||||
io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;
|
||||
io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;
|
||||
io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT;
|
||||
io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP;
|
||||
io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN;
|
||||
io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP;
|
||||
io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN;
|
||||
io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME;
|
||||
io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END;
|
||||
io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT;
|
||||
io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE;
|
||||
io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE;
|
||||
io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE;
|
||||
io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN;
|
||||
io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE;
|
||||
io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A;
|
||||
io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C;
|
||||
io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V;
|
||||
io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X;
|
||||
io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y;
|
||||
io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z;
|
||||
|
||||
io.SetClipboardTextFn = ImGui_ImplSdlGL3_SetClipboardText;
|
||||
io.GetClipboardTextFn = ImGui_ImplSdlGL3_GetClipboardText;
|
||||
io.ClipboardUserData = NULL;
|
||||
|
||||
state->mouse_cursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
|
||||
state->mouse_cursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
|
||||
state->mouse_cursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
|
||||
|
||||
#ifdef _WIN32
|
||||
SDL_SysWMinfo wmInfo;
|
||||
SDL_VERSION(&wmInfo.version);
|
||||
SDL_GetWindowWMInfo(state->window, &wmInfo);
|
||||
io.ImeWindowHandle = wmInfo.info.win.window;
|
||||
#endif
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -470,16 +470,27 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
|
||||
if (ImGui::BeginTabItem("GPU")) {
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
static const char *LIST_BACKEND_RENDERER[] = { "OpenGL", "Vulkan" };
|
||||
if (ImGui::Combo("Backend Renderer (Reboot to apply)", reinterpret_cast<int *>(&emuenv.backend_renderer), LIST_BACKEND_RENDERER, IM_ARRAYSIZE(LIST_BACKEND_RENDERER)))
|
||||
emuenv.cfg.backend_renderer = LIST_BACKEND_RENDERER[int(emuenv.backend_renderer)];
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Select your preferred backend renderer.");
|
||||
ImGui::Spacing();
|
||||
|
||||
const bool is_vulkan = (emuenv.backend_renderer == renderer::Backend::Vulkan);
|
||||
if (is_vulkan) {
|
||||
const std::vector<std::string> gpu_list_str = emuenv.renderer->get_gpu_list();
|
||||
// must convert to a vector of char*
|
||||
std::vector<const char *> gpu_list;
|
||||
for (const auto &gpu : gpu_list_str)
|
||||
gpu_list.push_back(gpu.c_str());
|
||||
if (ImGui::Combo("GPU (Reboot to apply)", &emuenv.cfg.gpu_idx, gpu_list.data(), gpu_list.size()))
|
||||
ImGui::SetTooltip("Select the GPU Vita3K should run on.");
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
#endif
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - (ImGui::CalcTextSize("Internal Resolution Upscaling").x / 2.f));
|
||||
ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "Internal Resolution Upscaling");
|
||||
ImGui::Spacing();
|
||||
@@ -502,25 +513,27 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
|
||||
config.disable_surface_sync = false;
|
||||
}
|
||||
ImGui::PopID();
|
||||
if ((config.resolution_multiplier == 1) && !config.disable_surface_sync)
|
||||
if (!emuenv.io.title_id.empty() || ((config.resolution_multiplier == 1) && !config.disable_surface_sync))
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Spacing();
|
||||
const auto res_scal = fmt::format("{}x{}", 960 * config.resolution_multiplier, 544 * config.resolution_multiplier);
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - (ImGui::CalcTextSize(res_scal.c_str()).x / 2.f) - (35.f * emuenv.dpi_scale));
|
||||
ImGui::Text("%s", res_scal.c_str());
|
||||
if (!emuenv.io.title_id.empty())
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Checkbox("Disable surface sync", &config.disable_surface_sync);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Speed hack, check the box to disable surface syncing between CPU and GPU.\nSurface syncing is needed by a few games.\nGive a big performance boost if disabled (in particular when upscaling is on).");
|
||||
ImGui::Spacing();
|
||||
if (!is_vulkan) {
|
||||
ImGui::Checkbox("Disable surface sync", &config.disable_surface_sync);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Speed hack, check the box to disable surface syncing between CPU and GPU.\nSurface syncing is needed by a few games.\nGive a big performance boost if disabled (in particular when upscaling is on).");
|
||||
ImGui::Spacing();
|
||||
}
|
||||
ImGui::Checkbox("Enable anti-aliasing (FXAA)", &config.enable_fxaa);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Anti-aliasing is a technique for smoothing out jagged edges.\n FXAA comes at almost no performance cost but makes games look slightly blurry.");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("V-Sync", &config.v_sync);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Disabling V-Sync can fix the speed issue in some games.\nIt is recommended to keep it enabled to avoid tearing.");
|
||||
if (!is_vulkan) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("V-Sync", &config.v_sync);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Disabling V-Sync can fix the speed issue in some games.\nIt is recommended to keep it enabled to avoid tearing.");
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
@@ -90,4 +90,6 @@ const char *get_container_name(const std::uint16_t idx);
|
||||
|
||||
int get_uniform_buffer_base(const SceGxmProgram &program, const SceGxmProgramParameter ¶meter);
|
||||
|
||||
uint16_t get_texture_count(const SceGxmProgram &program_gxp);
|
||||
|
||||
} // namespace gxp
|
||||
|
||||
@@ -158,7 +158,7 @@ enum SceGxmPolygonMode {
|
||||
SCE_GXM_POLYGON_MODE_TRIANGLE_POINT = 0x00030000u
|
||||
};
|
||||
|
||||
enum SceGxmPrimitiveType {
|
||||
enum SceGxmPrimitiveType : uint32_t {
|
||||
SCE_GXM_PRIMITIVE_TRIANGLES = 0x00000000u,
|
||||
SCE_GXM_PRIMITIVE_LINES = 0x04000000u,
|
||||
SCE_GXM_PRIMITIVE_POINTS = 0x08000000u,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <util/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
namespace gxp {
|
||||
|
||||
@@ -380,4 +381,42 @@ const char *get_container_name(const std::uint16_t idx) {
|
||||
|
||||
return "INVALID ";
|
||||
}
|
||||
|
||||
uint16_t get_texture_count(const SceGxmProgram &program_gxp) {
|
||||
const auto parameters = gxp::program_parameters(program_gxp);
|
||||
|
||||
int max_texture_index = -1;
|
||||
for (uint32_t i = 0; i < program_gxp.parameter_count; ++i) {
|
||||
const auto parameter = parameters[i];
|
||||
if (parameter.category == SCE_GXM_PARAMETER_CATEGORY_SAMPLER) {
|
||||
max_texture_index = std::max(max_texture_index, parameter.resource_index);
|
||||
}
|
||||
}
|
||||
|
||||
// symbols may be stripped, look for an anonymous texture
|
||||
auto vertex_varyings_ptr = program_gxp.vertex_varyings();
|
||||
const SceGxmProgramAttributeDescriptor *descriptor = reinterpret_cast<const SceGxmProgramAttributeDescriptor *>(
|
||||
reinterpret_cast<const std::uint8_t *>(&vertex_varyings_ptr->vertex_outputs1) + vertex_varyings_ptr->vertex_outputs1);
|
||||
|
||||
for (uint16_t i = 0; i < vertex_varyings_ptr->varyings_count; i++, descriptor++) {
|
||||
const uint32_t tex_coord_index = (descriptor->attribute_info & 0x40F);
|
||||
if (tex_coord_index == 0xF)
|
||||
continue;
|
||||
max_texture_index = std::max<int>(max_texture_index, descriptor->resource_index);
|
||||
}
|
||||
|
||||
// also look for an anonymous sampler
|
||||
const SceGxmProgramParameterContainer *container = gxp::get_container_by_index(program_gxp, 19);
|
||||
if (container) {
|
||||
const SceGxmDependentSampler *dependent_samplers = reinterpret_cast<const SceGxmDependentSampler *>(reinterpret_cast<const std::uint8_t *>(&program_gxp.dependent_sampler_offset)
|
||||
+ program_gxp.dependent_sampler_offset);
|
||||
|
||||
for (uint32_t i = 0; i < program_gxp.dependent_sampler_count; i++) {
|
||||
const uint16_t rsc_index = dependent_samplers[i].resource_index_layout_offset / 4;
|
||||
max_texture_index = std::max<int>(max_texture_index, rsc_index);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<uint16_t>(max_texture_index + 1);
|
||||
}
|
||||
} // namespace gxp
|
||||
|
||||
+22
-17
@@ -31,7 +31,8 @@
|
||||
#include <packages/pkg.h>
|
||||
#include <packages/sfo.h>
|
||||
#include <renderer/functions.h>
|
||||
#include <renderer/gl/functions.h>
|
||||
#include <renderer/shaders.h>
|
||||
#include <renderer/state.h>
|
||||
#include <shader/spirv_recompiler.h>
|
||||
#include <util/log.h>
|
||||
#include <util/string_utils.h>
|
||||
@@ -200,6 +201,7 @@ int main(int argc, char *argv[]) {
|
||||
gui::draw_begin(gui, emuenv);
|
||||
gui::draw_initial_setup(gui, emuenv);
|
||||
gui::draw_end(gui, emuenv.window.get());
|
||||
emuenv.renderer->swap_window(emuenv.window.get());
|
||||
} else
|
||||
return QuitRequested;
|
||||
}
|
||||
@@ -285,6 +287,7 @@ int main(int argc, char *argv[]) {
|
||||
gui::draw_ui(gui, emuenv);
|
||||
|
||||
gui::draw_end(gui, emuenv.window.get());
|
||||
emuenv.renderer->swap_window(emuenv.window.get());
|
||||
FrameMark; // Tracy - Frame end mark for UI rendering loop
|
||||
} else {
|
||||
return QuitRequested;
|
||||
@@ -347,20 +350,21 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
gui.live_area.information_bar = false;
|
||||
|
||||
// Pre-Compile Shader only for glsl, spriv is broken
|
||||
if (!emuenv.cfg.spirv_shader) {
|
||||
auto &glstate = static_cast<renderer::gl::GLState &>(*emuenv.renderer);
|
||||
if (renderer::gl::get_shaders_cache_hashs(glstate, emuenv.base_path.c_str(), emuenv.io.title_id.c_str(), emuenv.self_name.c_str()) && cfg.shader_cache) {
|
||||
for (const auto &hash : glstate.shaders_cache_hashs) {
|
||||
gui::draw_begin(gui, emuenv);
|
||||
draw_app_background(gui, emuenv);
|
||||
// Pre-Compile Shaders
|
||||
emuenv.renderer->base_path = emuenv.base_path.c_str();
|
||||
emuenv.renderer->title_id = emuenv.io.title_id.c_str();
|
||||
emuenv.renderer->self_name = emuenv.self_name.c_str();
|
||||
if (renderer::get_shaders_cache_hashs(*emuenv.renderer) && cfg.shader_cache) {
|
||||
for (const auto &hash : emuenv.renderer->shaders_cache_hashs) {
|
||||
gui::draw_begin(gui, emuenv);
|
||||
draw_app_background(gui, emuenv);
|
||||
|
||||
renderer::gl::pre_compile_program(glstate, emuenv.base_path.c_str(), emuenv.io.title_id.c_str(), emuenv.self_name.c_str(), hash);
|
||||
gui::draw_pre_compiling_shaders_progress(gui, emuenv, uint32_t(glstate.shaders_cache_hashs.size()));
|
||||
emuenv.renderer->precompile_shader(hash);
|
||||
gui::draw_pre_compiling_shaders_progress(gui, emuenv, uint32_t(emuenv.renderer->shaders_cache_hashs.size()));
|
||||
|
||||
gui::draw_end(gui, emuenv.window.get());
|
||||
SDL_SetWindowTitle(emuenv.window.get(), fmt::format("{} | {} ({}) | Please wait, compiling shaders...", window_title, emuenv.current_app_title, emuenv.io.title_id).c_str());
|
||||
}
|
||||
gui::draw_end(gui, emuenv.window.get());
|
||||
emuenv.renderer->swap_window(emuenv.window.get());
|
||||
SDL_SetWindowTitle(emuenv.window.get(), fmt::format("{} | {} ({}) | Please wait, compiling shaders...", window_title, emuenv.current_app_title, emuenv.io.title_id).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,8 +373,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
while (emuenv.frame_count == 0 && !emuenv.load_exec) {
|
||||
// Driver acto!
|
||||
renderer::process_batches(*emuenv.renderer.get(), emuenv.renderer->features, emuenv.mem, emuenv.cfg, emuenv.base_path.c_str(),
|
||||
emuenv.io.title_id.c_str(), emuenv.self_name.c_str());
|
||||
renderer::process_batches(*emuenv.renderer.get(), emuenv.renderer->features, emuenv.mem, emuenv.cfg);
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> guard(emuenv.display.display_info_mutex);
|
||||
@@ -384,6 +387,7 @@ int main(int argc, char *argv[]) {
|
||||
draw_app_background(gui, emuenv);
|
||||
|
||||
gui::draw_end(gui, emuenv.window.get());
|
||||
emuenv.renderer->swap_window(emuenv.window.get());
|
||||
|
||||
SDL_SetWindowTitle(emuenv.window.get(), fmt::format("{} | {} ({}) | Please wait, loading...", window_title, emuenv.current_app_title, emuenv.io.title_id).c_str());
|
||||
}
|
||||
@@ -391,8 +395,7 @@ int main(int argc, char *argv[]) {
|
||||
while (handle_events(emuenv, gui) && !emuenv.load_exec) {
|
||||
ZoneScopedN("Game rendering"); // Tracy - Track game rendering loop scope
|
||||
// Driver acto!
|
||||
renderer::process_batches(*emuenv.renderer.get(), emuenv.renderer->features, emuenv.mem, emuenv.cfg, emuenv.base_path.c_str(),
|
||||
emuenv.io.title_id.c_str(), emuenv.self_name.c_str());
|
||||
renderer::process_batches(*emuenv.renderer.get(), emuenv.renderer->features, emuenv.mem, emuenv.cfg);
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> guard(emuenv.display.display_info_mutex);
|
||||
@@ -419,6 +422,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
gui::draw_end(gui, emuenv.window.get());
|
||||
emuenv.renderer->swap_window(emuenv.window.get());
|
||||
FrameMark; // Tracy - Frame end mark for game rendering loop
|
||||
}
|
||||
|
||||
@@ -426,6 +430,7 @@ int main(int argc, char *argv[]) {
|
||||
CoUninitialize();
|
||||
#endif
|
||||
|
||||
emuenv.renderer->preclose_action();
|
||||
app::destroy(emuenv, gui.imgui_state.get());
|
||||
|
||||
if (emuenv.load_exec)
|
||||
|
||||
@@ -451,24 +451,12 @@ static int init_texture_base(const char *export_name, SceGxmTexture *texture, Pt
|
||||
texture->normalize_mode = 1;
|
||||
texture->min_filter = SCE_GXM_TEXTURE_FILTER_POINT;
|
||||
texture->mag_filter = SCE_GXM_TEXTURE_FILTER_POINT;
|
||||
texture->lod_min0 = 0;
|
||||
texture->lod_min1 = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t get_gxp_texture_count(const SceGxmProgram &program_gxp) {
|
||||
const auto parameters = gxp::program_parameters(program_gxp);
|
||||
|
||||
uint16_t max_texture_index = 0;
|
||||
for (uint32_t i = 0; i < program_gxp.parameter_count; ++i) {
|
||||
const auto parameter = parameters[i];
|
||||
if (parameter.category == SCE_GXM_PARAMETER_CATEGORY_SAMPLER) {
|
||||
max_texture_index = std::max(max_texture_index, static_cast<uint16_t>(parameter.resource_index));
|
||||
}
|
||||
}
|
||||
|
||||
return max_texture_index + 1;
|
||||
}
|
||||
|
||||
EXPORT(int, _sceGxmBeginScene) {
|
||||
return UNIMPLEMENTED();
|
||||
}
|
||||
@@ -706,7 +694,7 @@ EXPORT(int, sceGxmBeginScene, SceGxmContext *context, uint32_t flags, const SceG
|
||||
// Wait for the display queue to be done.
|
||||
// If it's offline render, the sync object already has the display queue subject done, so don't worry.
|
||||
renderer::add_command(context->renderer.get(), renderer::CommandOpcode::WaitSyncObject,
|
||||
nullptr, fragmentSyncObject, sync->last_display);
|
||||
nullptr, fragmentSyncObject, renderTarget->renderer.get(), sync->last_display);
|
||||
}
|
||||
|
||||
// TODO This may not be right.
|
||||
@@ -1026,12 +1014,12 @@ EXPORT(float, sceGxmDepthStencilSurfaceGetBackgroundDepth, const SceGxmDepthSten
|
||||
|
||||
EXPORT(bool, sceGxmDepthStencilSurfaceGetBackgroundMask, const SceGxmDepthStencilSurface *surface) {
|
||||
assert(surface);
|
||||
return surface->control.get(emuenv.mem)->backgroundMask;
|
||||
return (surface->control.content & SceGxmDepthStencilControl::mask_bit) != 0;
|
||||
}
|
||||
|
||||
EXPORT(uint8_t, sceGxmDepthStencilSurfaceGetBackgroundStencil, const SceGxmDepthStencilSurface *surface) {
|
||||
assert(surface);
|
||||
return surface->control.get(emuenv.mem)->backgroundStencil;
|
||||
return surface->control.content & SceGxmDepthStencilControl::stencil_bits;
|
||||
}
|
||||
|
||||
EXPORT(SceGxmDepthStencilForceLoadMode, sceGxmDepthStencilSurfaceGetForceLoadMode, const SceGxmDepthStencilSurface *surface) {
|
||||
@@ -1041,7 +1029,6 @@ EXPORT(SceGxmDepthStencilForceLoadMode, sceGxmDepthStencilSurfaceGetForceLoadMod
|
||||
|
||||
EXPORT(SceGxmDepthStencilForceStoreMode, sceGxmDepthStencilSurfaceGetForceStoreMode, const SceGxmDepthStencilSurface *surface) {
|
||||
assert(surface);
|
||||
// TODO: Implement on the renderer side
|
||||
return static_cast<SceGxmDepthStencilForceStoreMode>(surface->zlsControl & SCE_GXM_DEPTH_STENCIL_FORCE_STORE_ENABLED);
|
||||
}
|
||||
|
||||
@@ -1064,18 +1051,12 @@ EXPORT(int, sceGxmDepthStencilSurfaceInit, SceGxmDepthStencilSurface *surface, S
|
||||
return RET_ERROR(SCE_GXM_ERROR_INVALID_VALUE);
|
||||
}
|
||||
|
||||
SceGxmDepthStencilSurface tmp_surface;
|
||||
tmp_surface.depthData = depthData;
|
||||
tmp_surface.stencilData = stencilData;
|
||||
tmp_surface.zlsControl = SCE_GXM_DEPTH_STENCIL_FORCE_LOAD_DISABLED | SCE_GXM_DEPTH_STENCIL_FORCE_STORE_DISABLED;
|
||||
*surface = SceGxmDepthStencilSurface();
|
||||
surface->depthData = depthData;
|
||||
surface->stencilData = stencilData;
|
||||
surface->zlsControl = SCE_GXM_DEPTH_STENCIL_FORCE_LOAD_DISABLED | SCE_GXM_DEPTH_STENCIL_FORCE_STORE_DISABLED;
|
||||
|
||||
tmp_surface.control = alloc<SceGxmDepthStencilControl>(emuenv.mem, "gxm depth stencil control");
|
||||
SceGxmDepthStencilControl control;
|
||||
control.disabled = false;
|
||||
control.format = depthStencilFormat;
|
||||
control.backgroundStencil = 0;
|
||||
memcpy(tmp_surface.control.get(emuenv.mem), &control, sizeof(SceGxmDepthStencilControl));
|
||||
memcpy(surface, &tmp_surface, sizeof(SceGxmDepthStencilSurface));
|
||||
surface->control.content = static_cast<uint32_t>(depthStencilFormat) | SceGxmDepthStencilControl::mask_bit;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1084,19 +1065,15 @@ EXPORT(int, sceGxmDepthStencilSurfaceInitDisabled, SceGxmDepthStencilSurface *su
|
||||
return RET_ERROR(SCE_GXM_ERROR_INVALID_POINTER);
|
||||
}
|
||||
|
||||
SceGxmDepthStencilSurface tmp_surface;
|
||||
*surface = SceGxmDepthStencilSurface();
|
||||
|
||||
tmp_surface.control = alloc<SceGxmDepthStencilControl>(emuenv.mem, "gxm depth stencil control");
|
||||
SceGxmDepthStencilControl control;
|
||||
control.disabled = true;
|
||||
memcpy(tmp_surface.control.get(emuenv.mem), &control, sizeof(SceGxmDepthStencilControl));
|
||||
memcpy(surface, &tmp_surface, sizeof(SceGxmDepthStencilSurface));
|
||||
surface->control.content = SceGxmDepthStencilControl::disabled_bit | SceGxmDepthStencilControl::mask_bit;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT(bool, sceGxmDepthStencilSurfaceIsEnabled, const SceGxmDepthStencilSurface *surface) {
|
||||
assert(surface);
|
||||
return !surface->control.get(emuenv.mem)->disabled;
|
||||
return (surface->control.content & SceGxmDepthStencilControl::disabled_bit) == 0;
|
||||
}
|
||||
|
||||
EXPORT(void, sceGxmDepthStencilSurfaceSetBackgroundDepth, SceGxmDepthStencilSurface *surface, float depth) {
|
||||
@@ -1106,12 +1083,16 @@ EXPORT(void, sceGxmDepthStencilSurfaceSetBackgroundDepth, SceGxmDepthStencilSurf
|
||||
|
||||
EXPORT(void, sceGxmDepthStencilSurfaceSetBackgroundMask, SceGxmDepthStencilSurface *surface, bool mask) {
|
||||
assert(surface);
|
||||
surface->control.get(emuenv.mem)->backgroundMask = mask;
|
||||
if (mask)
|
||||
surface->control.content |= SceGxmDepthStencilControl::mask_bit;
|
||||
else
|
||||
surface->control.content &= ~SceGxmDepthStencilControl::mask_bit;
|
||||
}
|
||||
|
||||
EXPORT(void, sceGxmDepthStencilSurfaceSetBackgroundStencil, SceGxmDepthStencilSurface *surface, uint8_t stencil) {
|
||||
assert(surface);
|
||||
surface->control.get(emuenv.mem)->backgroundStencil = stencil;
|
||||
surface->control.content &= ~SceGxmDepthStencilControl::stencil_bits;
|
||||
surface->control.content |= stencil;
|
||||
}
|
||||
|
||||
EXPORT(void, sceGxmDepthStencilSurfaceSetForceLoadMode, SceGxmDepthStencilSurface *surface, SceGxmDepthStencilForceLoadMode forceLoad) {
|
||||
@@ -1195,6 +1176,8 @@ EXPORT(int, sceGxmDisplayQueueAddEntry, Ptr<SceGxmSyncObject> oldBuffer, Ptr<Sce
|
||||
// function may be blocking here (expected behavior)
|
||||
emuenv.gxm.display_queue.push(display_callback);
|
||||
|
||||
renderer::send_single_command(*emuenv.renderer, nullptr, renderer::CommandOpcode::NewFrame, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1783,7 +1766,7 @@ EXPORT(uint32_t, sceGxmGetPrecomputedFragmentStateSize, const SceGxmFragmentProg
|
||||
assert(fragmentProgram);
|
||||
|
||||
const auto &fragment_program_gxp = *fragmentProgram->program.get(emuenv.mem);
|
||||
const uint16_t texture_count = get_gxp_texture_count(fragment_program_gxp);
|
||||
const uint16_t texture_count = gxp::get_texture_count(fragment_program_gxp);
|
||||
|
||||
return texture_count * sizeof(TextureData) + sizeof(UniformBuffers);
|
||||
}
|
||||
@@ -1792,7 +1775,7 @@ EXPORT(uint32_t, sceGxmGetPrecomputedVertexStateSize, const SceGxmVertexProgram
|
||||
assert(vertexProgram);
|
||||
|
||||
const auto &vertex_program_gxp = *vertexProgram->program.get(emuenv.mem);
|
||||
const uint16_t texture_count = get_gxp_texture_count(vertex_program_gxp);
|
||||
const uint16_t texture_count = gxp::get_texture_count(vertex_program_gxp);
|
||||
|
||||
return texture_count * sizeof(TextureData) + sizeof(UniformBuffers);
|
||||
}
|
||||
@@ -1857,6 +1840,9 @@ EXPORT(int, sceGxmInitialize, const SceGxmInitializeParams *params) {
|
||||
}
|
||||
|
||||
emuenv.gxm.params = *params;
|
||||
// hack, limit the number of frame rendering at the same time to at most 3
|
||||
// this is necessary for vulkan and anyway there is no reason for 4 frames to be rendering at the same time
|
||||
uint32_t max_queue_size = std::min(params->displayQueueMaxPendingCount, 2U);
|
||||
emuenv.gxm.display_queue.maxPendingCount_ = params->displayQueueMaxPendingCount;
|
||||
|
||||
const ThreadStatePtr main_thread = util::find(thread_id, emuenv.kernel.threads);
|
||||
@@ -2101,7 +2087,7 @@ EXPORT(int, sceGxmPrecomputedFragmentStateInit, SceGxmPrecomputedFragmentState *
|
||||
new_state.program = program;
|
||||
|
||||
const auto &fragment_program_gxp = *program.get(emuenv.mem)->program.get(emuenv.mem);
|
||||
new_state.texture_count = get_gxp_texture_count(fragment_program_gxp);
|
||||
new_state.texture_count = gxp::get_texture_count(fragment_program_gxp);
|
||||
|
||||
new_state.textures = extra_data.cast<TextureData>();
|
||||
new_state.uniform_buffers = (extra_data.cast<TextureData>() + new_state.texture_count).cast<UniformBuffers>();
|
||||
@@ -2208,7 +2194,7 @@ EXPORT(int, sceGxmPrecomputedVertexStateInit, SceGxmPrecomputedVertexState *stat
|
||||
new_state.program = program;
|
||||
|
||||
const auto &vertex_program_gxp = *program.get(emuenv.mem)->program.get(emuenv.mem);
|
||||
new_state.texture_count = get_gxp_texture_count(vertex_program_gxp);
|
||||
new_state.texture_count = gxp::get_texture_count(vertex_program_gxp);
|
||||
|
||||
new_state.textures = extra_data.cast<TextureData>();
|
||||
new_state.uniform_buffers = (extra_data.cast<TextureData>() + new_state.texture_count).cast<UniformBuffers>();
|
||||
@@ -3405,6 +3391,7 @@ EXPORT(int, sceGxmShaderPatcherCreateVertexProgram, SceGxmShaderPatcher *shaderP
|
||||
|
||||
SceGxmVertexProgram *const vp = vertexProgram->get(mem);
|
||||
vp->program = programId->program;
|
||||
vp->key_hash = key.hash;
|
||||
|
||||
if (streams && streamCount > 0) {
|
||||
vp->streams.insert(vp->streams.end(), &streams[0], &streams[streamCount]);
|
||||
@@ -3572,11 +3559,7 @@ EXPORT(int, sceGxmSyncObjectCreate, Ptr<SceGxmSyncObject> *syncObject) {
|
||||
return RET_ERROR(SCE_GXM_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
// Set as if the last display was already done
|
||||
SceGxmSyncObject *sync = syncObject->get(emuenv.mem);
|
||||
sync->last_display = 0;
|
||||
sync->timestamp_current = 0;
|
||||
sync->timestamp_ahead = 0;
|
||||
renderer::create(syncObject->get(emuenv.mem), *emuenv.renderer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -3585,6 +3568,7 @@ EXPORT(int, sceGxmSyncObjectDestroy, Ptr<SceGxmSyncObject> syncObject) {
|
||||
if (!syncObject)
|
||||
return RET_ERROR(SCE_GXM_ERROR_INVALID_POINTER);
|
||||
|
||||
renderer::destroy(syncObject.get(emuenv.mem), *emuenv.renderer);
|
||||
free(emuenv.mem, syncObject);
|
||||
|
||||
return 0;
|
||||
@@ -3951,7 +3935,7 @@ EXPORT(int, sceGxmTextureSetMipFilter, SceGxmTexture *texture, SceGxmTextureMipF
|
||||
return RET_ERROR(SCE_GXM_ERROR_UNSUPPORTED);
|
||||
}
|
||||
|
||||
texture->mip_filter = (uint32_t)mipFilter;
|
||||
texture->mip_filter = static_cast<bool>(mipFilter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3968,7 +3952,7 @@ EXPORT(int, sceGxmTextureSetMipmapCount, SceGxmTexture *texture, uint32_t mipCou
|
||||
return RET_ERROR(SCE_GXM_ERROR_INVALID_VALUE);
|
||||
}
|
||||
|
||||
texture->mip_count = (uint32_t)mipCount;
|
||||
texture->mip_count = std::min<std::uint32_t>(15, mipCount - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -4127,7 +4111,7 @@ EXPORT(int, sceGxmTransferCopy, uint32_t width, uint32_t height, uint32_t colorK
|
||||
if (syncObject) {
|
||||
SceGxmSyncObject *sync = syncObject.get(emuenv.mem);
|
||||
renderer::send_single_command(*emuenv.renderer, nullptr, renderer::CommandOpcode::WaitSyncObject, false,
|
||||
syncObject, sync->last_display);
|
||||
syncObject, nullptr, sync->last_display);
|
||||
cmd_timestamp = ++sync->timestamp_ahead;
|
||||
}
|
||||
|
||||
@@ -4177,7 +4161,7 @@ EXPORT(int, sceGxmTransferDownscale, SceGxmTransferFormat srcFormat, Ptr<void> s
|
||||
if (syncObject) {
|
||||
SceGxmSyncObject *sync = syncObject.get(emuenv.mem);
|
||||
renderer::send_single_command(*emuenv.renderer, nullptr, renderer::CommandOpcode::WaitSyncObject, false,
|
||||
syncObject, sync->last_display);
|
||||
syncObject, nullptr, sync->last_display);
|
||||
cmd_timestamp = ++sync->timestamp_ahead;
|
||||
}
|
||||
|
||||
@@ -4221,7 +4205,7 @@ EXPORT(int, sceGxmTransferFill, uint32_t fillColor, SceGxmTransferFormat destFor
|
||||
if (syncObject) {
|
||||
SceGxmSyncObject *sync = syncObject.get(emuenv.mem);
|
||||
renderer::send_single_command(*emuenv.renderer, nullptr, renderer::CommandOpcode::WaitSyncObject, false,
|
||||
syncObject, sync->last_display);
|
||||
syncObject, nullptr, sync->last_display);
|
||||
cmd_timestamp = ++sync->timestamp_ahead;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,61 +1,38 @@
|
||||
if (USE_VULKAN)
|
||||
set(RENDERER_VULKAN_SOURCES
|
||||
include/renderer/vulkan/types.h
|
||||
include/renderer/vulkan/state.h
|
||||
include/renderer/vulkan/functions.h
|
||||
|
||||
src/vulkan/renderer.cpp
|
||||
src/vulkan/allocator.cpp
|
||||
)
|
||||
set(RENDERER_VULKAN_LIBRARIES vulkan vma)
|
||||
else()
|
||||
set(RENDERER_VULKAN_SOURCES "")
|
||||
set(RENDERER_VULKAN_LIBRARIES "")
|
||||
endif()
|
||||
|
||||
add_library(
|
||||
renderer
|
||||
STATIC
|
||||
include/renderer/commands.h
|
||||
include/renderer/driver_functions.h
|
||||
include/renderer/functions.h
|
||||
include/renderer/profile.h
|
||||
include/renderer/pvrt-dec.h
|
||||
include/renderer/state.h
|
||||
include/renderer/surface_cache.h
|
||||
include/renderer/texture_cache_state.h
|
||||
include/renderer/types.h
|
||||
|
||||
include/renderer/gl/fence.h
|
||||
include/renderer/gl/types.h
|
||||
include/renderer/gl/state.h
|
||||
include/renderer/gl/ring_buffer.h
|
||||
include/renderer/gl/screen_render.h
|
||||
include/renderer/gl/surface_cache.h
|
||||
include/renderer/gl/functions.h
|
||||
|
||||
src/gl/attribute_formats.cpp
|
||||
src/gl/color_formats.cpp
|
||||
src/gl/compile_program.cpp
|
||||
src/gl/draw.cpp
|
||||
src/gl/fence.cpp
|
||||
src/gl/load_shaders.cpp
|
||||
src/gl/renderer.cpp
|
||||
src/gl/ring_buffer.cpp
|
||||
src/gl/screen_render.cpp
|
||||
src/gl/screen_render.cpp
|
||||
src/gl/surface_cache.cpp
|
||||
src/gl/sync_state.cpp
|
||||
src/gl/texture_formats.cpp
|
||||
src/gl/texture.cpp
|
||||
src/gl/uniforms.cpp
|
||||
|
||||
${RENDERER_VULKAN_SOURCES}
|
||||
src/vulkan/allocator.cpp
|
||||
src/vulkan/context.cpp
|
||||
src/vulkan/creation.cpp
|
||||
src/vulkan/gxm_to_vulkan.cpp
|
||||
src/vulkan/pipeline_cache.cpp
|
||||
src/vulkan/renderer.cpp
|
||||
src/vulkan/scene.cpp
|
||||
src/vulkan/screen_renderer.cpp
|
||||
src/vulkan/surface_cache.cpp
|
||||
src/vulkan/sync_state.cpp
|
||||
src/vulkan/texture.cpp
|
||||
|
||||
src/batch.cpp
|
||||
src/creation.cpp
|
||||
src/pvrt-dec.cpp
|
||||
src/renderer.cpp
|
||||
src/scene.cpp
|
||||
src/shaders.cpp
|
||||
src/state_set.cpp
|
||||
src/sync.cpp
|
||||
src/texture_cache.cpp
|
||||
@@ -65,7 +42,7 @@ add_library(
|
||||
)
|
||||
|
||||
target_include_directories(renderer PUBLIC include)
|
||||
target_link_libraries(renderer PUBLIC crypto display dlmalloc mem stb shader glutil threads config util ${RENDERER_VULKAN_LIBRARIES})
|
||||
target_link_libraries(renderer PUBLIC crypto display dlmalloc mem stb shader glutil threads config util vkutil)
|
||||
target_link_libraries(renderer PRIVATE sdl2 stb ffmpeg xxHash::xxhash)
|
||||
|
||||
# Marshmallow Tracy linking
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include <dlmalloc.h>
|
||||
|
||||
namespace renderer {
|
||||
#define REPORT_MISSING(backend) // LOG_ERROR("Unimplemented graphics API handler with backend {}", (int)backend)
|
||||
#define REPORT_STUBBED() // LOG_INFO("Stubbed")
|
||||
#define REPORT_MISSING(backend) LOG_ERROR("Unimplemented graphics API handler with backend {}", (int)backend)
|
||||
#define REPORT_STUBBED() LOG_INFO("Stubbed")
|
||||
|
||||
struct Command;
|
||||
|
||||
@@ -74,6 +74,11 @@ enum class CommandOpcode : std::uint8_t {
|
||||
|
||||
SignalNotification,
|
||||
|
||||
/**
|
||||
* Start rendering a newframe
|
||||
*/
|
||||
NewFrame,
|
||||
|
||||
DestroyRenderTarget,
|
||||
DestroyContext
|
||||
};
|
||||
|
||||
@@ -71,5 +71,6 @@ COMMAND(handle_nop);
|
||||
COMMAND(handle_signal_sync_object);
|
||||
COMMAND(handle_wait_sync_object);
|
||||
COMMAND(handle_notification);
|
||||
COMMAND(new_frame);
|
||||
|
||||
} // namespace renderer
|
||||
|
||||
@@ -35,6 +35,8 @@ struct VertexProgram;
|
||||
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, State &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id);
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, State &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id);
|
||||
void create(SceGxmSyncObject *sync, State &state);
|
||||
void destroy(SceGxmSyncObject *sync, State &state);
|
||||
void finish(State &state, Context *context);
|
||||
|
||||
/**
|
||||
@@ -53,8 +55,8 @@ int wait_for_status(State &state, int *status, int signal, bool wake_on_equal);
|
||||
void reset_command_list(CommandList &command_list);
|
||||
void submit_command_list(State &state, renderer::Context *context, CommandList &command_list);
|
||||
bool is_cmd_ready(MemState &mem, CommandList &command_list);
|
||||
void process_batch(State &state, MemState &mem, Config &config, CommandList &command_list, const char *base_path, const char *title_id);
|
||||
void process_batches(State &state, const FeatureState &features, MemState &mem, Config &config, const char *base_path, const char *title_id, const char *self_name);
|
||||
void process_batch(State &state, MemState &mem, Config &config, CommandList &command_list);
|
||||
void process_batches(State &state, const FeatureState &features, MemState &mem, Config &config);
|
||||
bool init(SDL_Window *window, std::unique_ptr<State> &state, Backend backend, const Config &config, const char *base_path);
|
||||
|
||||
void set_depth_bias(State &state, Context *ctx, bool is_front, int factor, int units);
|
||||
@@ -177,12 +179,18 @@ void resolve_z_order_compressed_image(std::uint32_t width, std::uint32_t height,
|
||||
void swizzled_texture_to_linear_texture(uint8_t *dest, const uint8_t *src, uint16_t width, uint16_t height, uint8_t bits_per_pixel);
|
||||
void tiled_texture_to_linear_texture(uint8_t *dest, const uint8_t *src, uint16_t width, uint16_t height, uint8_t bits_per_pixel);
|
||||
|
||||
uint16_t get_upload_mip(const uint16_t true_mip, const uint16_t width, const uint16_t height, const SceGxmTextureBaseFormat base_format);
|
||||
|
||||
void upload_bound_texture(const TextureCacheState &cache, const SceGxmTexture &gxm_texture, const MemState &mem);
|
||||
void cache_and_bind_texture(TextureCacheState &cache, const SceGxmTexture &gxm_texture, MemState &mem);
|
||||
float get_integral_query_format(const SceGxmTextureBaseFormat format);
|
||||
size_t bits_per_pixel(SceGxmTextureBaseFormat base_format);
|
||||
bool is_compressed_format(SceGxmTextureBaseFormat base_format);
|
||||
bool can_texture_be_unswizzled_without_decode(SceGxmTextureBaseFormat fmt, bool is_vulkan);
|
||||
size_t get_compressed_size(SceGxmTextureBaseFormat base_format, std::uint32_t width, std::uint32_t height);
|
||||
TextureCacheHash hash_texture_data(const SceGxmTexture &texture, const MemState &mem);
|
||||
size_t texture_size(const SceGxmTexture &texture);
|
||||
bool convert_base_texture_format_to_base_color_format(SceGxmTextureBaseFormat format, SceGxmColorBaseFormat &color_format);
|
||||
|
||||
} // namespace texture
|
||||
|
||||
|
||||
@@ -39,20 +39,14 @@ namespace renderer::gl {
|
||||
SharedGLObject compile_program(GLState &renderer, const GxmRecordState &state, const FeatureState &features, const MemState &mem, bool shader_cache, bool spirv, bool maskupdate, const char *base_path, const char *title_id, const char *self_name);
|
||||
void pre_compile_program(GLState &renderer, const char *base_path, const char *title_id, const char *self_name, const ShadersHash &hashs);
|
||||
|
||||
// Shaders.
|
||||
bool get_shaders_cache_hashs(GLState &renderer, const char *base_path, const char *title_id, const char *self_name);
|
||||
std::string load_glsl_shader(const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, bool shader_cache);
|
||||
std::vector<std::uint32_t> load_spirv_shader(const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name);
|
||||
std::string pre_load_glsl_shader(const char *hash_text, const char *shader_type_str, const char *base_path, const char *title_id, const char *self_name);
|
||||
|
||||
// Uniforms.
|
||||
bool set_uniform_buffer(GLContext &context, MemState &mem, const bool vertex_shader, const int block_num, const int size, const void *data, bool log_active_shader);
|
||||
bool set_uniform_buffer(GLContext &context, const ShaderProgram *program, const bool vertex_shader, const int block_num, const int size, const uint8_t *data);
|
||||
|
||||
bool create(SDL_Window *window, std::unique_ptr<renderer::State> &state, const char *base_path, const bool hashless_texture_cache);
|
||||
bool create(std::unique_ptr<Context> &context);
|
||||
bool create(GLState &state, std::unique_ptr<RenderTarget> &rt, const SceGxmRenderTargetParams ¶ms, const FeatureState &features);
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, GLState &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id);
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, GLState &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id);
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, GLState &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend);
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, GLState &state, const SceGxmProgram &program);
|
||||
void set_context(GLState &state, GLContext &ctx, const MemState &mem, const GLRenderTarget *rt, const FeatureState &features);
|
||||
void get_surface_data(GLState &renderer, GLContext &context, uint32_t *pixels, SceGxmColorSurface &surface);
|
||||
void lookup_and_get_surface_data(GLState &renderer, MemState &mem, SceGxmColorSurface &surface);
|
||||
@@ -70,7 +64,7 @@ void sync_depth_func(const SceGxmDepthFunc func, const bool is_front);
|
||||
void sync_depth_write_enable(const SceGxmDepthWriteMode mode, const bool is_front);
|
||||
void sync_depth_data(const renderer::GxmRecordState &state);
|
||||
void sync_stencil_data(const renderer::GxmRecordState &state, const MemState &mem);
|
||||
void sync_stencil_func(const GxmStencilState &state, const MemState &mem, bool is_back_stencil);
|
||||
void sync_stencil_func(const GxmStencilStateOp &state_op, const GxmStencilStateValues &state_vals, const MemState &mem, const bool is_back_stencil);
|
||||
void sync_mask(const GLState &state, GLContext &context, const MemState &mem);
|
||||
void sync_polygon_mode(const SceGxmPolygonMode mode, const bool front);
|
||||
void sync_point_line_width(const std::uint32_t size, const bool front);
|
||||
@@ -94,7 +88,8 @@ namespace texture {
|
||||
// Textures.
|
||||
void bind_texture(GLTextureCacheState &cache, const SceGxmTexture &gxm_texture, const MemState &mem);
|
||||
void configure_bound_texture(const renderer::TextureCacheState &state, const SceGxmTexture &gxm_texture);
|
||||
void upload_bound_texture(const SceGxmTexture &gxm_texture, const MemState &mem);
|
||||
void upload_bound_texture(SceGxmTextureBaseFormat base_format, uint32_t width, uint32_t height,
|
||||
uint32_t mip_index, const void *pixels, int face, bool is_compressed, size_t pixels_per_stride);
|
||||
|
||||
// Texture formats.
|
||||
const GLint *translate_swizzle(SceGxmTextureFormat fmt);
|
||||
@@ -120,7 +115,6 @@ GLenum translate_type(SceGxmColorBaseFormat base_format);
|
||||
const GLint *translate_swizzle(SceGxmColorFormat fmt);
|
||||
size_t bytes_per_pixel(SceGxmColorBaseFormat base_format);
|
||||
size_t bytes_per_pixel_in_gl_storage(SceGxmColorBaseFormat base_format);
|
||||
bool convert_base_texture_format_to_base_color_format(SceGxmTextureBaseFormat format, SceGxmColorBaseFormat &color_format);
|
||||
bool is_write_surface_stored_rawly(SceGxmColorBaseFormat base_format);
|
||||
bool is_write_surface_non_linearity_filtering(SceGxmColorBaseFormat base_format);
|
||||
GLenum get_raw_store_internal_type(SceGxmColorBaseFormat base_format);
|
||||
|
||||
@@ -43,17 +43,18 @@ struct GLState : public renderer::State {
|
||||
GLTextureCacheState texture_cache;
|
||||
GLSurfaceCache surface_cache;
|
||||
|
||||
std::vector<ShadersHash> shaders_cache_hashs;
|
||||
std::string shader_version;
|
||||
|
||||
ScreenRenderer screen_renderer;
|
||||
|
||||
bool init(const char *base_path, const bool hashless_texture_cache) override;
|
||||
void render_frame(const SceFVector2 &viewport_pos, const SceFVector2 &viewport_size, const DisplayState &display,
|
||||
const GxmState &gxm, MemState &mem) override;
|
||||
void swap_window(SDL_Window *window) override;
|
||||
void set_fxaa(bool enable_fxaa) override;
|
||||
int get_max_anisotropic_filtering() override;
|
||||
void set_anisotropic_filtering(int anisotropic_filtering) override;
|
||||
|
||||
void precompile_shader(const ShadersHash &hash) override;
|
||||
void preclose_action() override;
|
||||
};
|
||||
|
||||
} // namespace renderer::gl
|
||||
|
||||
@@ -84,8 +84,8 @@ private:
|
||||
std::array<GLDepthStencilSurfaceCacheInfo, MAX_CACHE_SIZE_PER_CONTAINER> depth_stencil_textures;
|
||||
std::unordered_map<std::uint64_t, GLObjectArray<1>> framebuffer_array;
|
||||
|
||||
std::vector<std::uint64_t> last_use_color_surface_index;
|
||||
std::vector<std::size_t> last_use_depth_stencil_surface_index;
|
||||
std::vector<size_t> last_use_color_surface_index;
|
||||
std::vector<size_t> last_use_depth_stencil_surface_index;
|
||||
|
||||
GLObjectArray<1> typeless_copy_buffer;
|
||||
std::size_t typeless_copy_buffer_size = 0;
|
||||
@@ -93,29 +93,29 @@ private:
|
||||
const GLRenderTarget *target = nullptr;
|
||||
|
||||
private:
|
||||
void do_typeless_copy(const GLint dest_texture, const GLint source_texture, const GLenum dest_internal,
|
||||
void do_typeless_copy(const GLuint dest_texture, const GLuint source_texture, const GLenum dest_internal,
|
||||
const GLenum dest_upload_format, const GLenum dest_type, const GLenum source_format, const GLenum source_type,
|
||||
const int offset_x, const int offset_y, const int width, const int height, const int dest_width, const int dest_height, const std::size_t total_source_size);
|
||||
|
||||
public:
|
||||
explicit GLSurfaceCache();
|
||||
|
||||
std::uint64_t retrieve_color_surface_texture_handle(const State &state, std::uint16_t width, std::uint16_t height, const std::uint16_t pixel_stride,
|
||||
GLuint retrieve_color_surface_texture_handle(const State &state, std::uint16_t width, std::uint16_t height, const std::uint16_t pixel_stride,
|
||||
const SceGxmColorBaseFormat color_format, Ptr<void> address, SurfaceTextureRetrievePurpose purpose, std::uint32_t &swizzle,
|
||||
std::uint16_t *stored_height = nullptr, std::uint16_t *stored_width = nullptr) override;
|
||||
std::uint64_t retrieve_ping_pong_color_surface_texture_handle(Ptr<void> address) override;
|
||||
std::uint16_t *stored_height = nullptr, std::uint16_t *stored_width = nullptr);
|
||||
GLuint retrieve_ping_pong_color_surface_texture_handle(Ptr<void> address);
|
||||
|
||||
// We really can't sample this around... The only usage of this function is interally load/store from this texture.
|
||||
std::uint64_t retrieve_depth_stencil_texture_handle(const State &state, const MemState &mem, const SceGxmDepthStencilSurface &surface, std::int32_t force_width = -1,
|
||||
std::int32_t force_height = -1, const bool is_reading = false) override;
|
||||
std::uint64_t retrieve_framebuffer_handle(const State &state, const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
|
||||
std::uint64_t *color_texture_handle = nullptr, std::uint64_t *ds_texture_handle = nullptr,
|
||||
std::uint16_t *stored_height = nullptr) override;
|
||||
GLuint retrieve_depth_stencil_texture_handle(const State &state, const MemState &mem, const SceGxmDepthStencilSurface &surface, std::int32_t force_width = -1,
|
||||
std::int32_t force_height = -1, const bool is_reading = false);
|
||||
GLuint retrieve_framebuffer_handle(const State &state, const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
|
||||
GLuint *color_texture_handle = nullptr, GLuint *ds_texture_handle = nullptr,
|
||||
std::uint16_t *stored_height = nullptr);
|
||||
|
||||
void set_render_target(const GLRenderTarget *new_target) {
|
||||
target = new_target;
|
||||
}
|
||||
|
||||
std::uint64_t sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size) override;
|
||||
GLuint sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size);
|
||||
};
|
||||
} // namespace renderer::gl
|
||||
|
||||
@@ -48,41 +48,17 @@ inline bool operator==(const ExcludedUniform &lhs, const ExcludedUniform &rhs) {
|
||||
return (lhs.name == rhs.name) && (lhs.program == rhs.program);
|
||||
}
|
||||
|
||||
typedef std::map<std::string, SharedGLObject> ShaderCache;
|
||||
typedef std::tuple<std::string, std::string> ProgramHashes;
|
||||
typedef std::map<Sha256Hash, SharedGLObject> ShaderCache;
|
||||
typedef std::map<ProgramHashes, SharedGLObject> ProgramCache;
|
||||
typedef std::vector<ExcludedUniform> ExcludedUniforms; // vector instead of unordered_set since it's much faster for few elements
|
||||
typedef std::map<GLuint, GLenum> UniformTypes;
|
||||
|
||||
struct UniformSetRequest {
|
||||
const SceGxmProgramParameter *parameter;
|
||||
const void *data;
|
||||
};
|
||||
|
||||
struct GLTextureCacheState : public renderer::TextureCacheState {
|
||||
GLObjectArray<TextureCacheSize> textures;
|
||||
};
|
||||
|
||||
struct GLRenderTarget;
|
||||
|
||||
struct GXMRenderVertUniformBlock {
|
||||
float viewport_flip[4];
|
||||
float viewport_flag;
|
||||
float screen_width;
|
||||
float screen_height;
|
||||
float padding;
|
||||
float integral_texture_query_format[SCE_GXM_MAX_TEXTURE_UNITS];
|
||||
};
|
||||
|
||||
struct GXMRenderFragUniformBlock {
|
||||
float back_disabled = 0;
|
||||
float front_disabled = 0;
|
||||
float writing_mask = 0;
|
||||
float use_raw_image = 0;
|
||||
float integral_texture_query_format[SCE_GXM_MAX_TEXTURE_UNITS];
|
||||
int32_t res_multiplier = 0;
|
||||
};
|
||||
|
||||
struct GLContext : public renderer::Context {
|
||||
GLObjectArray<1> vertex_array;
|
||||
|
||||
@@ -93,32 +69,19 @@ struct GLContext : public renderer::Context {
|
||||
RingBuffer vertex_info_uniform_buffer;
|
||||
RingBuffer fragment_info_uniform_buffer;
|
||||
|
||||
GXMRenderVertUniformBlock previous_vert_info;
|
||||
GXMRenderFragUniformBlock previous_frag_info;
|
||||
|
||||
GXMRenderVertUniformBlock current_vert_render_info;
|
||||
GXMRenderFragUniformBlock current_frag_render_info;
|
||||
|
||||
const GLRenderTarget *render_target;
|
||||
|
||||
std::map<int, std::vector<uint8_t>> ubo_data;
|
||||
|
||||
GLObjectArray<SCE_GXM_MAX_VERTEX_STREAMS> stream_vertex_buffers;
|
||||
GLuint last_draw_program{ 0 };
|
||||
GLuint current_framebuffer{ 0 };
|
||||
GLuint current_color_attachment{ 0 };
|
||||
GLuint current_framebuffer_height{ 0 };
|
||||
|
||||
std::vector<GLuint> self_sampling_indices;
|
||||
|
||||
float viewport_flip[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
|
||||
std::vector<UniformSetRequest> vertex_set_requests;
|
||||
std::vector<UniformSetRequest> fragment_set_requests;
|
||||
|
||||
std::pair<std::uint8_t *, std::size_t> vertex_uniform_buffer_storage_ptr{ nullptr, 0 };
|
||||
std::pair<std::uint8_t *, std::size_t> fragment_uniform_buffer_storage_ptr{ nullptr, 0 };
|
||||
|
||||
std::vector<size_t> self_sampling_indices;
|
||||
|
||||
explicit GLContext();
|
||||
~GLContext() override = default;
|
||||
};
|
||||
@@ -145,9 +108,6 @@ struct GLFragmentProgram : public renderer::FragmentProgram {
|
||||
|
||||
struct GLVertexProgram : public renderer::VertexProgram {
|
||||
GLShaderStatics statics;
|
||||
shader::usse::AttributeInformationMap attribute_infos;
|
||||
|
||||
bool stripped_symbols_checked;
|
||||
};
|
||||
|
||||
struct GLRenderTarget : public renderer::RenderTarget {
|
||||
|
||||
@@ -53,10 +53,12 @@ struct SceGxmColorSurface {
|
||||
static_assert(sizeof(SceGxmColorSurface) == (32 + sizeof(SceGxmTexture)), "Incorrect size.");
|
||||
|
||||
struct SceGxmDepthStencilControl {
|
||||
bool disabled;
|
||||
SceGxmDepthStencilFormat format;
|
||||
bool backgroundMask = true;
|
||||
uint8_t backgroundStencil;
|
||||
uint32_t content;
|
||||
|
||||
static constexpr uint32_t format_bits = ~0xFFF;
|
||||
static constexpr uint32_t stencil_bits = 0xF;
|
||||
static constexpr uint32_t mask_bit = 0x10;
|
||||
static constexpr uint32_t disabled_bit = 0x20;
|
||||
};
|
||||
|
||||
struct SceGxmDepthStencilSurface {
|
||||
@@ -64,7 +66,7 @@ struct SceGxmDepthStencilSurface {
|
||||
Ptr<void> depthData;
|
||||
Ptr<void> stencilData;
|
||||
float backgroundDepth = 1.0f;
|
||||
Ptr<SceGxmDepthStencilControl> control;
|
||||
SceGxmDepthStencilControl control;
|
||||
};
|
||||
|
||||
struct SceGxmContextParams {
|
||||
@@ -133,6 +135,8 @@ struct SceGxmSyncObject {
|
||||
|
||||
std::mutex lock;
|
||||
std::condition_variable cond;
|
||||
// some extra space for additional data, on the Vulkan renderer this points to a RenderTarget* a,d a vector of fences
|
||||
void *extra;
|
||||
};
|
||||
|
||||
struct GxmContextState {
|
||||
@@ -277,6 +281,7 @@ struct SceGxmVertexProgram {
|
||||
std::vector<SceGxmVertexStream> streams;
|
||||
std::vector<SceGxmVertexAttribute> attributes;
|
||||
std::unique_ptr<renderer::VertexProgram> renderer_data;
|
||||
uint64_t key_hash;
|
||||
};
|
||||
|
||||
struct SceGxmPrecomputedDraw {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct SceGxmProgram;
|
||||
struct FeatureState;
|
||||
struct SceGxmVertexAttribute;
|
||||
|
||||
namespace renderer {
|
||||
|
||||
struct ShadersHash;
|
||||
struct State;
|
||||
|
||||
// Shaders.
|
||||
bool get_shaders_cache_hashs(State &renderer);
|
||||
void save_shaders_cache_hashs(State &renderer, std::vector<ShadersHash> &shaders_cache_hashs);
|
||||
std::string load_glsl_shader(const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, bool shader_cache);
|
||||
std::vector<uint32_t> load_spirv_shader(const SceGxmProgram &program, const FeatureState &features, bool is_vulkan, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, bool shader_cache);
|
||||
std::string pre_load_shader_glsl(const char *hash_text, const char *shader_type_str, const char *base_path, const char *title_id, const char *self_name);
|
||||
std::vector<uint32_t> pre_load_shader_spirv(const char *hash_text, const char *shader_type_str, const char *base_path, const char *title_id, const char *self_name);
|
||||
} // namespace renderer
|
||||
@@ -26,16 +26,23 @@
|
||||
#include <mutex>
|
||||
|
||||
struct SDL_Cursor;
|
||||
struct SDL_Window;
|
||||
struct DisplayState;
|
||||
struct GxmState;
|
||||
|
||||
namespace renderer {
|
||||
struct State {
|
||||
const char *base_path;
|
||||
const char *title_id;
|
||||
const char *self_name;
|
||||
|
||||
Backend current_backend;
|
||||
FeatureState features;
|
||||
int res_multiplier;
|
||||
bool disable_surface_sync;
|
||||
|
||||
Context *context;
|
||||
|
||||
GXPPtrMap gxp_ptr_map;
|
||||
Queue<CommandList> command_buffer_queue;
|
||||
std::condition_variable command_finish_one;
|
||||
@@ -44,10 +51,13 @@ struct State {
|
||||
std::condition_variable notification_ready;
|
||||
std::mutex notification_mutex;
|
||||
|
||||
std::vector<ShadersHash> shaders_cache_hashs;
|
||||
std::string shader_version;
|
||||
|
||||
int last_scene_id = 0;
|
||||
|
||||
uint32_t shaders_count_compiled;
|
||||
uint32_t programs_count_pre_compiled;
|
||||
uint32_t shaders_count_compiled = 0;
|
||||
uint32_t programs_count_pre_compiled = 0;
|
||||
|
||||
std::atomic<bool> should_display;
|
||||
|
||||
@@ -55,9 +65,16 @@ struct State {
|
||||
virtual void render_frame(const SceFVector2 &viewport_pos, const SceFVector2 &viewport_size, const DisplayState &display,
|
||||
const GxmState &gxm, MemState &mem)
|
||||
= 0;
|
||||
virtual void swap_window(SDL_Window *window) = 0;
|
||||
virtual void set_fxaa(bool enable_fxaa) = 0;
|
||||
virtual int get_max_anisotropic_filtering() = 0;
|
||||
virtual void set_anisotropic_filtering(int anisotropic_filtering) = 0;
|
||||
virtual std::vector<std::string> get_gpu_list() {
|
||||
return { "Automatic" };
|
||||
}
|
||||
|
||||
virtual void precompile_shader(const ShadersHash &hash) = 0;
|
||||
virtual void preclose_action() = 0;
|
||||
|
||||
virtual ~State() = default;
|
||||
};
|
||||
|
||||
@@ -34,24 +34,5 @@ enum SurfaceTextureRetrievePurpose {
|
||||
WRITING,
|
||||
};
|
||||
|
||||
class SurfaceCache {
|
||||
public:
|
||||
virtual std::uint64_t retrieve_color_surface_texture_handle(const State &state, std::uint16_t width, std::uint16_t height, const std::uint16_t pixel_stride,
|
||||
const SceGxmColorBaseFormat color_format, Ptr<void> address, SurfaceTextureRetrievePurpose purpose, std::uint32_t &swizzle,
|
||||
std::uint16_t *stored_height = nullptr, std::uint16_t *stored_width = nullptr)
|
||||
= 0;
|
||||
virtual std::uint64_t retrieve_ping_pong_color_surface_texture_handle(Ptr<void> address) = 0;
|
||||
|
||||
// We really can't sample this around... The only usage of this function is interally load/store from this texture.
|
||||
virtual std::uint64_t retrieve_depth_stencil_texture_handle(const State &state, const MemState &mem, const SceGxmDepthStencilSurface &surface, std::int32_t force_width = -1,
|
||||
std::int32_t force_height = -1, const bool is_reading = false)
|
||||
= 0;
|
||||
virtual std::uint64_t retrieve_framebuffer_handle(const State &state, const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
|
||||
std::uint64_t *color_texture_handle = nullptr, std::uint64_t *ds_texture_handle = nullptr,
|
||||
std::uint16_t *stored_height = nullptr)
|
||||
= 0;
|
||||
|
||||
virtual std::uint64_t sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size)
|
||||
= 0;
|
||||
};
|
||||
class SurfaceCache {};
|
||||
} // namespace renderer
|
||||
|
||||
@@ -28,9 +28,10 @@
|
||||
struct MemState;
|
||||
|
||||
namespace renderer {
|
||||
constexpr size_t TextureCacheSize = KiB(1);
|
||||
static constexpr size_t TextureCacheSize = 1024;
|
||||
typedef uint64_t TextureCacheTimestamp;
|
||||
typedef uint32_t TextureCacheHash;
|
||||
enum class Backend : uint32_t;
|
||||
|
||||
struct TextureCacheInfo {
|
||||
bool use_hash = false;
|
||||
@@ -49,10 +50,11 @@ struct TextureCacheState;
|
||||
|
||||
typedef std::array<TextureCacheInfo, TextureCacheSize> TextureCacheInfoes;
|
||||
typedef std::function<void(std::size_t, const void *)> TextureCacheStateSelectCallback;
|
||||
typedef std::function<void(TextureCacheState &, std::size_t, const void *)> TextureCacheStateConfigureTextureCallback;
|
||||
typedef std::function<void(std::size_t, const void *, const MemState &)> TextureCacheStateUploadTextureCallback;
|
||||
typedef std::function<void(TextureCacheState &, const void *)> TextureCacheStateConfigureTextureCallback;
|
||||
typedef std::function<void(SceGxmTextureBaseFormat base_format, uint32_t width, uint32_t height, uint32_t mip_index, const void *pixels, int face, bool is_compressed, size_t pixels_per_stride)> TextureCacheStateUploadTextureCallback;
|
||||
|
||||
struct TextureCacheState {
|
||||
Backend *backend;
|
||||
bool use_protect = false;
|
||||
int anisotropic_filtering = 1;
|
||||
size_t used = 0;
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <gxm/types.h>
|
||||
#include <renderer/commands.h>
|
||||
#include <renderer/gxm_types.h>
|
||||
#include <shader/spirv_recompiler.h>
|
||||
#include <shader/usse_program_analyzer.h>
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
@@ -40,23 +42,22 @@ using UniformBufferSizes = std::array<std::uint32_t, 15>;
|
||||
|
||||
namespace renderer {
|
||||
|
||||
typedef std::map<GLuint, std::string> AttributeLocations;
|
||||
typedef std::map<std::string, SharedGLObject> ShaderCache;
|
||||
typedef std::tuple<std::string, std::string> ProgramHashes;
|
||||
typedef std::map<ProgramHashes, SharedGLObject> ProgramCache;
|
||||
typedef std::tuple<Sha256Hash, Sha256Hash> ProgramHashes;
|
||||
typedef std::vector<std::string> ExcludedUniforms; // vector instead of unordered_set since it's much faster for few elements
|
||||
typedef std::map<GLuint, GLenum> UniformTypes;
|
||||
|
||||
// State types
|
||||
typedef std::map<Sha256Hash, const SceGxmProgram *> GXPPtrMap;
|
||||
|
||||
struct UniformSetRequest {
|
||||
const SceGxmProgramParameter *parameter;
|
||||
const void *data;
|
||||
};
|
||||
|
||||
struct CommandBuffer;
|
||||
|
||||
enum class Backend : uint32_t {
|
||||
OpenGL,
|
||||
#ifdef USE_VULKAN
|
||||
Vulkan,
|
||||
#endif
|
||||
Vulkan
|
||||
};
|
||||
|
||||
enum class GXMState : std::uint16_t {
|
||||
@@ -87,25 +88,38 @@ struct GXMStreamInfo {
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
// We seperate the following two parts of the stencil state because the first is part of the pipeline creation
|
||||
// while the second is dynamic
|
||||
struct GxmStencilStateOp {
|
||||
SceGxmStencilFunc func = SCE_GXM_STENCIL_FUNC_ALWAYS;
|
||||
SceGxmStencilOp stencil_fail = SCE_GXM_STENCIL_OP_KEEP;
|
||||
SceGxmStencilOp depth_fail = SCE_GXM_STENCIL_OP_KEEP;
|
||||
SceGxmStencilOp depth_pass = SCE_GXM_STENCIL_OP_KEEP;
|
||||
};
|
||||
|
||||
struct GxmStencilStateValues {
|
||||
uint8_t compare_mask = 0xff; // TODO What's the default value?
|
||||
uint8_t write_mask = 0xff; // TODO What's the default value?
|
||||
uint8_t ref = 0; // TODO What's the default value?
|
||||
};
|
||||
|
||||
// we hash the first part of this state as a key for the pipeline cache in vulkan
|
||||
struct GxmRecordState {
|
||||
// Programs.
|
||||
Ptr<const SceGxmFragmentProgram> fragment_program;
|
||||
Ptr<const SceGxmVertexProgram> vertex_program;
|
||||
Sha256Hash vertex_program_hash;
|
||||
Sha256Hash fragment_program_hash;
|
||||
|
||||
std::array<GXMStreamInfo, SCE_GXM_MAX_VERTEX_STREAMS> vertex_streams;
|
||||
|
||||
SceGxmColorSurface color_surface;
|
||||
SceGxmDepthStencilSurface depth_stencil_surface;
|
||||
SceGxmColorBaseFormat color_base_format;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
|
||||
SceGxmCullMode cull_mode = SCE_GXM_CULL_NONE;
|
||||
SceGxmTwoSidedMode two_sided = SCE_GXM_TWO_SIDED_DISABLED;
|
||||
SceGxmRegionClipMode region_clip_mode = SCE_GXM_REGION_CLIP_OUTSIDE;
|
||||
SceGxmPolygonMode front_polygon_mode = SCE_GXM_POLYGON_MODE_TRIANGLE_FILL;
|
||||
SceGxmPolygonMode back_polygon_mode = SCE_GXM_POLYGON_MODE_TRIANGLE_FILL;
|
||||
|
||||
SceIVector2 region_clip_min;
|
||||
SceIVector2 region_clip_max;
|
||||
|
||||
GxmStencilState front_stencil_state;
|
||||
GxmStencilState back_stencil_state;
|
||||
GxmStencilStateOp front_stencil_state_op;
|
||||
GxmStencilStateOp back_stencil_state_op;
|
||||
|
||||
SceGxmDepthFunc front_depth_func = SCE_GXM_DEPTH_FUNC_LESS_EQUAL;
|
||||
SceGxmDepthFunc back_depth_func = SCE_GXM_DEPTH_FUNC_LESS_EQUAL;
|
||||
@@ -116,9 +130,35 @@ struct GxmRecordState {
|
||||
SceGxmFragmentProgramMode front_side_fragment_program_mode = SCE_GXM_FRAGMENT_PROGRAM_ENABLED;
|
||||
SceGxmFragmentProgramMode back_side_fragment_program_mode = SCE_GXM_FRAGMENT_PROGRAM_ENABLED;
|
||||
|
||||
float writing_mask = 0.0f;
|
||||
bool viewport_flat = false;
|
||||
bool is_maskupdate = false;
|
||||
|
||||
// Do not put any state not used for the Vulkan pipeline creation before vertex_streams
|
||||
std::array<GXMStreamInfo, SCE_GXM_MAX_VERTEX_STREAMS> vertex_streams;
|
||||
|
||||
// Programs.
|
||||
Ptr<const SceGxmFragmentProgram> fragment_program;
|
||||
Ptr<const SceGxmVertexProgram> vertex_program;
|
||||
|
||||
SceGxmColorSurface color_surface;
|
||||
SceGxmDepthStencilSurface depth_stencil_surface;
|
||||
|
||||
bool viewport_flat = false;
|
||||
std::array<float, 4> viewport_flip = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
float z_offset = 0.5f;
|
||||
float z_scale = 0.5f;
|
||||
|
||||
GxmStencilStateValues front_stencil_state_values;
|
||||
GxmStencilStateValues back_stencil_state_values;
|
||||
|
||||
uint32_t line_width = 1;
|
||||
|
||||
int depth_bias_unit = 0;
|
||||
int depth_bias_slope = 0;
|
||||
|
||||
SceIVector2 region_clip_min;
|
||||
SceIVector2 region_clip_max;
|
||||
|
||||
float writing_mask = 0.0f;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
@@ -132,29 +172,44 @@ struct Context {
|
||||
int render_finish_status = 0;
|
||||
int notification_finish_status = 0;
|
||||
|
||||
std::string last_draw_fragment_program_hash;
|
||||
std::string last_draw_vertex_program_hash;
|
||||
std::vector<UniformSetRequest> vertex_set_requests;
|
||||
std::vector<UniformSetRequest> fragment_set_requests;
|
||||
|
||||
Sha256Hash last_draw_fragment_program_hash;
|
||||
Sha256Hash last_draw_vertex_program_hash;
|
||||
|
||||
shader::RenderVertUniformBlock previous_vert_info;
|
||||
shader::RenderFragUniformBlock previous_frag_info;
|
||||
|
||||
shader::RenderVertUniformBlock current_vert_render_info;
|
||||
shader::RenderFragUniformBlock current_frag_render_info;
|
||||
|
||||
std::map<int, std::vector<uint8_t>> ubo_data;
|
||||
|
||||
virtual ~Context() = default;
|
||||
};
|
||||
|
||||
struct ShaderProgram {
|
||||
std::string hash;
|
||||
Sha256Hash hash;
|
||||
UniformBufferSizes uniform_buffer_sizes; // Size of the buffer in 4-bytes unit
|
||||
UniformBufferSizes uniform_buffer_data_offsets; // Offset of the buffer in 4-bytes unit
|
||||
|
||||
std::size_t max_total_uniform_buffer_storage;
|
||||
uint16_t texture_count; // max texture index used by the shader + 1
|
||||
};
|
||||
|
||||
struct FragmentProgram : ShaderProgram {
|
||||
};
|
||||
|
||||
struct VertexProgram : ShaderProgram {
|
||||
shader::usse::AttributeInformationMap attribute_infos;
|
||||
|
||||
bool stripped_symbols_checked;
|
||||
};
|
||||
|
||||
struct ShadersHash {
|
||||
std::string frag;
|
||||
std::string vert;
|
||||
Sha256Hash frag;
|
||||
Sha256Hash vert;
|
||||
};
|
||||
|
||||
struct RenderTarget {
|
||||
|
||||
@@ -19,32 +19,54 @@
|
||||
|
||||
#include "state.h"
|
||||
|
||||
struct Config;
|
||||
|
||||
namespace renderer::vulkan {
|
||||
#define VULKAN_CHECK(a) assert(a == vk::Result::eSuccess)
|
||||
|
||||
bool create(SDL_Window *window, std::unique_ptr<renderer::State> &state);
|
||||
void close(std::unique_ptr<renderer::State> &state);
|
||||
bool create(SDL_Window *window, std::unique_ptr<renderer::State> &state, const char *base_path);
|
||||
|
||||
// I think I will drop this approach but this is fine for now.
|
||||
enum class CommandType {
|
||||
General,
|
||||
Transfer,
|
||||
};
|
||||
bool create(VKState &state, std::unique_ptr<Context> &context);
|
||||
bool create(VKState &state, std::unique_ptr<RenderTarget> &rt, const SceGxmRenderTargetParams ¶ms, const FeatureState &features);
|
||||
void destroy(VKState &state, std::unique_ptr<RenderTarget> &rt);
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, VKState &state, const SceGxmProgram &program);
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, VKState &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend);
|
||||
void create(SceGxmSyncObject *sync);
|
||||
void destroy(SceGxmSyncObject *sync);
|
||||
|
||||
enum class MemoryType {
|
||||
Mappable,
|
||||
Device,
|
||||
};
|
||||
void draw(VKContext &context, SceGxmPrimitiveType type, SceGxmIndexFormat format,
|
||||
void *indices, size_t count, uint32_t instance_count, MemState &mem, const Config &config);
|
||||
|
||||
vk::Queue select_queue(VulkanState &state, CommandType type);
|
||||
void new_frame(VKContext &context);
|
||||
void update_sync_target(SceGxmSyncObject *sync, VKRenderTarget *target);
|
||||
void update_sync_signal(SceGxmSyncObject *sync);
|
||||
|
||||
bool resize_swapchain(VulkanState &state, vk::Extent2D size);
|
||||
void set_context(VKContext &context, const MemState &mem, VKRenderTarget *rt, const FeatureState &features);
|
||||
void set_uniform_buffer(VKContext &context, const ShaderProgram *program, const bool vertex_shader, const int block_num, const int size, const uint8_t *data);
|
||||
|
||||
vk::CommandBuffer create_command_buffer(VulkanState &state, CommandType type);
|
||||
void free_command_buffer(VulkanState &state, CommandType type, vk::CommandBuffer buffer);
|
||||
void sync_clipping(VKContext &context);
|
||||
void sync_stencil_func(VKContext &context, const bool is_back);
|
||||
void sync_mask(VKContext &context, const MemState &mem);
|
||||
void sync_depth_bias(VKContext &context);
|
||||
void sync_depth_data(VKContext &context);
|
||||
void sync_stencil_data(VKContext &context, const MemState &mem);
|
||||
void sync_point_line_width(VKContext &context, const bool is_front);
|
||||
void sync_texture(VKContext &context, MemState &mem, std::size_t index, SceGxmTexture texture, const Config &config,
|
||||
const std::string &base_path, const std::string &title_id);
|
||||
void sync_viewport_flat(VKContext &context);
|
||||
void sync_viewport_real(VKContext &context, const float xOffset, const float yOffset, const float zOffset,
|
||||
const float xScale, const float yScale, const float zScale);
|
||||
|
||||
void refresh_pipeline(VKContext &context);
|
||||
|
||||
namespace texture {
|
||||
|
||||
bool init(VKTextureCacheState &cache, const bool hashless_texture_cache);
|
||||
|
||||
void configure_bound_texture(const renderer::TextureCacheState &state, const SceGxmTexture &gxm_texture);
|
||||
vk::Sampler create_sampler(VKState &state, const SceGxmTexture &gxm_texture, const uint16_t mip_count = 1);
|
||||
void upload_bound_texture(VKTextureCacheState &cache, SceGxmTextureBaseFormat base_format, uint32_t width, uint32_t height,
|
||||
uint32_t mip_index, const void *pixels, int face, bool is_compressed, size_t pixels_per_stride);
|
||||
|
||||
} // namespace texture
|
||||
|
||||
vk::Buffer create_buffer(VulkanState &state, const vk::BufferCreateInfo &buffer_info, MemoryType type, VmaAllocation &allocation);
|
||||
void destroy_buffer(VulkanState &state, vk::Buffer buffer, VmaAllocation allocation);
|
||||
vk::Image create_image(VulkanState &state, const vk::ImageCreateInfo &image_info, MemoryType type, VmaAllocation &allocation);
|
||||
void destroy_image(VulkanState &state, vk::Image image, VmaAllocation allocation);
|
||||
} // namespace renderer::vulkan
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#define VULKAN_HPP_NO_CONSTRUCTORS
|
||||
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include <gxm/types.h>
|
||||
|
||||
#include <util/log.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
vk::Format translate_attribute_format(SceGxmAttributeFormat format, unsigned int component_count, bool is_integer, bool is_signed);
|
||||
vk::BlendFactor translate_blend_factor(const SceGxmBlendFactor blend_factor);
|
||||
vk::BlendOp translate_blend_func(const SceGxmBlendFunc blend_func);
|
||||
vk::PrimitiveTopology translate_primitive(SceGxmPrimitiveType primitive);
|
||||
vk::CompareOp translate_depth_func(SceGxmDepthFunc depth_func);
|
||||
vk::PolygonMode translate_polygon_mode(SceGxmPolygonMode polygon_mode);
|
||||
vk::CullModeFlags translate_cull_mode(SceGxmCullMode cull_mode);
|
||||
vk::CompareOp translate_stencil_func(SceGxmStencilFunc stencil_func);
|
||||
vk::StencilOp translate_stencil_op(SceGxmStencilOp stencil_op);
|
||||
|
||||
namespace color {
|
||||
vk::Format translate_format(SceGxmColorBaseFormat base_format);
|
||||
vk::ComponentMapping translate_swizzle(SceGxmColorFormat format);
|
||||
} // namespace color
|
||||
|
||||
namespace texture {
|
||||
vk::Format translate_format(SceGxmTextureBaseFormat base_format);
|
||||
vk::ComponentMapping translate_swizzle(SceGxmTextureFormat format);
|
||||
vk::SamplerAddressMode translate_address_mode(SceGxmTextureAddrMode src);
|
||||
vk::Filter translate_filter(SceGxmTextureFilter src);
|
||||
vk::SamplerMipmapMode translate_mimpmap_mode(SceGxmTextureFilter src);
|
||||
} // namespace texture
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,93 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <vkutil/objects.h>
|
||||
// TODO: remove this include
|
||||
#include <util/fs.h>
|
||||
|
||||
struct SceGxmProgram;
|
||||
enum SceGxmPrimitiveType : uint32_t;
|
||||
struct SceGxmVertexAttribute;
|
||||
struct MemState;
|
||||
|
||||
using Sha256Hash = std::array<uint8_t, 32>;
|
||||
|
||||
namespace renderer::vulkan {
|
||||
struct VKState;
|
||||
struct VKContext;
|
||||
|
||||
class PipelineCache {
|
||||
private:
|
||||
VKState &state;
|
||||
VKContext *current_context;
|
||||
|
||||
// does the GPU support vertex attributes with 3 components (like R16G16B16_UNORM), some (like AMD GPUs) don't
|
||||
// this is needed when creating the input state
|
||||
bool support_rgb_vertex_attribute;
|
||||
|
||||
// how much time should we wait after the last shader compilation
|
||||
// to update the disk-saved shader cache (in seconds)
|
||||
static constexpr int pipeline_cache_save_delay = 30;
|
||||
|
||||
vk::PipelineCache pipeline_cache;
|
||||
|
||||
// first index: 1 if depth-stencil is force loaded, 0 otherwise
|
||||
// second index: 1 if depth-stencil is force stored, 0 otherwise
|
||||
std::map<vk::Format, vk::RenderPass> render_passes[2][2];
|
||||
std::map<Sha256Hash, vk::ShaderModule> shaders;
|
||||
std::unordered_map<uint64_t, vk::Pipeline> pipelines;
|
||||
|
||||
// temp vars used to store the result computed by auxialiary functions before createPipeline is called
|
||||
std::vector<vk::VertexInputBindingDescription> binding_descr;
|
||||
std::vector<vk::VertexInputAttributeDescription> attr_descr;
|
||||
|
||||
vk::PipelineShaderStageCreateInfo retrieve_shader(const SceGxmProgram *program, const Sha256Hash &hash, bool is_vertex, bool maskupdate, MemState &mem, const std::vector<SceGxmVertexAttribute> *hint_attributes);
|
||||
vk::PipelineLayout retrieve_pipeline_layout(const uint16_t vert_texture_count, const uint16_t frag_texture_count);
|
||||
vk::PipelineVertexInputStateCreateInfo get_vertex_input_state(MemState &mem);
|
||||
|
||||
public:
|
||||
// if not 0, next time the pipeline cache should be saved (in seconds since epoch)
|
||||
uint64_t next_pipeline_cache_save = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
vk::DescriptorSetLayout uniforms_layout;
|
||||
// used for the mask, color attachment
|
||||
vk::DescriptorSetLayout attachments_layout;
|
||||
std::array<vk::DescriptorSetLayout, 17> vertex_textures_layout;
|
||||
std::array<vk::DescriptorSetLayout, 17> fragment_textures_layout;
|
||||
|
||||
// there are at most 16 different textures in the fragment shader, and 16 in the vertex shader
|
||||
// first index is vertex, second is fragment
|
||||
vk::PipelineLayout pipeline_layouts[17][17] = {};
|
||||
|
||||
explicit PipelineCache(VKState &state);
|
||||
void init();
|
||||
|
||||
void read_pipeline_cache();
|
||||
void save_pipeline_cache();
|
||||
|
||||
vk::RenderPass retrieve_render_pass(vk::Format format, uint32_t zls_control);
|
||||
vk::Pipeline retrieve_pipeline(VKContext &context, SceGxmPrimitiveType &type, MemState &mem);
|
||||
|
||||
bool precompile_shader(const Sha256Hash &hash);
|
||||
};
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,104 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vkutil/objects.h>
|
||||
|
||||
struct SDL_Window;
|
||||
struct SceFVector2;
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
struct VKState;
|
||||
|
||||
class ScreenRenderer {
|
||||
public:
|
||||
VKState &state;
|
||||
SDL_Window *window;
|
||||
|
||||
vk::SurfaceKHR surface;
|
||||
vk::SwapchainKHR swapchain;
|
||||
|
||||
vk::SurfaceCapabilitiesKHR surface_capabilities;
|
||||
vk::SurfaceFormatKHR surface_format;
|
||||
vk::PresentModeKHR present_mode;
|
||||
|
||||
vk::Extent2D extent;
|
||||
uint32_t swapchain_size;
|
||||
std::vector<vk::Image> swapchain_images;
|
||||
std::vector<vk::ImageView> swapchain_views;
|
||||
std::vector<vk::Framebuffer> swapchain_framebuffers;
|
||||
std::vector<vk::CommandBuffer> command_buffers;
|
||||
std::vector<vk::Fence> fences;
|
||||
|
||||
vk::ShaderModule shader_vertex;
|
||||
vk::ShaderModule shader_fragment;
|
||||
vk::ShaderModule shader_fragment_fxaa;
|
||||
vk::RenderPass render_pass;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
std::vector<vk::DescriptorSet> descriptor_sets;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
|
||||
vk::Pipeline pipeline;
|
||||
vk::Pipeline pipeline_fxaa;
|
||||
|
||||
vk::Semaphore image_acquired_semaphore;
|
||||
vk::Semaphore image_ready_semaphore;
|
||||
|
||||
vma::Allocation vao_allocation;
|
||||
vk::Buffer vao;
|
||||
std::vector<std::array<float, 4>> last_uvs;
|
||||
|
||||
std::vector<vkutil::Image> vita_surface;
|
||||
vk::Sampler vita_surface_sampler;
|
||||
vma::Allocation vita_surface_staging_alloc;
|
||||
vma::AllocationInfo vita_surface_staging_info;
|
||||
vk::Buffer vita_surface_staging;
|
||||
|
||||
vk::CommandBuffer current_cmd_buffer;
|
||||
|
||||
// these are used by the gui
|
||||
uint32_t swapchain_image_idx = ~0;
|
||||
// set to true after a window resize, in this case the pipeline needs to be rebuilt
|
||||
bool need_rebuild = false;
|
||||
// is fxaa used
|
||||
bool enable_fxaa = false;
|
||||
|
||||
ScreenRenderer(VKState &state);
|
||||
|
||||
bool create(SDL_Window *window);
|
||||
// called after the logical device has been created
|
||||
bool setup(const char *base_path);
|
||||
void cleanup();
|
||||
|
||||
bool acquire_swapchain_image(bool start_render_pass = false);
|
||||
void render(vk::ImageView image_view, vk::ImageLayout layout, std::array<float, 4> &uvs, SceFVector2 &texture_size);
|
||||
void swap_window();
|
||||
|
||||
private:
|
||||
void create_render_pass();
|
||||
void create_layout_sync();
|
||||
void create_swapchain();
|
||||
vk::Pipeline create_graphics_pipeline_impl(std::array<vk::PipelineShaderStageCreateInfo, 2> &shader_stages);
|
||||
bool create_graphics_pipelines();
|
||||
void copy_to_vao(const void *data);
|
||||
void create_surface_image();
|
||||
void destroy_swapchain();
|
||||
};
|
||||
} // namespace renderer::vulkan
|
||||
@@ -20,53 +20,62 @@
|
||||
#include <renderer/state.h>
|
||||
#include <renderer/types.h>
|
||||
|
||||
#include <renderer/vulkan/pipeline_cache.h>
|
||||
#include <renderer/vulkan/screen_renderer.h>
|
||||
#include <renderer/vulkan/surface_cache.h>
|
||||
#include <renderer/vulkan/types.h>
|
||||
|
||||
typedef void *ImTextureID;
|
||||
|
||||
namespace renderer::vulkan {
|
||||
struct VulkanState : public renderer::State {
|
||||
struct VKState : public renderer::State {
|
||||
// 0 = automatic, > 0 = order in instance.enumeratePhysicalDevices
|
||||
int gpu_idx;
|
||||
|
||||
VKSurfaceCache surface_cache;
|
||||
PipelineCache pipeline_cache;
|
||||
VKTextureCacheState texture_cache;
|
||||
|
||||
vk::Instance instance;
|
||||
vk::Device device;
|
||||
|
||||
ScreenRenderer screen_renderer;
|
||||
|
||||
// Used for memory allocation and general query later.
|
||||
vk::PhysicalDevice physical_device;
|
||||
vk::PhysicalDeviceProperties physical_device_properties;
|
||||
vk::PhysicalDeviceFeatures physical_device_features;
|
||||
vk::SurfaceCapabilitiesKHR physical_device_surface_capabilities;
|
||||
std::vector<vk::SurfaceFormatKHR> physical_device_surface_formats;
|
||||
vk::PhysicalDeviceMemoryProperties physical_device_memory;
|
||||
std::vector<vk::QueueFamilyProperties> physical_device_queue_families;
|
||||
|
||||
VmaAllocator allocator;
|
||||
vma::Allocator allocator;
|
||||
|
||||
uint32_t general_family_index = 0;
|
||||
uint32_t transfer_family_index = 0;
|
||||
uint32_t general_queue_last = 0;
|
||||
uint32_t transfer_queue_last = 0;
|
||||
std::vector<vk::Queue> general_queues;
|
||||
std::vector<vk::Queue> transfer_queues;
|
||||
vk::Queue general_queue;
|
||||
vk::Queue transfer_queue;
|
||||
|
||||
// These might be merged into one queue, but for now they are different.
|
||||
vk::CommandPool general_command_pool;
|
||||
// Transfer pool has transient bit set.
|
||||
vk::CommandPool transfer_command_pool;
|
||||
|
||||
vk::CommandBuffer general_command_buffer;
|
||||
|
||||
vk::SurfaceKHR surface;
|
||||
vk::SwapchainKHR swapchain;
|
||||
|
||||
// These would be vectors...
|
||||
uint32_t swapchain_width = 0, swapchain_height = 0;
|
||||
vk::Image swapchain_images[2];
|
||||
vk::ImageView swapchain_views[2];
|
||||
VKState(int gpu_idx);
|
||||
|
||||
bool init(const char *base_path, const bool hashless_texture_cache) override;
|
||||
bool create(SDL_Window *window, std::unique_ptr<renderer::State> &state, const char *base_path);
|
||||
void cleanup();
|
||||
void render_frame(const SceFVector2 &viewport_pos, const SceFVector2 &viewport_size, const DisplayState &display,
|
||||
const GxmState &gxm, MemState &mem) override;
|
||||
void swap_window(SDL_Window *window) override;
|
||||
void set_fxaa(bool enable_fxaa) override;
|
||||
int get_max_anisotropic_filtering() override;
|
||||
void set_anisotropic_filtering(int anisotropic_filtering) override;
|
||||
std::vector<std::string> get_gpu_list() override;
|
||||
|
||||
void precompile_shader(const ShadersHash &hash) override;
|
||||
void preclose_action() override;
|
||||
};
|
||||
} // namespace renderer::vulkan
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <renderer/surface_cache.h>
|
||||
|
||||
#include <vkutil/objects.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
struct VKRenderTarget;
|
||||
struct VKState;
|
||||
|
||||
struct SurfaceCacheInfo {
|
||||
enum {
|
||||
FLAG_DIRTY = 1 << 0,
|
||||
FLAG_FREE = 1 << 1
|
||||
};
|
||||
|
||||
std::uint32_t flags = FLAG_FREE;
|
||||
vkutil::Image texture;
|
||||
};
|
||||
|
||||
struct CastedTexture {
|
||||
vkutil::Image texture;
|
||||
// only used if an image to image copy is not possible
|
||||
vkutil::Buffer transition_buffer;
|
||||
uint64_t last_used_time = 0; // Use for garbage collect on the next frame
|
||||
uint64_t scene_timestamp = 0;
|
||||
uint32_t cropped_x = 0;
|
||||
uint32_t cropped_y = 0;
|
||||
uint32_t cropped_width = 0;
|
||||
uint32_t cropped_height = 0;
|
||||
SceGxmColorBaseFormat format;
|
||||
};
|
||||
|
||||
struct ColorSurfaceCacheInfo : public SurfaceCacheInfo {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint16_t original_width;
|
||||
uint16_t original_height;
|
||||
uint16_t pixel_stride;
|
||||
size_t total_bytes;
|
||||
|
||||
SceGxmColorBaseFormat format;
|
||||
vk::ComponentMapping swizzle;
|
||||
|
||||
Ptr<void> data;
|
||||
std::vector<CastedTexture> casted_textures;
|
||||
// same image with a different view(swizzle) used for sampling
|
||||
vkutil::Image sampled_image;
|
||||
};
|
||||
|
||||
struct DepthStencilSurfaceCacheInfo : public SurfaceCacheInfo {
|
||||
SceGxmDepthStencilSurface surface;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
// used when reading from this depth stencil in a shader
|
||||
vkutil::Image read_only;
|
||||
// used so that we copy the depth stencil at most once per scene
|
||||
uint64_t scene_timestamp = 0;
|
||||
};
|
||||
|
||||
class VKSurfaceCache : public SurfaceCache {
|
||||
private:
|
||||
VKState &state;
|
||||
|
||||
static constexpr std::uint32_t MAX_CACHE_SIZE_PER_CONTAINER = 20;
|
||||
|
||||
std::map<Address, ColorSurfaceCacheInfo> color_surface_textures;
|
||||
std::array<DepthStencilSurfaceCacheInfo, MAX_CACHE_SIZE_PER_CONTAINER> depth_stencil_textures;
|
||||
std::map<std::pair<vk::ImageView, vk::ImageView>, vk::Framebuffer> framebuffer_array;
|
||||
|
||||
std::vector<Address> last_use_color_surface_index;
|
||||
std::vector<size_t> last_use_depth_stencil_surface_index;
|
||||
|
||||
VKRenderTarget *target = nullptr;
|
||||
|
||||
// destroy all framebuffers using view as their color or depth-stencil
|
||||
void destroy_framebuffers(vk::ImageView view);
|
||||
|
||||
void destroy_surface(ColorSurfaceCacheInfo &info);
|
||||
void destroy_surface(DepthStencilSurfaceCacheInfo &info);
|
||||
|
||||
public:
|
||||
explicit VKSurfaceCache(VKState &state);
|
||||
|
||||
// when writing, the swizzled given to this function is inversed
|
||||
vkutil::Image *retrieve_color_surface_texture_handle(uint16_t width, uint16_t height, const uint16_t pixel_stride,
|
||||
const SceGxmColorBaseFormat base_format, Ptr<void> address, SurfaceTextureRetrievePurpose purpose, vk::ComponentMapping &swizzle,
|
||||
uint16_t *stored_height = nullptr, uint16_t *stored_width = nullptr);
|
||||
|
||||
vkutil::Image *retrieve_depth_stencil_texture_handle(const MemState &mem, const SceGxmDepthStencilSurface &surface, int32_t force_width = -1,
|
||||
int32_t force_height = -1, const bool is_reading = false);
|
||||
|
||||
vk::Framebuffer retrieve_framebuffer_handle(const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
|
||||
vk::RenderPass render_pass, vkutil::Image **color_texture_handle = nullptr, vkutil::Image **ds_texture_handle = nullptr,
|
||||
uint16_t *stored_height = nullptr);
|
||||
|
||||
// destroy all framebuffers associated with render_target
|
||||
// (meaning their color or depth-stencil surface is not backed by memory)
|
||||
void destroy_associated_framebuffers(const VKRenderTarget *render_target);
|
||||
|
||||
vk::ImageView sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const uint32_t pitch, std::array<float, 4> &uvs, const int res_multiplier, SceFVector2 &texture_size);
|
||||
|
||||
void set_render_target(VKRenderTarget *new_target) {
|
||||
target = new_target;
|
||||
}
|
||||
};
|
||||
} // namespace renderer::vulkan
|
||||
@@ -17,13 +17,150 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <renderer/texture_cache_state.h>
|
||||
#include <renderer/types.h>
|
||||
|
||||
#include <vk_mem_alloc.h>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <vkutil/objects.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
struct VulkanContext : renderer::Context {
|
||||
|
||||
struct VKState;
|
||||
struct VKRenderTarget;
|
||||
|
||||
constexpr int MAX_FRAMES_RENDERING = 3;
|
||||
|
||||
struct VKTextureCacheState : public renderer::TextureCacheState {
|
||||
VKState &state;
|
||||
|
||||
vma::Allocation alloc;
|
||||
vma::AllocationInfo alloc_info{};
|
||||
vk::Buffer staging_buffer{};
|
||||
vk::Fence wait_fence;
|
||||
|
||||
std::array<vkutil::Image, TextureCacheSize> textures;
|
||||
|
||||
vkutil::Image *current_texture = nullptr;
|
||||
|
||||
VKTextureCacheState(VKState &state);
|
||||
};
|
||||
|
||||
struct FrameObject {
|
||||
vk::CommandPool command_pool;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
|
||||
std::vector<vk::Fence> rendered_fences;
|
||||
|
||||
// destroy gpu objects MAX_FRAMES_RENDERING frames later to make sure they are no longer being used
|
||||
vkutil::DestroyQueue destroy_queue;
|
||||
};
|
||||
|
||||
struct VKContext : public renderer::Context {
|
||||
// GXM Context Info
|
||||
VKState &state;
|
||||
|
||||
std::array<FrameObject, MAX_FRAMES_RENDERING> frames;
|
||||
int current_frame_idx = 0;
|
||||
uint64_t frame_timestamp = 0;
|
||||
uint64_t scene_timestamp = 0;
|
||||
|
||||
vkutil::HostRingBuffer vertex_stream_ring_buffer;
|
||||
vkutil::HostRingBuffer index_stream_ring_buffer;
|
||||
vkutil::HostRingBuffer vertex_uniform_stream_ring_buffer;
|
||||
vkutil::HostRingBuffer fragment_uniform_stream_ring_buffer;
|
||||
vkutil::LocalRingBuffer vertex_info_uniform_buffer;
|
||||
vkutil::LocalRingBuffer fragment_info_uniform_buffer;
|
||||
|
||||
vk::DescriptorImageInfo vertex_textures[SCE_GXM_MAX_TEXTURE_UNITS] = {};
|
||||
vk::DescriptorImageInfo fragment_textures[SCE_GXM_MAX_TEXTURE_UNITS] = {};
|
||||
|
||||
bool vertex_uniform_storage_allocated = false;
|
||||
bool fragment_uniform_storage_allocated = false;
|
||||
|
||||
std::array<vk::DeviceSize, SCE_GXM_MAX_VERTEX_STREAMS> vertex_buffer_offsets = {};
|
||||
|
||||
// descriptor pool for dynamic uniforms (allocated once for the whole game)
|
||||
vk::DescriptorPool global_descriptor_pool;
|
||||
// we will use this descriptor set for all the draws
|
||||
vk::DescriptorSet global_set;
|
||||
|
||||
// descriptor set used to store the mask and the color attachment
|
||||
vk::DescriptorSet rendertarget_set;
|
||||
|
||||
uint16_t last_vert_texture_count = ~0;
|
||||
vk::DescriptorSet last_vert_texture_descriptor;
|
||||
uint16_t last_frag_texture_count = ~0;
|
||||
vk::DescriptorSet last_frag_texture_descriptor;
|
||||
|
||||
VKRenderTarget *render_target = nullptr;
|
||||
vk::Viewport viewport;
|
||||
vk::Rect2D scissor;
|
||||
SceGxmPrimitiveType last_primitive;
|
||||
|
||||
vk::RenderPass current_render_pass;
|
||||
vk::Pipeline current_pipeline;
|
||||
|
||||
vk::Framebuffer current_framebuffer;
|
||||
uint16_t current_framebuffer_height;
|
||||
vkutil::Image *current_color_attachment;
|
||||
vkutil::Image *current_ds_attachment;
|
||||
|
||||
bool is_recording = false;
|
||||
bool in_renderpass = false;
|
||||
bool refresh_pipeline = false;
|
||||
// command buffer used to record the current scene
|
||||
vk::CommandBuffer render_cmd{};
|
||||
// command buffer used for commands that need to be executed before render_cmd (mostly because they can't be done during a render pass)
|
||||
vk::CommandBuffer prerender_cmd{};
|
||||
VKRenderTarget *cmd_target = nullptr;
|
||||
|
||||
// image used when a shader read from an image that has not been set
|
||||
vkutil::Image default_image;
|
||||
|
||||
inline FrameObject &frame() {
|
||||
return frames[current_frame_idx];
|
||||
}
|
||||
|
||||
explicit VKContext(VKState &state);
|
||||
// TODO: properly destroy the context
|
||||
~VKContext() override = default;
|
||||
|
||||
void start_recording();
|
||||
void start_render_pass();
|
||||
void stop_render_pass();
|
||||
void stop_recording();
|
||||
};
|
||||
|
||||
struct VKRenderTarget : public renderer::RenderTarget {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
vkutil::Image mask;
|
||||
vkutil::Image color;
|
||||
vkutil::Image depthstencil;
|
||||
|
||||
uint64_t last_used_frame = 0;
|
||||
|
||||
// sync objects
|
||||
std::vector<vk::Fence> fences;
|
||||
// the current fence we're at
|
||||
int fence_idx = 0;
|
||||
|
||||
std::array<std::vector<vk::CommandBuffer>, MAX_FRAMES_RENDERING> cmd_buffers;
|
||||
// the command buffer index we're at in a frame
|
||||
int cmd_buffer_idx = 0;
|
||||
|
||||
VKRenderTarget(VKState &state, vma::Allocator allocator, uint16_t width, uint16_t height, uint16_t samples_per_frame);
|
||||
~VKRenderTarget() override = default;
|
||||
};
|
||||
|
||||
struct VKFragmentProgram : public renderer::FragmentProgram {
|
||||
vk::PipelineColorBlendAttachmentState blending;
|
||||
uint64_t blending_hash;
|
||||
};
|
||||
|
||||
// used with SceGxmSyncObject
|
||||
struct SyncExtraData {
|
||||
VKRenderTarget *render_target = nullptr;
|
||||
// fences the display queue must wait for before displaying the buffer
|
||||
std::vector<vk::Fence> fences;
|
||||
};
|
||||
} // namespace renderer::vulkan
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include <renderer/state.h>
|
||||
#include <renderer/types.h>
|
||||
|
||||
#include <renderer/vulkan/types.h>
|
||||
|
||||
#include <functional>
|
||||
#include <util/log.h>
|
||||
#include <util/string_utils.h>
|
||||
@@ -48,13 +50,12 @@ bool is_cmd_ready(MemState &mem, CommandList &command_list) {
|
||||
return true;
|
||||
|
||||
SceGxmSyncObject *sync = reinterpret_cast<Ptr<SceGxmSyncObject> *>(&command_list.first->data[0])->get(mem);
|
||||
const uint32_t timestamp = *reinterpret_cast<uint32_t *>(&command_list.first->data[4]);
|
||||
const uint32_t timestamp = *reinterpret_cast<uint32_t *>(&command_list.first->data[sizeof(uint32_t) + sizeof(void *)]);
|
||||
|
||||
return sync->timestamp_current >= timestamp;
|
||||
}
|
||||
|
||||
void process_batch(renderer::State &state, const FeatureState &features, MemState &mem, Config &config, CommandList &command_list, const char *base_path,
|
||||
const char *title_id, const char *self_name) {
|
||||
void process_batch(renderer::State &state, const FeatureState &features, MemState &mem, Config &config, CommandList &command_list) {
|
||||
using CommandHandlerFunc = std::function<void(renderer::State &, MemState &, Config &,
|
||||
CommandHelper &, const FeatureState &, Context *, const char *, const char *, const char *)>;
|
||||
|
||||
@@ -72,6 +73,7 @@ void process_batch(renderer::State &state, const FeatureState &features, MemStat
|
||||
{ CommandOpcode::SignalSyncObject, cmd_handle_signal_sync_object },
|
||||
{ CommandOpcode::WaitSyncObject, cmd_handle_wait_sync_object },
|
||||
{ CommandOpcode::SignalNotification, cmd_handle_notification },
|
||||
{ CommandOpcode::NewFrame, cmd_new_frame },
|
||||
{ CommandOpcode::DestroyRenderTarget, cmd_handle_destroy_render_target },
|
||||
{ CommandOpcode::DestroyContext, cmd_handle_destroy_context }
|
||||
};
|
||||
@@ -89,7 +91,7 @@ void process_batch(renderer::State &state, const FeatureState &features, MemStat
|
||||
LOG_ERROR("Unimplemented command opcode {}", static_cast<int>(cmd->opcode));
|
||||
} else {
|
||||
CommandHelper helper(cmd);
|
||||
handler->second(state, mem, config, helper, features, command_list.context, base_path, title_id, self_name);
|
||||
handler->second(state, mem, config, helper, features, command_list.context, state.base_path, state.title_id, state.self_name);
|
||||
}
|
||||
|
||||
Command *last_cmd = cmd;
|
||||
@@ -103,8 +105,7 @@ void process_batch(renderer::State &state, const FeatureState &features, MemStat
|
||||
} while (true);
|
||||
}
|
||||
|
||||
void process_batches(renderer::State &state, const FeatureState &features, MemState &mem, Config &config, const char *base_path,
|
||||
const char *title_id, const char *self_name) {
|
||||
void process_batches(renderer::State &state, const FeatureState &features, MemState &mem, Config &config) {
|
||||
while (!state.should_display.exchange(false)) {
|
||||
auto cmd_list = state.command_buffer_queue.top(3);
|
||||
|
||||
@@ -115,7 +116,7 @@ void process_batches(renderer::State &state, const FeatureState &features, MemSt
|
||||
}
|
||||
|
||||
state.command_buffer_queue.pop();
|
||||
process_batch(state, features, mem, config, *cmd_list, base_path, title_id, self_name);
|
||||
process_batch(state, features, mem, config, *cmd_list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,17 +23,34 @@
|
||||
#include <renderer/types.h>
|
||||
|
||||
#include <renderer/gl/functions.h>
|
||||
#ifdef USE_VULKAN
|
||||
#include <renderer/vulkan/functions.h>
|
||||
#endif
|
||||
#include <renderer/texture_cache_state.h>
|
||||
#include <renderer/vulkan/functions.h>
|
||||
#include <renderer/vulkan/state.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
#include <gxm/types.h>
|
||||
#include <renderer/functions.h>
|
||||
#include <util/log.h>
|
||||
#include <util/string_utils.h>
|
||||
|
||||
namespace renderer {
|
||||
|
||||
static void layout_ssbo_offset_from_uniform_buffer_sizes(UniformBufferSizes &sizes, UniformBufferSizes &offsets, std::size_t &total_hold) {
|
||||
std::uint32_t last_offset = 0;
|
||||
|
||||
for (std::size_t i = 0; i < sizes.size(); i++) {
|
||||
if (sizes[i] != 0) {
|
||||
// Round to vec4 unit
|
||||
offsets[i] = last_offset;
|
||||
last_offset += ((sizes[i] + 3) / 4 * 4);
|
||||
} else {
|
||||
offsets[i] = static_cast<std::uint32_t>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
total_hold = static_cast<std::size_t>(last_offset);
|
||||
}
|
||||
|
||||
COMMAND(handle_create_context) {
|
||||
std::unique_ptr<Context> *ctx = helper.pop<std::unique_ptr<Context> *>();
|
||||
bool result = false;
|
||||
@@ -44,12 +61,19 @@ COMMAND(handle_create_context) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan: {
|
||||
result = vulkan::create(dynamic_cast<vulkan::VKState &>(renderer), *ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renderer.context = ctx->get();
|
||||
|
||||
complete_command(renderer, helper, result);
|
||||
}
|
||||
|
||||
@@ -67,22 +91,38 @@ COMMAND(handle_create_render_target) {
|
||||
bool result = false;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
result = gl::create(static_cast<gl::GLState &>(renderer), *render_target, *params, features);
|
||||
case Backend::OpenGL:
|
||||
result = gl::create(dynamic_cast<gl::GLState &>(renderer), *render_target, *params, features);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
case Backend::Vulkan:
|
||||
result = vulkan::create(dynamic_cast<vulkan::VKState &>(renderer), *render_target, *params, features);
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
complete_command(renderer, helper, result);
|
||||
}
|
||||
|
||||
COMMAND(handle_destroy_render_target) {
|
||||
std::unique_ptr<RenderTarget> *render_target = helper.pop<std::unique_ptr<RenderTarget> *>();
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
// nothing to do
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::destroy(dynamic_cast<vulkan::VKState &>(renderer), *render_target);
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
}
|
||||
|
||||
render_target->reset();
|
||||
|
||||
complete_command(renderer, helper, 0);
|
||||
@@ -94,32 +134,77 @@ COMMAND(handle_prepare_overall_buffer_storage) {
|
||||
// Client
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, State &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id) {
|
||||
switch (state.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
return gl::create(fp, static_cast<gl::GLState &>(state), program, blend, gxp_ptr_map, base_path, title_id);
|
||||
}
|
||||
|
||||
default: {
|
||||
REPORT_MISSING(state.current_backend);
|
||||
case Backend::OpenGL:
|
||||
gl::create(fp, dynamic_cast<gl::GLState &>(state), program, blend);
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::create(fp, dynamic_cast<vulkan::VKState &>(state), program, blend);
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(state.current_backend);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Try to hash this shader
|
||||
fp->hash = sha256(&program, program.size);
|
||||
gxp_ptr_map.emplace(fp->hash, &program);
|
||||
|
||||
shader::usse::get_uniform_buffer_sizes(program, fp->uniform_buffer_sizes);
|
||||
layout_ssbo_offset_from_uniform_buffer_sizes(fp->uniform_buffer_sizes, fp->uniform_buffer_data_offsets, fp->max_total_uniform_buffer_storage);
|
||||
fp->texture_count = gxp::get_texture_count(program);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, State &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id) {
|
||||
switch (state.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
return gl::create(vp, static_cast<gl::GLState &>(state), program, gxp_ptr_map, base_path, title_id);
|
||||
}
|
||||
|
||||
default: {
|
||||
REPORT_MISSING(state.current_backend);
|
||||
case Backend::OpenGL:
|
||||
gl::create(vp, dynamic_cast<gl::GLState &>(state), program);
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::create(vp, dynamic_cast<vulkan::VKState &>(state), program);
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(state.current_backend);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Hash this shader
|
||||
vp->hash = sha256(&program, program.size);
|
||||
gxp_ptr_map.emplace(vp->hash, &program);
|
||||
|
||||
shader::usse::get_uniform_buffer_sizes(program, vp->uniform_buffer_sizes);
|
||||
shader::usse::get_attribute_informations(program, vp->attribute_infos);
|
||||
layout_ssbo_offset_from_uniform_buffer_sizes(vp->uniform_buffer_sizes, vp->uniform_buffer_data_offsets, vp->max_total_uniform_buffer_storage);
|
||||
vp->texture_count = gxp::get_texture_count(program);
|
||||
|
||||
if (vp->attribute_infos.empty()) {
|
||||
vp->stripped_symbols_checked = false;
|
||||
} else {
|
||||
vp->stripped_symbols_checked = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void create(SceGxmSyncObject *sync, State &state) {
|
||||
// Set as if the last display was already done
|
||||
sync->last_display = 0;
|
||||
sync->timestamp_current = 0;
|
||||
sync->timestamp_ahead = 0;
|
||||
sync->extra = 0;
|
||||
|
||||
if (state.current_backend == Backend::Vulkan)
|
||||
vulkan::create(sync);
|
||||
}
|
||||
|
||||
void destroy(SceGxmSyncObject *sync, State &state) {
|
||||
if (state.current_backend == Backend::Vulkan)
|
||||
vulkan::destroy(sync);
|
||||
}
|
||||
|
||||
bool init(SDL_Window *window, std::unique_ptr<State> &state, Backend backend, const Config &config, const char *base_path) {
|
||||
@@ -129,13 +214,13 @@ bool init(SDL_Window *window, std::unique_ptr<State> &state, Backend backend, co
|
||||
if (!gl::create(window, state, base_path, config.hashless_texture_cache))
|
||||
return false;
|
||||
break;
|
||||
#ifdef USE_VULKAN
|
||||
|
||||
case Backend::Vulkan:
|
||||
state = std::make_unique<vulkan::VulkanState>();
|
||||
if (!vulkan::create(window, state))
|
||||
state = std::make_unique<vulkan::VKState>(config.gpu_idx);
|
||||
if (!vulkan::create(window, state, base_path))
|
||||
return false;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
LOG_ERROR("Cannot create a renderer with unsupported backend {}.", static_cast<int>(backend));
|
||||
return false;
|
||||
|
||||
@@ -249,44 +249,5 @@ GLenum get_raw_store_upload_data_type(SceGxmColorBaseFormat base_format) {
|
||||
|
||||
return GL_RGBA_INTEGER;
|
||||
}
|
||||
|
||||
bool convert_base_texture_format_to_base_color_format(SceGxmTextureBaseFormat format, SceGxmColorBaseFormat &color_format) {
|
||||
static const std::map<std::uint32_t, std::uint32_t> TEXTURE_TO_COLOR_FORMAT_MAPPING = {
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8, SCE_GXM_COLOR_BASE_FORMAT_U8U8U8U8 },
|
||||
// { SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8, SCE_GXM_COLOR_BASE_FORMAT_U8U8U8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U5U6U5, SCE_GXM_COLOR_BASE_FORMAT_U5U6U5 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U1U5U5U5, SCE_GXM_COLOR_BASE_FORMAT_U1U5U5U5 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U4U4U4U4, SCE_GXM_COLOR_BASE_FORMAT_U4U4U4U4 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8U3U3U2, SCE_GXM_COLOR_BASE_FORMAT_U8U3U3U2 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F16, SCE_GXM_COLOR_BASE_FORMAT_F16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F16F16, SCE_GXM_COLOR_BASE_FORMAT_F16F16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F32, SCE_GXM_COLOR_BASE_FORMAT_F32 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S16, SCE_GXM_COLOR_BASE_FORMAT_S16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S16S16, SCE_GXM_COLOR_BASE_FORMAT_S16S16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U16, SCE_GXM_COLOR_BASE_FORMAT_U16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U16U16, SCE_GXM_COLOR_BASE_FORMAT_U16U16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U2U10U10U10, SCE_GXM_COLOR_BASE_FORMAT_U2U10U10U10 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8, SCE_GXM_COLOR_BASE_FORMAT_U8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S8, SCE_GXM_COLOR_BASE_FORMAT_S8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S5S5U6, SCE_GXM_COLOR_BASE_FORMAT_S5S5U6 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8U8, SCE_GXM_COLOR_BASE_FORMAT_U8U8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S8S8, SCE_GXM_COLOR_BASE_FORMAT_S8S8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8S8, SCE_GXM_COLOR_BASE_FORMAT_S8S8S8S8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F16F16F16F16, SCE_GXM_COLOR_BASE_FORMAT_F16F16F16F16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F32F32, SCE_GXM_COLOR_BASE_FORMAT_F32F32 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F11F11F10, SCE_GXM_COLOR_BASE_FORMAT_F11F11F10 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9, SCE_GXM_COLOR_BASE_FORMAT_SE5M9M9M9 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9, SCE_GXM_COLOR_BASE_FORMAT_SE5M9M9M9 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U2F10F10F10, SCE_GXM_COLOR_BASE_FORMAT_U2F10F10F10 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U32U32, SCE_GXM_COLOR_BASE_FORMAT_F32F32 }
|
||||
};
|
||||
|
||||
auto ite = TEXTURE_TO_COLOR_FORMAT_MAPPING.find(format);
|
||||
if (ite == TEXTURE_TO_COLOR_FORMAT_MAPPING.end())
|
||||
return false;
|
||||
|
||||
color_format = static_cast<SceGxmColorBaseFormat>(ite->second);
|
||||
return true;
|
||||
}
|
||||
} // namespace color
|
||||
} // namespace renderer::gl
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/profile.h>
|
||||
#include <renderer/shaders.h>
|
||||
#include <renderer/types.h>
|
||||
|
||||
#include <renderer/gl/functions.h>
|
||||
@@ -105,42 +106,11 @@ static SharedGLObject compile_spirv(GLenum type, const std::vector<std::uint32_t
|
||||
return shader;
|
||||
}
|
||||
|
||||
static void save_shaders_cache_hashs(std::vector<ShadersHash> &shaders_cache_hashs, const char *base_path, const char *title_id, const char *self_name) {
|
||||
const auto shaders_path{ fs::path(base_path) / "cache/shaders" / title_id / self_name };
|
||||
if (!fs::exists(shaders_path))
|
||||
fs::create_directory(shaders_path);
|
||||
fs::ofstream shaders_hashs(shaders_path / "hashs.dat", std::ios::out | std::ios::binary);
|
||||
|
||||
if (shaders_hashs.is_open()) {
|
||||
// Write Size of shaders cache hashes list
|
||||
const auto size = shaders_cache_hashs.size();
|
||||
shaders_hashs.write((char *)&size, sizeof(size));
|
||||
|
||||
// Write version of cache
|
||||
const uint32_t versionInFile = shader::CURRENT_VERSION;
|
||||
shaders_hashs.write((char *)&versionInFile, sizeof(uint32_t));
|
||||
|
||||
// Write shader hash list
|
||||
for (const auto &hash : shaders_cache_hashs) {
|
||||
auto write = [&shaders_hashs](const std::string &i) {
|
||||
const auto size = i.length();
|
||||
|
||||
shaders_hashs.write((char *)&size, sizeof(size));
|
||||
shaders_hashs.write(i.c_str(), size);
|
||||
};
|
||||
|
||||
write(hash.frag);
|
||||
write(hash.vert);
|
||||
}
|
||||
shaders_hashs.close();
|
||||
}
|
||||
}
|
||||
|
||||
static std::string convert_string_to_hex(const std::string &hash) {
|
||||
static std::string convert_hash_to_hex(const Sha256Hash &hash) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
for (size_t i = 0; hash.length() > i; ++i) {
|
||||
ss << std::setw(2) << static_cast<uint32_t>(static_cast<unsigned char>(hash[i]));
|
||||
for (size_t i = 0; hash.size() > i; ++i) {
|
||||
ss << std::setw(2) << static_cast<uint32_t>(hash[i]);
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
@@ -184,12 +154,12 @@ static SharedGLObject compile_program(ProgramCache &program_cache, const SharedG
|
||||
}
|
||||
|
||||
static SharedGLObject compile_shader(const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, const std::string &hash_hex,
|
||||
const char *type_str, const GLenum type, ShaderCache &cache, const std::string &hash) {
|
||||
const char *type_str, const GLenum type, ShaderCache &cache, const Sha256Hash &hash) {
|
||||
// Set Shader version with hash
|
||||
const std::string hash_hex_ver = shader_version + "-" + hash_hex;
|
||||
|
||||
// Load Shader
|
||||
const std::string shader = pre_load_glsl_shader(hash_hex_ver.c_str(), type_str, base_path, title_id, self_name);
|
||||
const std::string shader = pre_load_shader_glsl(hash_hex_ver.c_str(), type_str, base_path, title_id, self_name);
|
||||
if (shader.empty()) {
|
||||
LOG_WARN("{} shader is empty or not found:\n{}", type_str, hash_hex);
|
||||
return SharedGLObject();
|
||||
@@ -209,7 +179,7 @@ static SharedGLObject compile_shader(const char *base_path, const char *title_id
|
||||
return obj;
|
||||
}
|
||||
|
||||
static std::vector<ShadersHash>::iterator get_shaders_hash_index(std::vector<ShadersHash> &shaders_cache_hashs, const std::string &frag_hash, const std::string &vert_hash) {
|
||||
static std::vector<ShadersHash>::iterator get_shaders_hash_index(std::vector<ShadersHash> &shaders_cache_hashs, const Sha256Hash &frag_hash, const Sha256Hash &vert_hash) {
|
||||
const auto shader_hash_index = std::find_if(shaders_cache_hashs.begin(), shaders_cache_hashs.end(), [&](const ShadersHash &h) {
|
||||
return (h.frag == frag_hash) && (h.vert == vert_hash);
|
||||
});
|
||||
@@ -221,7 +191,7 @@ void pre_compile_program(GLState &renderer, const char *base_path, const char *t
|
||||
const auto shader_path{ fs::path(base_path) / "cache/shaders" / title_id / self_name };
|
||||
if (fs::exists(shader_path) && !fs::is_empty(shader_path)) {
|
||||
// Compile Fragment Shader
|
||||
const auto frag_hash_hex = convert_string_to_hex(hash.frag);
|
||||
const auto frag_hash_hex = convert_hash_to_hex(hash.frag);
|
||||
const SharedGLObject frag_shader = compile_shader(base_path, title_id, self_name, renderer.shader_version,
|
||||
frag_hash_hex.c_str(), "frag", GL_FRAGMENT_SHADER, renderer.fragment_shader_cache, hash.frag);
|
||||
if (!frag_shader) {
|
||||
@@ -229,7 +199,7 @@ void pre_compile_program(GLState &renderer, const char *base_path, const char *t
|
||||
}
|
||||
|
||||
// Compile Vertex Shader
|
||||
const auto vert_hash_hex = convert_string_to_hex(hash.vert);
|
||||
const auto vert_hash_hex = convert_hash_to_hex(hash.vert);
|
||||
const SharedGLObject vert_shader = compile_shader(base_path, title_id, self_name, renderer.shader_version,
|
||||
vert_hash_hex.c_str(), "vert", GL_VERTEX_SHADER, renderer.vertex_shader_cache, hash.vert);
|
||||
if (!vert_shader) {
|
||||
@@ -244,7 +214,7 @@ void pre_compile_program(GLState &renderer, const char *base_path, const char *t
|
||||
}
|
||||
}
|
||||
|
||||
static SharedGLObject get_or_compile_shader(const SceGxmProgram *program, const FeatureState &features, const std::string &hash,
|
||||
static SharedGLObject get_or_compile_shader(const SceGxmProgram *program, const FeatureState &features, const Sha256Hash &hash,
|
||||
ShaderCache &cache, const GLenum type, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool shader_cache, bool spirv, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, uint32_t &shaders_count_compiled) {
|
||||
const auto cached = cache.find(hash);
|
||||
if (cached == cache.end()) {
|
||||
@@ -252,7 +222,7 @@ static SharedGLObject get_or_compile_shader(const SceGxmProgram *program, const
|
||||
|
||||
// Need to compile new one and add it to cache
|
||||
if (features.spirv_shader && spirv) {
|
||||
obj = compile_spirv(type, load_spirv_shader(*program, features, hint_attributes, maskupdate, base_path, title_id, self_name));
|
||||
obj = compile_spirv(type, load_spirv_shader(*program, features, false, hint_attributes, maskupdate, base_path, title_id, self_name, shader_version + "spv", shader_cache));
|
||||
} else {
|
||||
obj = compile_glsl(type, load_glsl_shader(*program, features, hint_attributes, maskupdate, base_path, title_id, self_name, shader_version, shader_cache));
|
||||
}
|
||||
@@ -297,7 +267,7 @@ SharedGLObject compile_program(GLState &renderer, const GxmRecordState &state, c
|
||||
GL_FRAGMENT_SHADER, nullptr, shader_cache, spirv, maskupdate, base_path, title_id, self_name, renderer.shader_version, renderer.shaders_count_compiled);
|
||||
|
||||
if (!fragment_shader) {
|
||||
LOG_CRITICAL("Error in get/compile fragment vertex shader:\n{}", vertex_program.hash);
|
||||
LOG_CRITICAL("Error in get/compile fragment vertex shader:\n{}", hex_string(vertex_program.hash));
|
||||
return SharedGLObject();
|
||||
}
|
||||
|
||||
@@ -305,19 +275,17 @@ SharedGLObject compile_program(GLState &renderer, const GxmRecordState &state, c
|
||||
GL_VERTEX_SHADER, &vertex_program_gxm.attributes, shader_cache, spirv, maskupdate, base_path, title_id, self_name, renderer.shader_version, renderer.shaders_count_compiled);
|
||||
|
||||
if (!vertex_shader) {
|
||||
LOG_CRITICAL("Error in get/compiled vertex shader:\n{}", vertex_program.hash);
|
||||
LOG_CRITICAL("Error in get/compiled vertex shader:\n{}", hex_string(vertex_program.hash));
|
||||
return SharedGLObject();
|
||||
}
|
||||
|
||||
const SharedGLObject program = compile_program(renderer.program_cache, fragment_shader, vertex_shader, hashes);
|
||||
|
||||
// Save shader cache haches
|
||||
if (!spirv) {
|
||||
const auto shader_cache_hash_index = get_shaders_hash_index(renderer.shaders_cache_hashs, fragment_program.hash, vertex_program.hash);
|
||||
if (shader_cache_hash_index == renderer.shaders_cache_hashs.end()) {
|
||||
renderer.shaders_cache_hashs.push_back({ fragment_program.hash, vertex_program.hash });
|
||||
save_shaders_cache_hashs(renderer.shaders_cache_hashs, base_path, title_id, self_name);
|
||||
}
|
||||
const auto shader_cache_hash_index = get_shaders_hash_index(renderer.shaders_cache_hashs, fragment_program.hash, vertex_program.hash);
|
||||
if (shader_cache_hash_index == renderer.shaders_cache_hashs.end()) {
|
||||
renderer.shaders_cache_hashs.push_back({ fragment_program.hash, vertex_program.hash });
|
||||
save_shaders_cache_hashs(renderer, renderer.shaders_cache_hashs);
|
||||
}
|
||||
|
||||
return program;
|
||||
|
||||
@@ -120,22 +120,24 @@ void draw(GLState &renderer, GLContext &context, const FeatureState &features, S
|
||||
}
|
||||
glBindImageTexture(shader::MASK_TEXTURE_SLOT_IMAGE, context.render_target->masktexture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
|
||||
|
||||
GXMRenderVertUniformBlock &vert_ublock = context.current_vert_render_info;
|
||||
std::memcpy(vert_ublock.viewport_flip, context.viewport_flip, sizeof(context.viewport_flip));
|
||||
shader::RenderVertUniformBlock &vert_ublock = context.current_vert_render_info;
|
||||
vert_ublock.viewport_flip = context.record.viewport_flip;
|
||||
vert_ublock.viewport_flag = (context.record.viewport_flat) ? 0.0f : 1.0f;
|
||||
vert_ublock.z_offset = context.record.z_offset;
|
||||
vert_ublock.z_scale = context.record.z_scale;
|
||||
vert_ublock.screen_width = static_cast<float>(context.record.color_surface.width);
|
||||
vert_ublock.screen_height = static_cast<float>(context.record.color_surface.height);
|
||||
|
||||
if (memcmp(&context.previous_vert_info, &vert_ublock, sizeof(GXMRenderVertUniformBlock)) != 0) {
|
||||
std::pair<std::uint8_t *, std::size_t> allocated_buffer = context.vertex_info_uniform_buffer.allocate(sizeof(GXMRenderVertUniformBlock));
|
||||
std::memcpy(allocated_buffer.first, &vert_ublock, sizeof(GXMRenderVertUniformBlock));
|
||||
if (memcmp(&context.previous_vert_info, &vert_ublock, sizeof(shader::RenderVertUniformBlock)) != 0) {
|
||||
std::pair<std::uint8_t *, std::size_t> allocated_buffer = context.vertex_info_uniform_buffer.allocate(sizeof(shader::RenderVertUniformBlock));
|
||||
std::memcpy(allocated_buffer.first, &vert_ublock, sizeof(shader::RenderVertUniformBlock));
|
||||
|
||||
context.previous_vert_info = vert_ublock;
|
||||
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, 2, context.vertex_info_uniform_buffer.handle(), allocated_buffer.second, sizeof(GXMRenderVertUniformBlock));
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, 2, context.vertex_info_uniform_buffer.handle(), allocated_buffer.second, sizeof(shader::RenderVertUniformBlock));
|
||||
}
|
||||
|
||||
GXMRenderFragUniformBlock &frag_ublock = context.current_frag_render_info;
|
||||
shader::RenderFragUniformBlock &frag_ublock = context.current_frag_render_info;
|
||||
const bool both_side_fragment_program_disabled = (context.record.front_side_fragment_program_mode == SCE_GXM_FRAGMENT_PROGRAM_DISABLED)
|
||||
&& ((context.record.back_side_fragment_program_mode == SCE_GXM_FRAGMENT_PROGRAM_DISABLED) || (context.record.two_sided == SCE_GXM_TWO_SIDED_DISABLED));
|
||||
if (both_side_fragment_program_disabled) {
|
||||
@@ -161,13 +163,13 @@ void draw(GLState &renderer, GLContext &context, const FeatureState &features, S
|
||||
frag_ublock.use_raw_image = static_cast<float>(use_raw_image);
|
||||
frag_ublock.res_multiplier = renderer.res_multiplier;
|
||||
|
||||
if (memcmp(&context.previous_frag_info, &frag_ublock, sizeof(GXMRenderFragUniformBlock)) != 0) {
|
||||
std::pair<std::uint8_t *, std::size_t> allocated_buffer = context.fragment_info_uniform_buffer.allocate(sizeof(GXMRenderFragUniformBlock));
|
||||
std::memcpy(allocated_buffer.first, &frag_ublock, sizeof(GXMRenderFragUniformBlock));
|
||||
if (memcmp(&context.previous_frag_info, &frag_ublock, sizeof(shader::RenderFragUniformBlock)) != 0) {
|
||||
std::pair<std::uint8_t *, std::size_t> allocated_buffer = context.fragment_info_uniform_buffer.allocate(sizeof(shader::RenderFragUniformBlock));
|
||||
std::memcpy(allocated_buffer.first, &frag_ublock, sizeof(shader::RenderFragUniformBlock));
|
||||
|
||||
context.previous_frag_info = frag_ublock;
|
||||
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, 3, context.fragment_info_uniform_buffer.handle(), allocated_buffer.second, sizeof(GXMRenderFragUniformBlock));
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, 3, context.fragment_info_uniform_buffer.handle(), allocated_buffer.second, sizeof(shader::RenderFragUniformBlock));
|
||||
}
|
||||
|
||||
context.vertex_set_requests.clear();
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/functions.h>
|
||||
#include <renderer/profile.h>
|
||||
|
||||
#include <renderer/gl/functions.h>
|
||||
|
||||
#include <gxm/types.h>
|
||||
#include <renderer/types.h>
|
||||
#include <shader/spirv_recompiler.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/log.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace renderer::gl {
|
||||
|
||||
bool get_shaders_cache_hashs(GLState &renderer, const char *base_path, const char *title_id, const char *self_name) {
|
||||
const auto shaders_path{ fs::path(base_path) / "cache/shaders" / title_id / self_name };
|
||||
fs::ifstream shaders_hashs(shaders_path / "hashs.dat", std::ios::in | std::ios::binary);
|
||||
if (shaders_hashs.is_open()) {
|
||||
renderer.shaders_cache_hashs.clear();
|
||||
// Read size of hashes list
|
||||
size_t size;
|
||||
shaders_hashs.read((char *)&size, sizeof(size));
|
||||
|
||||
// Check version of cache
|
||||
uint32_t versionInFile;
|
||||
shaders_hashs.read((char *)&versionInFile, sizeof(uint32_t));
|
||||
if (versionInFile != shader::CURRENT_VERSION) {
|
||||
shaders_hashs.close();
|
||||
fs::remove_all(shaders_path);
|
||||
fs::remove_all(fs::path(base_path) / "shaderlog" / title_id / self_name);
|
||||
LOG_WARN("Current version of cache: {}, is outdated, recreate it.", versionInFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read Hashs info value
|
||||
for (size_t a = 0; a < size; a++) {
|
||||
auto read = [&shaders_hashs]() {
|
||||
size_t size;
|
||||
|
||||
shaders_hashs.read((char *)&size, sizeof(size));
|
||||
|
||||
std::vector<char> buffer(size);
|
||||
shaders_hashs.read(buffer.data(), size);
|
||||
|
||||
return std::string(buffer.begin(), buffer.end());
|
||||
};
|
||||
|
||||
ShadersHash hash;
|
||||
hash.frag = read();
|
||||
hash.vert = read();
|
||||
|
||||
renderer.shaders_cache_hashs.push_back({ hash.frag, hash.vert });
|
||||
}
|
||||
|
||||
shaders_hashs.close();
|
||||
}
|
||||
|
||||
return !renderer.shaders_cache_hashs.empty();
|
||||
}
|
||||
|
||||
static bool load_shader(const char *hash, const char *extension, const char *base_path, const char *title_id, const char *self_name, char **destination, std::size_t &size_read) {
|
||||
const auto shader_path = fs_utils::construct_file_name(base_path, (fs::path("cache/shaders") / title_id / self_name).string().c_str(), hash, extension);
|
||||
fs::ifstream is(shader_path, fs::ifstream::binary);
|
||||
if (!is) {
|
||||
return false;
|
||||
}
|
||||
|
||||
is.seekg(0, fs::ifstream::end);
|
||||
size_read = is.tellg();
|
||||
is.seekg(0);
|
||||
|
||||
if (size_read == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (destination == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
is.read(*destination, size_read);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const Sha256HashText get_shader_hash(const SceGxmProgram &program) {
|
||||
const Sha256Hash hash_bytes = sha256(&program, program.size);
|
||||
return hex(hash_bytes);
|
||||
}
|
||||
|
||||
template <typename R>
|
||||
R load_shader_generic(const char *hash_text, const char *base_path, const char *title_id, const char *self_name, const char *shader_type_str) {
|
||||
std::size_t read_size = 0;
|
||||
R source;
|
||||
|
||||
if (load_shader(hash_text, shader_type_str, base_path, title_id, self_name, nullptr, read_size)) {
|
||||
source.resize((read_size + sizeof(typename R::value_type) - 1) / sizeof(typename R::value_type));
|
||||
|
||||
char *dest_pointer = reinterpret_cast<char *>(source.data());
|
||||
load_shader(hash_text, shader_type_str, base_path, title_id, self_name, &dest_pointer, read_size);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
template <typename R, typename F>
|
||||
R load_shader_generic(F genfunc, const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const char *shader_type_str, const std::string &shader_version, bool shader_cache) {
|
||||
const Sha256HashText hash_text = get_shader_hash(program);
|
||||
// Set Shader Hash with Version
|
||||
const std::string hash_hex_ver = shader_version + "-" + static_cast<std::string>(hash_text.data());
|
||||
|
||||
// Todo, spirv shader no can load any shader, is totaly broken
|
||||
R source = shader_cache ? load_shader_generic<R>(hash_hex_ver.c_str(), base_path, title_id, self_name, shader_type_str) : R{};
|
||||
|
||||
if (source.empty()) {
|
||||
LOG_INFO("Generating {} shader {}", shader_type_str, hash_text.data());
|
||||
|
||||
std::string spirv_dump;
|
||||
std::string disasm_dump;
|
||||
|
||||
const fs::path shader_base_dir{ fs::path("shaderlog") / title_id / self_name };
|
||||
if (!fs::exists(base_path / shader_base_dir))
|
||||
fs::create_directories(base_path / shader_base_dir);
|
||||
|
||||
auto shader_base_path = fs_utils::construct_file_name(base_path, shader_base_dir, hash_hex_ver.c_str(), ".gxp");
|
||||
|
||||
// Dump gxp binary
|
||||
fs::ofstream of{ shader_base_path, fs::ofstream::binary };
|
||||
if (!of.fail()) {
|
||||
of.write(reinterpret_cast<const char *>(&program), program.size);
|
||||
of.close();
|
||||
}
|
||||
|
||||
const auto write_data_with_ext = [&](const std::string &ext, const std::string &data) {
|
||||
fs::path out_path{ shader_base_path };
|
||||
out_path.replace_extension(ext);
|
||||
fs::ofstream of{ out_path };
|
||||
if (!of.fail()) {
|
||||
of << data;
|
||||
of.close();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
source = genfunc(program, hash_text.data(), features, hint_attributes, maskupdate, false, write_data_with_ext);
|
||||
|
||||
// Copy shader generate to shaders cache
|
||||
shader_base_path.replace_extension(shader_type_str);
|
||||
if (fs::exists(shader_base_path)) {
|
||||
try {
|
||||
const auto shaders_cache_path = fs::path("cache/shaders") / title_id / self_name;
|
||||
if (!fs::exists(base_path / shaders_cache_path))
|
||||
fs::create_directories(base_path / shaders_cache_path);
|
||||
|
||||
const auto shader_dst_path = fs_utils::construct_file_name(base_path, shaders_cache_path, hash_hex_ver.c_str(), shader_type_str);
|
||||
fs::copy_file(shader_base_path, shader_dst_path, fs::copy_option::overwrite_if_exists);
|
||||
fs::remove(shader_base_path);
|
||||
} catch (std::exception &e) {
|
||||
LOG_ERROR("Failed to moved shaders file: \n{}", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string load_glsl_shader(const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, bool shader_cache) {
|
||||
SceGxmProgramType program_type = program.get_type();
|
||||
|
||||
auto shader_type_to_str = [](SceGxmProgramType type) {
|
||||
return (type == SceGxmProgramType::Vertex) ? "vert" : ((type == SceGxmProgramType::Fragment) ? "frag" : "unknown");
|
||||
};
|
||||
|
||||
const char *shader_type_str = shader_type_to_str(program_type);
|
||||
return load_shader_generic<std::string>(shader::convert_gxp_to_glsl, program, features, hint_attributes, maskupdate, base_path, title_id, self_name, shader_type_str, shader_version, shader_cache);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> load_spirv_shader(const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name) {
|
||||
return load_shader_generic<std::vector<std::uint32_t>>(shader::convert_gxp_to_spirv, program, features, hint_attributes, maskupdate, base_path, title_id, self_name, "spv", "v0", false);
|
||||
}
|
||||
|
||||
std::string pre_load_glsl_shader(const char *hash_text, const char *shader_type_str, const char *base_path, const char *title_id, const char *self_name) {
|
||||
return load_shader_generic<std::string>(hash_text, base_path, title_id, self_name, shader_type_str);
|
||||
}
|
||||
|
||||
} // namespace renderer::gl
|
||||
@@ -49,8 +49,8 @@ GLContext::GLContext()
|
||||
, fragment_uniform_stream_ring_buffer(GL_SHADER_STORAGE_BUFFER, MiB(256))
|
||||
, vertex_info_uniform_buffer(GL_UNIFORM_BUFFER, MiB(8))
|
||||
, fragment_info_uniform_buffer(GL_UNIFORM_BUFFER, MiB(8)) {
|
||||
std::memset(&previous_vert_info, 0, sizeof(GXMRenderVertUniformBlock));
|
||||
std::memset(&previous_frag_info, 0, sizeof(GXMRenderFragUniformBlock));
|
||||
std::memset(&previous_vert_info, 0, sizeof(shader::RenderVertUniformBlock));
|
||||
std::memset(&previous_frag_info, 0, sizeof(shader::RenderFragUniformBlock));
|
||||
}
|
||||
|
||||
namespace texture {
|
||||
@@ -62,13 +62,11 @@ bool init(GLTextureCacheState &cache, const bool hashless_texture_cache) {
|
||||
glBindTexture(get_gl_texture_type(*texture_casted), gl_texture);
|
||||
};
|
||||
|
||||
cache.configure_texture_callback = [](const renderer::TextureCacheState &text_cache, const std::size_t index, const void *texture) {
|
||||
cache.configure_texture_callback = [](const renderer::TextureCacheState &text_cache, const void *texture) {
|
||||
configure_bound_texture(text_cache, *reinterpret_cast<const SceGxmTexture *>(texture));
|
||||
};
|
||||
|
||||
cache.upload_texture_callback = [](const std::size_t index, const void *texture, const MemState &mem) {
|
||||
upload_bound_texture(*reinterpret_cast<const SceGxmTexture *>(texture), mem);
|
||||
};
|
||||
cache.upload_texture_callback = upload_bound_texture;
|
||||
|
||||
cache.use_protect = hashless_texture_cache;
|
||||
|
||||
@@ -280,10 +278,14 @@ bool create(SDL_Window *window, std::unique_ptr<State> &state, const char *base_
|
||||
LOG_WARN("Consider updating your graphics drivers or upgrading your GPU.");
|
||||
}
|
||||
|
||||
// always enabled in the opengl renderer
|
||||
gl_state.features.use_mask_bit = true;
|
||||
|
||||
return gl_state.init(base_path, hashless_texture_cache);
|
||||
}
|
||||
|
||||
bool GLState::init(const char *base_path, const bool hashless_texture_cache) {
|
||||
texture_cache.backend = ¤t_backend;
|
||||
if (!texture::init(texture_cache, hashless_texture_cache)) {
|
||||
LOG_ERROR("Failed to initialize texture cache!");
|
||||
return false;
|
||||
@@ -353,34 +355,12 @@ bool create(GLState &state, std::unique_ptr<RenderTarget> &rt, const SceGxmRende
|
||||
return true;
|
||||
}
|
||||
|
||||
static void layout_ssbo_offset_from_uniform_buffer_sizes(UniformBufferSizes &sizes, UniformBufferSizes &offsets, std::size_t &total_hold) {
|
||||
std::uint32_t last_offset = 0;
|
||||
|
||||
for (std::size_t i = 0; i < sizes.size(); i++) {
|
||||
if (sizes[i] != 0) {
|
||||
// Round to vec4 unit
|
||||
offsets[i] = last_offset;
|
||||
last_offset += ((sizes[i] + 3) / 4 * 4);
|
||||
} else {
|
||||
offsets[i] = static_cast<std::uint32_t>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
total_hold = static_cast<std::size_t>(last_offset);
|
||||
}
|
||||
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, GLState &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id) {
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, GLState &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend) {
|
||||
R_PROFILE(__func__);
|
||||
|
||||
fp = std::make_unique<GLFragmentProgram>();
|
||||
GLFragmentProgram *frag_program_gl = reinterpret_cast<GLFragmentProgram *>(fp.get());
|
||||
|
||||
// Try to hash this shader
|
||||
const Sha256Hash hash_bytes_f = sha256(&program, program.size);
|
||||
fp->hash.assign(hash_bytes_f.begin(), hash_bytes_f.end());
|
||||
|
||||
gxp_ptr_map.emplace(hash_bytes_f, &program);
|
||||
|
||||
// Translate blending.
|
||||
if (blend != nullptr) {
|
||||
frag_program_gl->color_mask_red = ((blend->colorMask & SCE_GXM_COLOR_MASK_R) != 0) ? GL_TRUE : GL_FALSE;
|
||||
@@ -395,32 +375,13 @@ bool create(std::unique_ptr<FragmentProgram> &fp, GLState &state, const SceGxmPr
|
||||
frag_program_gl->alpha_src = translate_blend_factor(blend->alphaSrc);
|
||||
frag_program_gl->alpha_dst = translate_blend_factor(blend->alphaDst);
|
||||
}
|
||||
shader::usse::get_uniform_buffer_sizes(program, fp->uniform_buffer_sizes);
|
||||
layout_ssbo_offset_from_uniform_buffer_sizes(fp->uniform_buffer_sizes, fp->uniform_buffer_data_offsets, fp->max_total_uniform_buffer_storage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, GLState &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const char *base_path, const char *title_id) {
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, GLState &state, const SceGxmProgram &program) {
|
||||
R_PROFILE(__func__);
|
||||
|
||||
vp = std::make_unique<GLVertexProgram>();
|
||||
GLVertexProgram *vert_program_gl = reinterpret_cast<GLVertexProgram *>(vp.get());
|
||||
|
||||
// Try to hash this shader
|
||||
const Sha256Hash hash_bytes_v = sha256(&program, program.size);
|
||||
vp->hash.assign(hash_bytes_v.begin(), hash_bytes_v.end());
|
||||
gxp_ptr_map.emplace(hash_bytes_v, &program);
|
||||
|
||||
shader::usse::get_uniform_buffer_sizes(program, vp->uniform_buffer_sizes);
|
||||
shader::usse::get_attribute_informations(program, vert_program_gl->attribute_infos);
|
||||
layout_ssbo_offset_from_uniform_buffer_sizes(vp->uniform_buffer_sizes, vp->uniform_buffer_data_offsets, vp->max_total_uniform_buffer_storage);
|
||||
|
||||
if (vert_program_gl->attribute_infos.empty()) {
|
||||
vert_program_gl->stripped_symbols_checked = false;
|
||||
} else {
|
||||
vert_program_gl->stripped_symbols_checked = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -448,12 +409,12 @@ void set_context(GLState &state, GLContext &context, const MemState &mem, const
|
||||
ds_surface_fin = nullptr;
|
||||
}
|
||||
|
||||
std::uint64_t current_color_attachment_handle = 0;
|
||||
GLuint current_color_attachment_handle = 0;
|
||||
std::uint16_t current_framebuffer_height = 0;
|
||||
|
||||
context.current_framebuffer = static_cast<GLuint>(state.surface_cache.retrieve_framebuffer_handle(
|
||||
state, mem, color_surface_fin, ds_surface_fin, ¤t_color_attachment_handle, nullptr, ¤t_framebuffer_height));
|
||||
context.current_color_attachment = static_cast<GLuint>(current_color_attachment_handle);
|
||||
context.current_framebuffer = state.surface_cache.retrieve_framebuffer_handle(
|
||||
state, mem, color_surface_fin, ds_surface_fin, ¤t_color_attachment_handle, nullptr, ¤t_framebuffer_height);
|
||||
context.current_color_attachment = current_color_attachment_handle;
|
||||
context.current_framebuffer_height = current_framebuffer_height;
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, context.current_framebuffer);
|
||||
@@ -473,8 +434,8 @@ void set_context(GLState &state, GLContext &context, const MemState &mem, const
|
||||
sync_depth_write_enable(context.record.back_depth_write_mode, false);
|
||||
|
||||
sync_stencil_data(context.record, mem);
|
||||
sync_stencil_func(context.record.back_stencil_state, mem, true);
|
||||
sync_stencil_func(context.record.front_stencil_state, mem, false);
|
||||
sync_stencil_func(context.record.back_stencil_state_op, context.record.back_stencil_state_values, mem, true);
|
||||
sync_stencil_func(context.record.front_stencil_state_op, context.record.front_stencil_state_values, mem, false);
|
||||
|
||||
if (context.record.region_clip_mode != SCE_GXM_REGION_CLIP_NONE) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
@@ -788,6 +749,10 @@ void GLState::render_frame(const SceFVector2 &viewport_pos, const SceFVector2 &v
|
||||
screen_renderer.render(viewport_pos, viewport_size, need_uv ? uvs : nullptr, static_cast<GLuint>(surface_handle), texture_size);
|
||||
}
|
||||
|
||||
void GLState::swap_window(SDL_Window *window) {
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
void GLState::set_fxaa(bool enable_fxaa) {
|
||||
screen_renderer.enable_fxaa = enable_fxaa;
|
||||
}
|
||||
@@ -802,4 +767,10 @@ void GLState::set_anisotropic_filtering(int anisotropic_filtering) {
|
||||
texture_cache.anisotropic_filtering = anisotropic_filtering;
|
||||
}
|
||||
|
||||
void GLState::precompile_shader(const ShadersHash &hash) {
|
||||
pre_compile_program(*this, base_path, title_id, self_name, hash);
|
||||
}
|
||||
|
||||
void GLState::preclose_action() {}
|
||||
|
||||
} // namespace renderer::gl
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace renderer::gl {
|
||||
bool ScreenRenderer::init(const std::string &base_path) {
|
||||
glGenTextures(1, &m_screen_texture);
|
||||
|
||||
const auto builtin_shaders_path = base_path + "shaders-builtin/";
|
||||
const auto builtin_shaders_path = base_path + "shaders-builtin/opengl/";
|
||||
|
||||
m_render_shader_nofilter = ::gl::load_shaders(builtin_shaders_path + "render_main.vert", builtin_shaders_path + "render_main.frag");
|
||||
m_render_shader_fxaa = ::gl::load_shaders(builtin_shaders_path + "render_main.vert", builtin_shaders_path + "render_main_fxaa.frag");
|
||||
|
||||
@@ -29,7 +29,7 @@ static constexpr std::uint64_t CASTED_UNUSED_TEXTURE_PURGE_SECS = 40;
|
||||
GLSurfaceCache::GLSurfaceCache() {
|
||||
}
|
||||
|
||||
void GLSurfaceCache::do_typeless_copy(const GLint dest_texture, const GLint source_texture, const GLenum dest_internal,
|
||||
void GLSurfaceCache::do_typeless_copy(const GLuint dest_texture, const GLuint source_texture, const GLenum dest_internal,
|
||||
const GLenum dest_upload_format, const GLenum dest_type, const GLenum source_format, const GLenum source_type, const int offset_x,
|
||||
const int offset_y, const int width, const int height, const int dest_width, const int dest_height, const std::size_t total_source_size) {
|
||||
static constexpr GLsizei I32_SIGNED_MAX = 0x7FFFFFFF;
|
||||
@@ -58,7 +58,7 @@ void GLSurfaceCache::do_typeless_copy(const GLint dest_texture, const GLint sour
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, GL_NONE);
|
||||
}
|
||||
|
||||
std::uint64_t GLSurfaceCache::retrieve_color_surface_texture_handle(const State &state, std::uint16_t width, std::uint16_t height, const std::uint16_t pixel_stride,
|
||||
GLuint GLSurfaceCache::retrieve_color_surface_texture_handle(const State &state, std::uint16_t width, std::uint16_t height, const std::uint16_t pixel_stride,
|
||||
const SceGxmColorBaseFormat base_format, Ptr<void> address, SurfaceTextureRetrievePurpose purpose, std::uint32_t &swizzle,
|
||||
std::uint16_t *stored_height, std::uint16_t *stored_width) {
|
||||
// Create the key to access the cache struct
|
||||
@@ -454,7 +454,7 @@ std::uint64_t GLSurfaceCache::retrieve_color_surface_texture_handle(const State
|
||||
return texture_handle_return;
|
||||
}
|
||||
|
||||
std::uint64_t GLSurfaceCache::retrieve_ping_pong_color_surface_texture_handle(Ptr<void> address) {
|
||||
GLuint GLSurfaceCache::retrieve_ping_pong_color_surface_texture_handle(Ptr<void> address) {
|
||||
auto ite = color_surface_textures.find(address.address());
|
||||
if (ite == color_surface_textures.end()) {
|
||||
return 0;
|
||||
@@ -484,7 +484,7 @@ std::uint64_t GLSurfaceCache::retrieve_ping_pong_color_surface_texture_handle(Pt
|
||||
return info.gl_ping_pong_texture[0];
|
||||
}
|
||||
|
||||
std::uint64_t GLSurfaceCache::retrieve_depth_stencil_texture_handle(const State &state, const MemState &mem, const SceGxmDepthStencilSurface &surface, std::int32_t force_width, std::int32_t force_height, const bool is_reading) {
|
||||
GLuint GLSurfaceCache::retrieve_depth_stencil_texture_handle(const State &state, const MemState &mem, const SceGxmDepthStencilSurface &surface, std::int32_t force_width, std::int32_t force_height, const bool is_reading) {
|
||||
if (!target) {
|
||||
LOG_ERROR("Unable to retrieve Depth Stencil texture with no active render target!");
|
||||
return 0;
|
||||
@@ -493,13 +493,7 @@ std::uint64_t GLSurfaceCache::retrieve_depth_stencil_texture_handle(const State
|
||||
force_width *= state.res_multiplier;
|
||||
force_height *= state.res_multiplier;
|
||||
|
||||
bool packed_ds = false;
|
||||
if (surface.control) {
|
||||
const SceGxmDepthStencilControl *control = surface.control.get(mem);
|
||||
if (control && (control->format == SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24)) {
|
||||
packed_ds = true;
|
||||
}
|
||||
}
|
||||
bool packed_ds = (surface.control.content & SceGxmDepthStencilControl::format_bits) == SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24;
|
||||
|
||||
if (force_width < 0) {
|
||||
force_width = target->width;
|
||||
@@ -605,8 +599,8 @@ std::uint64_t GLSurfaceCache::retrieve_depth_stencil_texture_handle(const State
|
||||
return depth_stencil_textures[found_index].gl_texture[0];
|
||||
}
|
||||
|
||||
std::uint64_t GLSurfaceCache::retrieve_framebuffer_handle(const State &state, const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
|
||||
std::uint64_t *color_texture_handle, std::uint64_t *ds_texture_handle, std::uint16_t *stored_height) {
|
||||
GLuint GLSurfaceCache::retrieve_framebuffer_handle(const State &state, const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
|
||||
GLuint *color_texture_handle, GLuint *ds_texture_handle, std::uint16_t *stored_height) {
|
||||
if (!target) {
|
||||
LOG_ERROR("Unable to retrieve framebuffer with no active render target!");
|
||||
return 0;
|
||||
@@ -689,7 +683,7 @@ std::uint64_t GLSurfaceCache::retrieve_framebuffer_handle(const State &state, co
|
||||
return fb[0];
|
||||
}
|
||||
|
||||
std::uint64_t GLSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size) {
|
||||
GLuint GLSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size) {
|
||||
auto ite = color_surface_textures.lower_bound(address.address());
|
||||
if (ite == color_surface_textures.end()) {
|
||||
return 0;
|
||||
|
||||
@@ -109,17 +109,12 @@ static GLenum translate_stencil_func(SceGxmStencilFunc stencil_func) {
|
||||
}
|
||||
|
||||
void sync_mask(const GLState &state, GLContext &context, const MemState &mem) {
|
||||
auto control = context.record.depth_stencil_surface.control.get(mem);
|
||||
auto control = context.record.depth_stencil_surface.control.content;
|
||||
// mask is not upscaled
|
||||
auto width = context.render_target->width / state.res_multiplier;
|
||||
auto height = context.render_target->height / state.res_multiplier;
|
||||
GLubyte initial_byte;
|
||||
if (control) {
|
||||
initial_byte = control->backgroundMask ? 0xFF : 0;
|
||||
} else {
|
||||
// always accept
|
||||
initial_byte = 0xFF;
|
||||
}
|
||||
GLubyte initial_byte = (control & SceGxmDepthStencilControl::mask_bit) ? 0xFF : 0;
|
||||
|
||||
GLubyte clear_bytes[4] = { initial_byte, initial_byte, initial_byte, initial_byte };
|
||||
glClearTexImage(context.render_target->masktexture[0], 0, GL_RGBA, GL_UNSIGNED_BYTE, clear_bytes);
|
||||
}
|
||||
@@ -127,23 +122,9 @@ void sync_mask(const GLState &state, GLContext &context, const MemState &mem) {
|
||||
void sync_viewport_flat(const GLState &state, GLContext &context) {
|
||||
const GLsizei display_w = context.record.color_surface.width;
|
||||
const GLsizei display_h = context.record.color_surface.height;
|
||||
const float previous_flip_y = context.viewport_flip[1];
|
||||
|
||||
context.viewport_flip[0] = 1.0f;
|
||||
context.viewport_flip[1] = -1.0f;
|
||||
context.viewport_flip[2] = 1.0f;
|
||||
context.viewport_flip[3] = 1.0f;
|
||||
|
||||
context.record.viewport_flat = true;
|
||||
|
||||
glViewport(0, (context.current_framebuffer_height - display_h) * state.res_multiplier, display_w * state.res_multiplier, display_h * state.res_multiplier);
|
||||
glDepthRange(0, 1);
|
||||
|
||||
if (previous_flip_y != context.viewport_flip[1]) {
|
||||
// We need to sync again state that uses the flip
|
||||
sync_cull(context.record);
|
||||
sync_clipping(state, context);
|
||||
}
|
||||
}
|
||||
|
||||
void sync_viewport_real(const GLState &state, GLContext &context, const float xOffset, const float yOffset, const float zOffset,
|
||||
@@ -156,23 +137,8 @@ void sync_viewport_real(const GLState &state, GLContext &context, const float xO
|
||||
const GLfloat x = xOffset - std::abs(xScale);
|
||||
const GLfloat y = std::min<GLfloat>(ymin, ymax);
|
||||
|
||||
const float previous_flip_y = context.viewport_flip[1];
|
||||
|
||||
context.viewport_flip[0] = 1.0f;
|
||||
context.viewport_flip[1] = (ymin < ymax) ? -1.0f : 1.0f;
|
||||
context.viewport_flip[2] = 1.0f;
|
||||
context.viewport_flip[3] = 1.0f;
|
||||
|
||||
context.record.viewport_flat = false;
|
||||
|
||||
glViewportIndexedf(0, x * state.res_multiplier, y * state.res_multiplier, w * state.res_multiplier, h * state.res_multiplier);
|
||||
glDepthRange(zOffset - zScale, zOffset + zScale);
|
||||
|
||||
if (previous_flip_y != context.viewport_flip[1]) {
|
||||
// We need to sync again state that uses the flip
|
||||
sync_cull(context.record);
|
||||
sync_clipping(state, context);
|
||||
}
|
||||
glDepthRange(0, 1);
|
||||
}
|
||||
|
||||
void sync_clipping(const GLState &state, GLContext &context) {
|
||||
@@ -180,7 +146,7 @@ void sync_clipping(const GLState &state, GLContext &context) {
|
||||
const GLsizei scissor_x = context.record.region_clip_min.x;
|
||||
GLsizei scissor_y = 0;
|
||||
|
||||
if (context.viewport_flip[1] == -1.0f)
|
||||
if (context.record.viewport_flip[1] == -1.0f)
|
||||
scissor_y = context.record.region_clip_min.y;
|
||||
else
|
||||
scissor_y = display_h - context.record.region_clip_max.y - 1;
|
||||
@@ -247,23 +213,23 @@ void sync_depth_data(const renderer::GxmRecordState &state) {
|
||||
}
|
||||
}
|
||||
|
||||
void sync_stencil_func(const GxmStencilState &state, const MemState &mem, const bool is_back_stencil) {
|
||||
void sync_stencil_func(const GxmStencilStateOp &state_op, const GxmStencilStateValues &state_vals, const MemState &mem, const bool is_back_stencil) {
|
||||
const GLenum face = is_back_stencil ? GL_BACK : GL_FRONT;
|
||||
|
||||
glStencilOpSeparate(face,
|
||||
translate_stencil_op(state.stencil_fail),
|
||||
translate_stencil_op(state.depth_fail),
|
||||
translate_stencil_op(state.depth_pass));
|
||||
glStencilFuncSeparate(face, translate_stencil_func(state.func), state.ref, state.compare_mask);
|
||||
glStencilMaskSeparate(face, state.write_mask);
|
||||
translate_stencil_op(state_op.stencil_fail),
|
||||
translate_stencil_op(state_op.depth_fail),
|
||||
translate_stencil_op(state_op.depth_pass));
|
||||
glStencilFuncSeparate(face, translate_stencil_func(state_op.func), state_vals.ref, state_vals.compare_mask);
|
||||
glStencilMaskSeparate(face, state_vals.write_mask);
|
||||
}
|
||||
|
||||
void sync_stencil_data(const GxmRecordState &state, const MemState &mem) {
|
||||
// Stencil test.
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilMask(GL_TRUE);
|
||||
if (((state.depth_stencil_surface.zlsControl & SCE_GXM_DEPTH_STENCIL_FORCE_LOAD_ENABLED) == 0) && state.depth_stencil_surface.control) {
|
||||
glClearStencil(state.depth_stencil_surface.control.get(mem)->backgroundStencil);
|
||||
if ((state.depth_stencil_surface.zlsControl & SCE_GXM_DEPTH_STENCIL_FORCE_LOAD_ENABLED) == 0) {
|
||||
glClearStencil(state.depth_stencil_surface.control.content & SceGxmDepthStencilControl::stencil_bits);
|
||||
glClear(GL_STENCIL_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
@@ -305,22 +271,6 @@ void sync_depth_bias(const int factor, const int unit, const bool is_front) {
|
||||
}
|
||||
}
|
||||
|
||||
static float get_integral_query_format(const SceGxmTextureBaseFormat format) {
|
||||
if ((format == SCE_GXM_TEXTURE_BASE_FORMAT_S8) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S8S8) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8S8)) {
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_8BIT_SIGNED;
|
||||
}
|
||||
|
||||
if ((format == SCE_GXM_TEXTURE_BASE_FORMAT_U16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_U16U16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_U16U16U16U16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S16S16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S16S16S16S16)) {
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_16BIT;
|
||||
}
|
||||
|
||||
if ((format == SCE_GXM_TEXTURE_BASE_FORMAT_U32) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_U32U32) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S32)) {
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_32BIT;
|
||||
}
|
||||
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_8BIT_UNSIGNED;
|
||||
}
|
||||
|
||||
void sync_texture(GLState &state, GLContext &context, MemState &mem, std::size_t index, SceGxmTexture texture,
|
||||
const Config &config, const std::string &base_path, const std::string &title_id) {
|
||||
Address data_addr = texture.data_addr << 2;
|
||||
@@ -340,9 +290,9 @@ void sync_texture(GLState &state, GLContext &context, MemState &mem, std::size_t
|
||||
|
||||
if (index >= SCE_GXM_MAX_TEXTURE_UNITS) {
|
||||
// Vertex textures
|
||||
context.current_vert_render_info.integral_texture_query_format[index - SCE_GXM_MAX_TEXTURE_UNITS] = get_integral_query_format(base_format);
|
||||
context.current_vert_render_info.integral_texture_query_format[index - SCE_GXM_MAX_TEXTURE_UNITS] = renderer::texture::get_integral_query_format(base_format);
|
||||
} else {
|
||||
context.current_frag_render_info.integral_texture_query_format[index] = get_integral_query_format(base_format);
|
||||
context.current_frag_render_info.integral_texture_query_format[index] = renderer::texture::get_integral_query_format(base_format);
|
||||
}
|
||||
|
||||
glActiveTexture(static_cast<GLenum>(static_cast<std::size_t>(GL_TEXTURE0) + index));
|
||||
@@ -355,10 +305,9 @@ void sync_texture(GLState &state, GLContext &context, MemState &mem, std::size_t
|
||||
texture_as_surface = context.current_color_attachment;
|
||||
swizzle_surface = color::translate_swizzle(context.record.color_surface.colorFormat);
|
||||
|
||||
if (std::find(context.self_sampling_indices.begin(), context.self_sampling_indices.end(),
|
||||
static_cast<GLuint>(index))
|
||||
if (std::find(context.self_sampling_indices.begin(), context.self_sampling_indices.end(), index)
|
||||
== context.self_sampling_indices.end()) {
|
||||
context.self_sampling_indices.push_back(static_cast<GLuint>(index));
|
||||
context.self_sampling_indices.push_back(index);
|
||||
}
|
||||
} else {
|
||||
auto res = std::find(context.self_sampling_indices.begin(), context.self_sampling_indices.end(),
|
||||
@@ -372,7 +321,7 @@ void sync_texture(GLState &state, GLContext &context, MemState &mem, std::size_t
|
||||
std::uint16_t width = static_cast<std::uint16_t>(gxm::get_width(&texture));
|
||||
std::uint16_t height = static_cast<std::uint16_t>(gxm::get_height(&texture));
|
||||
|
||||
if (color::convert_base_texture_format_to_base_color_format(base_format, format_target_of_texture)) {
|
||||
if (renderer::texture::convert_base_texture_format_to_base_color_format(base_format, format_target_of_texture)) {
|
||||
std::uint16_t stride_in_pixels = width;
|
||||
|
||||
if (texture.texture_type() == SCE_GXM_TEXTURE_LINEAR_STRIDED) {
|
||||
@@ -500,7 +449,7 @@ void sync_vertex_streams_and_attributes(GLContext &context, GxmRecordState &stat
|
||||
const SceGxmProgram *vertex_program_body = vertex_program.program.get(mem);
|
||||
if (vertex_program_body && (vertex_program_body->primary_reg_count != 0)) {
|
||||
for (std::size_t i = 0; i < vertex_program.attributes.size(); i++) {
|
||||
glvert->attribute_infos.emplace(vertex_program.attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<std::uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false));
|
||||
glvert->attribute_infos.emplace(vertex_program.attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<std::uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,30 +42,7 @@ void bind_texture(GLTextureCacheState &cache, const SceGxmTexture &gxm_texture,
|
||||
R_PROFILE(__func__);
|
||||
glBindTexture(get_gl_texture_type(gxm_texture), cache.textures[0]);
|
||||
configure_bound_texture(cache, gxm_texture);
|
||||
upload_bound_texture(gxm_texture, mem);
|
||||
}
|
||||
|
||||
static bool can_texture_be_unswizzled_without_decode(SceGxmTextureBaseFormat fmt) {
|
||||
return (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_P4
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U4U4U4U4
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_P8
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U8
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U5U6U5
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U1U5U5U5
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8);
|
||||
}
|
||||
|
||||
static bool is_block_compressed_format(SceGxmTextureBaseFormat fmt) {
|
||||
return (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC1
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC2
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC3
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC4
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC5
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT4BPP
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP);
|
||||
renderer::texture::upload_bound_texture(cache, gxm_texture, mem);
|
||||
}
|
||||
|
||||
void configure_bound_texture(const renderer::TextureCacheState &state, const SceGxmTexture &gxm_texture) {
|
||||
@@ -75,8 +52,15 @@ void configure_bound_texture(const renderer::TextureCacheState &state, const Sce
|
||||
const SceGxmTextureBaseFormat base_format = gxm::get_base_format(fmt);
|
||||
const SceGxmTextureAddrMode uaddr = (SceGxmTextureAddrMode)(gxm_texture.uaddr_mode);
|
||||
const SceGxmTextureAddrMode vaddr = (SceGxmTextureAddrMode)(gxm_texture.vaddr_mode);
|
||||
auto width = static_cast<uint32_t>(gxm::get_width(&gxm_texture));
|
||||
auto height = static_cast<uint32_t>(gxm::get_height(&gxm_texture));
|
||||
if (gxm::is_block_compressed_format(base_format)) {
|
||||
// align width and height to block size
|
||||
width = (width + 3) & ~3;
|
||||
height = (height + 3) & ~3;
|
||||
}
|
||||
const GLint *const swizzle = translate_swizzle(fmt);
|
||||
uint32_t mip_count = gxm_texture.true_mip_count();
|
||||
uint32_t mip_count = renderer::texture::get_upload_mip(gxm_texture.true_mip_count(), width, height, base_format);
|
||||
|
||||
const GLenum texture_bind_type = get_gl_texture_type(gxm_texture);
|
||||
|
||||
@@ -98,8 +82,6 @@ void configure_bound_texture(const renderer::TextureCacheState &state, const Sce
|
||||
glTexParameterf(texture_bind_type, GL_TEXTURE_MAX_ANISOTROPY_EXT, static_cast<float>(state.anisotropic_filtering));
|
||||
|
||||
const GLenum internal_format = translate_internal_format(base_format);
|
||||
auto width = static_cast<uint32_t>(gxm::get_width(&gxm_texture));
|
||||
auto height = static_cast<uint32_t>(gxm::get_height(&gxm_texture));
|
||||
const GLenum format = translate_format(base_format);
|
||||
const GLenum type = translate_type(base_format);
|
||||
const auto texture_type = gxm_texture.texture_type();
|
||||
@@ -129,7 +111,7 @@ void configure_bound_texture(const renderer::TextureCacheState &state, const Sce
|
||||
if (block_compressed) {
|
||||
size_t compressed_size = renderer::texture::get_compressed_size(base_fmt, width, height);
|
||||
glCompressedTexImage2D(upload_type, mip_index, internal_format, width, height, 0, static_cast<GLsizei>(compressed_size), nullptr);
|
||||
} else if (!is_swizzled || (is_swizzled && can_texture_be_unswizzled_without_decode(base_fmt))) {
|
||||
} else if (!is_swizzled || (is_swizzled && renderer::texture::can_texture_be_unswizzled_without_decode(base_fmt, false))) {
|
||||
glTexImage2D(upload_type, mip_index, internal_format, width, height, 0, format, type, nullptr);
|
||||
} else {
|
||||
if (is_swizzled) {
|
||||
@@ -154,510 +136,28 @@ void configure_bound_texture(const renderer::TextureCacheState &state, const Sce
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Remove arbitraty blocks from block compressed texture
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of ((width + 3) / 4) * ((height + 3) / 4) * 8 or 16 (bytes) depending on texture base format.
|
||||
* \param data Source data to decompress.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Void.
|
||||
*/
|
||||
static void remove_compressed_arbitrary_blocks(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const std::uint32_t width, const std::uint32_t height) {
|
||||
uint32_t w = (width + 3) / 4;
|
||||
uint32_t h = (height + 3) / 4;
|
||||
|
||||
uint32_t a_w = next_power_of_two(w);
|
||||
|
||||
std::size_t block_size;
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
block_size = 8;
|
||||
return;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
block_size = 16;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
w *= block_size;
|
||||
a_w *= block_size;
|
||||
|
||||
const std::uint8_t *src = reinterpret_cast<const std::uint8_t *>(data);
|
||||
std::uint8_t *dst = reinterpret_cast<std::uint8_t *>(dest);
|
||||
|
||||
for (std::size_t j = h; j; j--) {
|
||||
memcpy(dst, src, w);
|
||||
src += a_w;
|
||||
dst += w;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Try to decompress texture to 32-bit RGBA.
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of align(width, 4) * align(height,4) * 4 (bytes).
|
||||
* \param data Source data to decompress.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Size of source taken.
|
||||
*/
|
||||
static size_t decompress_compressed_swizz_texture(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const std::uint32_t width, const std::uint32_t height) {
|
||||
int bc_type = 0;
|
||||
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
bc_type = 1;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
bc_type = 2;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
bc_type = 3;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
bc_type = 4;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
bc_type = 5;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
bc_type = 6;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
bc_type = 7;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bc_type) {
|
||||
renderer::texture::decompress_bc_swizz_image(width, height, reinterpret_cast<const std::uint8_t *>(data),
|
||||
reinterpret_cast<std::uint32_t *>(dest), bc_type);
|
||||
return (((width + 3) / 4) * ((height + 3) / 4) * ((bc_type != 1 && bc_type != 4 && bc_type != 5) ? 16 : 8));
|
||||
} else if ((fmt >= SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP) && (fmt <= SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP)) {
|
||||
pvr::PVRTDecompressPVRTC(data, (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP) || (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP), width, height,
|
||||
(fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP) || (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP), reinterpret_cast<uint8_t *>(dest));
|
||||
|
||||
const bool is_2bpp = (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP) || (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP);
|
||||
|
||||
const std::uint32_t num_xword = (width + (is_2bpp ? 7 : 3)) / (is_2bpp ? 8 : 4);
|
||||
const std::uint32_t num_yword = (height + 3) / 4;
|
||||
|
||||
return (std::size_t)num_xword * (std::size_t)num_yword * (size_t)8;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Try to decompress texture to 16-bit RGB floating point color.
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of align(width, 4) * height * 4 (bytes).
|
||||
* \param data Source data to decompress.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Void.
|
||||
*/
|
||||
static void decompress_packed_float_e5m9m9m9(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const uint32_t width, const uint32_t height) {
|
||||
const uint32_t *in = reinterpret_cast<const uint32_t *>(data);
|
||||
uint16_t *out = reinterpret_cast<uint16_t *>(dest);
|
||||
|
||||
for (uint32_t in_offset = 0, out_offset = 0; in_offset < width * height; ++in_offset) {
|
||||
const uint32_t packed = in[in_offset];
|
||||
const uint16_t exponent = static_cast<uint16_t>(packed >> 17);
|
||||
|
||||
out[out_offset++] = exponent | ((packed & (0x1FF << 18)) >> 17);
|
||||
out[out_offset++] = exponent | ((packed & (0x1FF << 9)) >> 8);
|
||||
out[out_offset++] = exponent | ((packed & 0x1FF) << 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Try to resolve Z-order of block compressed texture
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of align(width, 4) * align(height,4) * 4 (bytes).
|
||||
* \param data Source data to solve.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Void.
|
||||
*/
|
||||
static void resolve_z_order_compressed_texture(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const std::uint32_t width, const std::uint32_t height) {
|
||||
int bc_type = 0;
|
||||
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
bc_type = 1;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
bc_type = 2;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
bc_type = 3;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
bc_type = 4;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
bc_type = 5;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
bc_type = 6;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
bc_type = 7;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bc_type)
|
||||
renderer::texture::resolve_z_order_compressed_image(width, height, reinterpret_cast<const std::uint8_t *>(data),
|
||||
reinterpret_cast<std::uint8_t *>(dest), bc_type);
|
||||
}
|
||||
|
||||
static void convert_x8u24_to_u24x8(void *dest, const void *data, const uint32_t width, const uint32_t height, const size_t row_length_in_pixels) {
|
||||
auto dst = static_cast<uint32_t *>(dest);
|
||||
auto src = static_cast<const uint32_t *>(data);
|
||||
|
||||
for (uint32_t row = 0; row < height; ++row) {
|
||||
for (uint32_t col = 0; col < width; ++col) {
|
||||
const uint32_t src_value = src[col];
|
||||
const uint32_t value = (src_value << 8) | (src_value >> 24);
|
||||
*dst++ = value;
|
||||
}
|
||||
|
||||
src += row_length_in_pixels;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_f32m_to_f32(void *dest, const void *data, const uint32_t width, const uint32_t height, const size_t row_length_in_pixels) {
|
||||
auto dst = static_cast<uint32_t *>(dest);
|
||||
auto src = static_cast<const uint32_t *>(data);
|
||||
|
||||
for (uint32_t row = 0; row < height; ++row) {
|
||||
for (uint32_t col = 0; col < width; ++col) {
|
||||
const uint32_t src_value = src[col];
|
||||
const uint32_t value = src_value & 0x7FFFFFFF;
|
||||
*dst++ = value;
|
||||
}
|
||||
|
||||
src += row_length_in_pixels;
|
||||
}
|
||||
}
|
||||
|
||||
void upload_bound_texture(const SceGxmTexture &gxm_texture, const MemState &mem) {
|
||||
void upload_bound_texture(SceGxmTextureBaseFormat base_format, uint32_t width, uint32_t height, uint32_t mip_index, const void *pixels, int face, bool is_compressed, size_t pixels_per_stride) {
|
||||
R_PROFILE(__func__);
|
||||
|
||||
const SceGxmTextureFormat fmt = gxm::get_format(&gxm_texture);
|
||||
const SceGxmTextureBaseFormat base_format = gxm::get_base_format(fmt);
|
||||
auto width = static_cast<uint32_t>(gxm::get_width(&gxm_texture));
|
||||
auto height = static_cast<uint32_t>(gxm::get_height(&gxm_texture));
|
||||
const Ptr<uint8_t> data(gxm_texture.data_addr << 2);
|
||||
uint8_t *texture_data = data.get(mem);
|
||||
|
||||
if (!texture_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> texture_data_decompressed;
|
||||
std::vector<uint8_t> texture_pixels_lineared; // TODO Move to context to avoid frequent allocation?
|
||||
std::vector<uint32_t> palette_texture_pixels;
|
||||
std::vector<uint8_t> yuv_texture_pixels;
|
||||
|
||||
const void *pixels = nullptr;
|
||||
|
||||
size_t pixels_per_stride = 0;
|
||||
size_t bpp = renderer::texture::bits_per_pixel(base_format);
|
||||
size_t bytes_per_pixel = (bpp + 7) >> 3;
|
||||
|
||||
const bool block_compressed = renderer::texture::is_compressed_format(base_format);
|
||||
|
||||
const auto texture_type = gxm_texture.texture_type();
|
||||
const bool is_swizzled = (texture_type == SCE_GXM_TEXTURE_SWIZZLED) || (texture_type == SCE_GXM_TEXTURE_CUBE) || (texture_type == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY);
|
||||
const bool need_unswizzle = is_swizzled && block_compressed;
|
||||
const bool need_decompress_and_unswizzle_on_cpu = is_swizzled && !block_compressed && !can_texture_be_unswizzled_without_decode(base_format);
|
||||
|
||||
uint32_t mip_index = 0;
|
||||
uint32_t total_mip = gxm_texture.true_mip_count();
|
||||
uint32_t face_uploaded_count = 0;
|
||||
uint32_t face_total_count;
|
||||
size_t source_size = 0;
|
||||
size_t total_source_so_far = 0;
|
||||
|
||||
// Modified during decompression
|
||||
std::uint32_t org_width = width;
|
||||
std::uint32_t org_height = height;
|
||||
|
||||
std::uint32_t org_width_const = width;
|
||||
std::uint32_t org_height_const = height;
|
||||
|
||||
std::uint32_t face_align_bytes = 4;
|
||||
|
||||
if (texture_type == SCE_GXM_TEXTURE_LINEAR_STRIDED) {
|
||||
total_mip = 1;
|
||||
}
|
||||
|
||||
// GXM's cube map index is same as OpenGL: right, left, top, bottom, front, back
|
||||
GLenum upload_type = GL_TEXTURE_2D;
|
||||
if (face > 0)
|
||||
// GXM's cube map index is same as OpenGL: right, left, top, bottom, front, back
|
||||
upload_type = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (face - 1);
|
||||
|
||||
face_total_count = 1;
|
||||
if (is_compressed) {
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
if ((texture_type == SCE_GXM_TEXTURE_CUBE) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY)) {
|
||||
upload_type = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
|
||||
face_total_count = 6;
|
||||
const GLenum format = translate_format(base_format);
|
||||
size_t compressed_size = renderer::texture::get_compressed_size(base_format, width, height);
|
||||
glCompressedTexSubImage2D(upload_type, mip_index, 0, 0, width, height, format, static_cast<GLsizei>(compressed_size), pixels);
|
||||
} else {
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(pixels_per_stride));
|
||||
|
||||
const bool twok_align_cond1 = ((width >= 32) && (height >= 32) && ((bytes_per_pixel == 1) || (is_block_compressed_format(base_format))));
|
||||
const bool twok_align_cond2 = ((width >= 16) && (height >= 16) && ((bytes_per_pixel == 2) || (bytes_per_pixel == 4)));
|
||||
const bool twok_align_cond3 = ((width >= 8) && (height >= 8) && (bytes_per_pixel == 8));
|
||||
const GLenum format = translate_format(base_format);
|
||||
const GLenum type = translate_type(base_format);
|
||||
glTexSubImage2D(upload_type, mip_index, 0, 0, width, height, format, type, pixels);
|
||||
|
||||
if (twok_align_cond1 || twok_align_cond2 || twok_align_cond3) {
|
||||
face_align_bytes = 2048;
|
||||
}
|
||||
}
|
||||
|
||||
while ((face_uploaded_count < face_total_count) && org_width && org_height) {
|
||||
width = org_width;
|
||||
height = org_height;
|
||||
pixels = texture_data;
|
||||
|
||||
// Get pixels per stride
|
||||
switch (texture_type) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY:
|
||||
case SCE_GXM_TEXTURE_CUBE_ARBITRARY:
|
||||
width = next_power_of_two(width);
|
||||
height = next_power_of_two(height);
|
||||
case SCE_GXM_TEXTURE_SWIZZLED:
|
||||
case SCE_GXM_TEXTURE_CUBE:
|
||||
case SCE_GXM_TEXTURE_TILED:
|
||||
pixels_per_stride = static_cast<size_t>(width);
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_LINEAR:
|
||||
pixels_per_stride = static_cast<size_t>((width + 7) & ~7);
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_LINEAR_STRIDED:
|
||||
pixels_per_stride = gxm::get_stride_in_bytes(&gxm_texture) / bytes_per_pixel;
|
||||
if (base_format == SCE_GXM_TEXTURE_BASE_FORMAT_P4) // P4 textures are the only one not byte aligned, therefore bytes_per_pixel should be 0.5 and not 1, correct it here
|
||||
pixels_per_stride *= 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (gxm::is_paletted_format(base_format)) {
|
||||
palette_texture_pixels.resize(width * height * 4);
|
||||
if (base_format == SCE_GXM_TEXTURE_BASE_FORMAT_P8) {
|
||||
renderer::texture::palette_texture_to_rgba_8(palette_texture_pixels.data(),
|
||||
reinterpret_cast<const uint8_t *>(pixels), width, height, pixels_per_stride, renderer::texture::get_texture_palette(gxm_texture, mem));
|
||||
} else {
|
||||
renderer::texture::palette_texture_to_rgba_4(reinterpret_cast<uint32_t *>(palette_texture_pixels.data()),
|
||||
reinterpret_cast<const uint8_t *>(pixels), width, height, pixels_per_stride / 2, renderer::texture::get_texture_palette(gxm_texture, mem));
|
||||
}
|
||||
pixels = palette_texture_pixels.data();
|
||||
bytes_per_pixel = 4;
|
||||
bpp = 32;
|
||||
}
|
||||
|
||||
switch (texture_type) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLED:
|
||||
case SCE_GXM_TEXTURE_CUBE:
|
||||
case SCE_GXM_TEXTURE_TILED:
|
||||
case SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY:
|
||||
case SCE_GXM_TEXTURE_CUBE_ARBITRARY: {
|
||||
if (need_unswizzle) {
|
||||
// Must unswizzle them
|
||||
texture_data_decompressed.resize(renderer::texture::get_compressed_size(base_format, width, height));
|
||||
resolve_z_order_compressed_texture(base_format, texture_data_decompressed.data(), pixels, width, height);
|
||||
pixels = texture_data_decompressed.data();
|
||||
} else if (need_decompress_and_unswizzle_on_cpu) {
|
||||
// Must decompress them
|
||||
texture_data_decompressed.resize(align(width, 4) * align(height, 4) * 4);
|
||||
source_size = decompress_compressed_swizz_texture(base_format, texture_data_decompressed.data(), pixels, width, height);
|
||||
bytes_per_pixel = 4;
|
||||
bpp = 32;
|
||||
pixels = texture_data_decompressed.data();
|
||||
}
|
||||
|
||||
switch (base_format) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT4BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP:
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9:
|
||||
texture_data_decompressed.resize(width * height * 6);
|
||||
decompress_packed_float_e5m9m9m9(base_format, texture_data_decompressed.data(), pixels, width, height);
|
||||
pixels = texture_data_decompressed.data();
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_X8U24:
|
||||
// X8 = [24-31], D24 = [0-23], technically this is GL_UNSIGNED_INT_24_8_REV which does not exist
|
||||
// TODO: Requires shader to convert the normalized value read by GL to unsigned int. Just multiply by 2^24-1 when reading and you're done.
|
||||
texture_data_decompressed.resize(width * height * 4);
|
||||
convert_x8u24_to_u24x8(texture_data_decompressed.data(), pixels, width, height, pixels_per_stride);
|
||||
pixels = texture_data_decompressed.data();
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32M:
|
||||
// Convert F32M to F32
|
||||
texture_data_decompressed.resize(width * height * 4);
|
||||
convert_f32m_to_f32(texture_data_decompressed.data(), pixels, width, height, pixels_per_stride);
|
||||
pixels = texture_data_decompressed.data();
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
source_size = renderer::texture::get_compressed_size(base_format, width, height);
|
||||
|
||||
if ((texture_type == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY)) {
|
||||
size_t compressed_size = renderer::texture::get_compressed_size(base_format, org_width, org_height);
|
||||
|
||||
texture_pixels_lineared.resize(compressed_size);
|
||||
remove_compressed_arbitrary_blocks(base_format, texture_pixels_lineared.data(), pixels, org_width, org_height);
|
||||
|
||||
pixels = texture_pixels_lineared.data();
|
||||
|
||||
if (need_unswizzle)
|
||||
texture_data_decompressed.clear();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Convert data
|
||||
texture_pixels_lineared.resize(width * height * bytes_per_pixel);
|
||||
|
||||
if (is_swizzled)
|
||||
renderer::texture::swizzled_texture_to_linear_texture(texture_pixels_lineared.data(), reinterpret_cast<const uint8_t *>(pixels), width, height,
|
||||
static_cast<std::uint8_t>(bpp));
|
||||
else
|
||||
renderer::texture::tiled_texture_to_linear_texture(texture_pixels_lineared.data(), reinterpret_cast<const uint8_t *>(pixels), width, height,
|
||||
static_cast<std::uint8_t>(bpp));
|
||||
|
||||
pixels = texture_pixels_lineared.data();
|
||||
|
||||
if (need_decompress_and_unswizzle_on_cpu)
|
||||
texture_data_decompressed.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
if ((texture_type == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY)) {
|
||||
width = org_width;
|
||||
height = org_height;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SCE_GXM_TEXTURE_LINEAR:
|
||||
case SCE_GXM_TEXTURE_LINEAR_STRIDED:
|
||||
break;
|
||||
}
|
||||
|
||||
if (gxm::is_paletted_format(base_format)) {
|
||||
pixels_per_stride = width;
|
||||
}
|
||||
|
||||
if (gxm::is_yuv_format(base_format)) {
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P2_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P2_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P2_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P2_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P3_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P3_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P3_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P3_CSC1: {
|
||||
yuv_texture_pixels.resize(width * height * 3);
|
||||
renderer::texture::yuv420_texture_to_rgb(yuv_texture_pixels.data(),
|
||||
reinterpret_cast<const uint8_t *>(pixels), width, height);
|
||||
pixels = yuv_texture_pixels.data();
|
||||
pixels_per_stride = width;
|
||||
break;
|
||||
}
|
||||
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUYV422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVYU422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_UYVY422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_VYUY422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUYV422_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVYU422_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_UYVY422_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_VYUY422_CSC1:
|
||||
LOG_ERROR("Yuv Texture format not implemented: {}", fmt);
|
||||
assert(false);
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (block_compressed) {
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
const GLenum format = translate_format(base_format);
|
||||
size_t compressed_size = renderer::texture::get_compressed_size(base_format, width, height);
|
||||
glCompressedTexSubImage2D(upload_type, mip_index, 0, 0, width, height, format, static_cast<GLsizei>(compressed_size), pixels);
|
||||
} else {
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(pixels_per_stride));
|
||||
|
||||
if (need_decompress_and_unswizzle_on_cpu)
|
||||
glTexSubImage2D(upload_type, mip_index, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
else {
|
||||
const GLenum format = translate_format(base_format);
|
||||
const GLenum type = translate_type(base_format);
|
||||
source_size = (width * height * ((bpp + 7) >> 3));
|
||||
glTexSubImage2D(upload_type, mip_index, 0, 0, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
mip_index++;
|
||||
org_width /= 2;
|
||||
org_height /= 2;
|
||||
|
||||
texture_data += source_size;
|
||||
total_source_so_far += source_size;
|
||||
|
||||
if (mip_index == total_mip) {
|
||||
mip_index = 0;
|
||||
face_uploaded_count++;
|
||||
|
||||
org_width = org_width_const;
|
||||
org_height = org_height_const;
|
||||
|
||||
upload_type++;
|
||||
|
||||
size_t source_unaligned_size = total_source_so_far;
|
||||
total_source_so_far = align(total_source_so_far, face_align_bytes);
|
||||
|
||||
texture_data += total_source_so_far - source_unaligned_size;
|
||||
}
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,7 +173,7 @@ void dump(const SceGxmTexture &gxm_texture, const MemState &mem, const std::stri
|
||||
|
||||
if (g_dumped_hashes.find(hash) != g_dumped_hashes.end()) {
|
||||
if (log_parameter && parameter_name != "") {
|
||||
LOG_TRACE("Setting {} of {} by texture {}", parameter_name, hex(program_hash).data(), g_dumped_hashes[hash]);
|
||||
LOG_TRACE("Setting {} of {} by texture {}", parameter_name, hex_string(program_hash), g_dumped_hashes[hash]);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
@@ -688,7 +188,7 @@ void dump(const SceGxmTexture &gxm_texture, const MemState &mem, const std::stri
|
||||
const SceGxmTextureBaseFormat base_format = gxm::get_base_format(format);
|
||||
|
||||
const bool is_swizzled = (gxm_texture.texture_type() == SCE_GXM_TEXTURE_SWIZZLED) || (gxm_texture.texture_type() == SCE_GXM_TEXTURE_CUBE) || (gxm_texture.texture_type() == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (gxm_texture.texture_type() == SCE_GXM_TEXTURE_CUBE_ARBITRARY);
|
||||
const bool need_decompress_and_unswizzle_on_cpu = is_swizzled && !can_texture_be_unswizzled_without_decode(base_format);
|
||||
const bool need_decompress_and_unswizzle_on_cpu = is_swizzled && !renderer::texture::can_texture_be_unswizzled_without_decode(base_format, false);
|
||||
|
||||
size_t bpp = renderer::texture::bits_per_pixel(base_format);
|
||||
size_t stride = (width + 7) & ~7; // NOTE: This is correct only with linear textures.
|
||||
@@ -723,7 +223,7 @@ void dump(const SceGxmTexture &gxm_texture, const MemState &mem, const std::stri
|
||||
if (!fs::exists(texturelog_path))
|
||||
fs::create_directories(texturelog_path);
|
||||
|
||||
const auto tex_filename = fmt::format("tex_{}_{:08X}_{}.png", tex_index, hash, hex(program_hash).data());
|
||||
const auto tex_filename = fmt::format("tex_{}_{:08X}_{}.png", tex_index, hash, hex_string(program_hash));
|
||||
const auto filepath = texturelog_path / tex_filename;
|
||||
|
||||
if (!stbi_write_png(filepath.string().c_str(), static_cast<int>(width), static_cast<int>(height), static_cast<int>(components), (void *)g_pixels.data(), static_cast<int>(stride * components)))
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
#include <algorithm>
|
||||
|
||||
namespace renderer::gl {
|
||||
bool set_uniform_buffer(GLContext &context, MemState &mem, const bool vertex_shader, const int block_num, const int size, const void *data, bool log_active_shader) {
|
||||
renderer::ShaderProgram *program = vertex_shader ? reinterpret_cast<renderer::ShaderProgram *>(context.record.vertex_program.get(mem)->renderer_data.get())
|
||||
: reinterpret_cast<renderer::ShaderProgram *>(context.record.fragment_program.get(mem)->renderer_data.get());
|
||||
|
||||
bool set_uniform_buffer(GLContext &context, const ShaderProgram *program, const bool vertex_shader, const int block_num, const int size, const uint8_t *data) {
|
||||
auto offset = program->uniform_buffer_data_offsets.at(block_num);
|
||||
if (offset == static_cast<std::uint32_t>(-1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t data_size_upload = std::min<size_t>(size, program->uniform_buffer_sizes.at(block_num) * 4);
|
||||
const size_t offset_start_upload = offset * 4;
|
||||
|
||||
if (vertex_shader) {
|
||||
if (!context.vertex_uniform_buffer_storage_ptr.first) {
|
||||
// Allocate a region for it. Don't worry though, when the shader program is changed
|
||||
@@ -45,6 +45,8 @@ bool set_uniform_buffer(GLContext &context, MemState &mem, const bool vertex_sha
|
||||
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, context.vertex_uniform_stream_ring_buffer.handle(), context.vertex_uniform_buffer_storage_ptr.second, program->max_total_uniform_buffer_storage * 4);
|
||||
}
|
||||
|
||||
std::memcpy(context.vertex_uniform_buffer_storage_ptr.first + offset_start_upload, data, data_size_upload);
|
||||
} else {
|
||||
if (!context.fragment_uniform_buffer_storage_ptr.first) {
|
||||
// Allocate a region for it. Don't worry though, when the shader program is changed
|
||||
@@ -57,23 +59,10 @@ bool set_uniform_buffer(GLContext &context, MemState &mem, const bool vertex_sha
|
||||
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 1, context.fragment_uniform_stream_ring_buffer.handle(), context.fragment_uniform_buffer_storage_ptr.second, program->max_total_uniform_buffer_storage * 4);
|
||||
}
|
||||
}
|
||||
|
||||
const int base_binding_ubo_relative = vertex_shader ? 0 : (SCE_GXM_REAL_MAX_UNIFORM_BUFFER + 1);
|
||||
const std::size_t data_size_upload = std::min<GLsizeiptr>(size, program->uniform_buffer_sizes.at(block_num) * 4);
|
||||
const std::size_t offset_start_upload = offset * 4;
|
||||
|
||||
if (vertex_shader) {
|
||||
std::memcpy(context.vertex_uniform_buffer_storage_ptr.first + offset_start_upload, data, data_size_upload);
|
||||
} else {
|
||||
std::memcpy(context.fragment_uniform_buffer_storage_ptr.first + offset_start_upload, data, data_size_upload);
|
||||
}
|
||||
|
||||
if (log_active_shader) {
|
||||
std::vector<uint8_t> my_data((uint8_t *)data, (uint8_t *)data + size);
|
||||
context.ubo_data[base_binding_ubo_relative + block_num] = my_data;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace renderer::gl
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <renderer/gl/functions.h>
|
||||
#include <renderer/gl/types.h>
|
||||
|
||||
#include <renderer/vulkan/functions.h>
|
||||
|
||||
#include <config/state.h>
|
||||
#include <renderer/functions.h>
|
||||
#include <util/log.h>
|
||||
@@ -38,7 +40,7 @@
|
||||
|
||||
namespace renderer {
|
||||
COMMAND(handle_set_context) {
|
||||
const RenderTarget *rt = helper.pop<const RenderTarget *>();
|
||||
RenderTarget *rt = helper.pop<RenderTarget *>();
|
||||
const SceGxmColorSurface *color_surface = helper.pop<SceGxmColorSurface *>();
|
||||
const SceGxmDepthStencilSurface *depth_stencil_surface = helper.pop<SceGxmDepthStencilSurface *>();
|
||||
|
||||
@@ -48,27 +50,35 @@ COMMAND(handle_set_context) {
|
||||
|
||||
if (color_surface && !color_surface->disabled) {
|
||||
render_context->record.color_surface = *color_surface;
|
||||
delete color_surface;
|
||||
} else {
|
||||
// Disable writing to this surface.
|
||||
// Data is still in render target though.
|
||||
render_context->record.color_surface.data = nullptr;
|
||||
}
|
||||
|
||||
// Maybe we should disable writing to depth stencil too if it's null
|
||||
if (color_surface)
|
||||
delete color_surface;
|
||||
|
||||
if (depth_stencil_surface) {
|
||||
render_context->record.depth_stencil_surface = *depth_stencil_surface;
|
||||
delete depth_stencil_surface;
|
||||
} else {
|
||||
render_context->record.depth_stencil_surface.depthData.reset();
|
||||
render_context->record.depth_stencil_surface.stencilData.reset();
|
||||
static const SceGxmDepthStencilSurface default_ds{
|
||||
.zlsControl = 0,
|
||||
.depthData = Ptr<void>(0),
|
||||
.stencilData = Ptr<void>(0),
|
||||
.backgroundDepth = 1.0f,
|
||||
.control = SceGxmDepthStencilControl::mask_bit
|
||||
};
|
||||
render_context->record.depth_stencil_surface = default_ds;
|
||||
}
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
gl::set_context(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context), mem, reinterpret_cast<const gl::GLRenderTarget *>(rt), features);
|
||||
case Backend::OpenGL:
|
||||
gl::set_context(dynamic_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context), mem, reinterpret_cast<const gl::GLRenderTarget *>(rt), features);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::set_context(*reinterpret_cast<vulkan::VKContext *>(render_context), mem, reinterpret_cast<vulkan::VKRenderTarget *>(rt), features);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -77,6 +87,13 @@ COMMAND(handle_set_context) {
|
||||
}
|
||||
|
||||
COMMAND(handle_sync_surface_data) {
|
||||
if (renderer.current_backend == Backend::Vulkan) {
|
||||
// TODO: put this in a function
|
||||
vulkan::VKContext *context = reinterpret_cast<vulkan::VKContext *>(renderer.context);
|
||||
if (context->is_recording)
|
||||
context->stop_recording();
|
||||
}
|
||||
|
||||
SceGxmColorSurface *surface = &render_context->record.color_surface;
|
||||
if (helper.cmd->status) {
|
||||
surface = helper.pop<SceGxmColorSurface *>();
|
||||
@@ -106,15 +123,17 @@ COMMAND(handle_sync_surface_data) {
|
||||
unprotect_inner(mem, data, total_size);
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
case Backend::OpenGL:
|
||||
if (helper.cmd->status) {
|
||||
gl::lookup_and_get_surface_data(static_cast<gl::GLState &>(renderer), mem, *surface);
|
||||
} else {
|
||||
gl::get_surface_data(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context), pixels, *surface);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
// not implemented for now
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -155,18 +174,20 @@ COMMAND(handle_draw) {
|
||||
const std::uint32_t instance_count = helper.pop<const std::uint32_t>();
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
gl::draw(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context),
|
||||
case Backend::OpenGL:
|
||||
gl::draw(dynamic_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context),
|
||||
features, type, format, indicies, count, instance_count, mem, base_path, title_id, self_name, config);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
case Backend::Vulkan:
|
||||
vulkan::draw(*reinterpret_cast<vulkan::VKContext *>(render_context), type, format, indicies,
|
||||
count, instance_count, mem, config);
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
COMMAND(handle_transfer_copy) {
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/shaders.h>
|
||||
|
||||
#include <renderer/profile.h>
|
||||
|
||||
#include <renderer/vulkan/state.h>
|
||||
|
||||
#include <gxm/types.h>
|
||||
#include <renderer/state.h>
|
||||
#include <renderer/types.h>
|
||||
#include <shader/spirv_recompiler.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/log.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace renderer {
|
||||
|
||||
bool get_shaders_cache_hashs(State &renderer) {
|
||||
const auto shaders_path{ fs::path(renderer.base_path) / "cache/shaders" / renderer.title_id / renderer.self_name };
|
||||
const std::string hash_file_name = fmt::format("hashs-{}.dat", (renderer.current_backend == Backend::OpenGL) ? "gl" : "vk");
|
||||
|
||||
if (renderer.current_backend == Backend::Vulkan) {
|
||||
// try to read pipeline cache
|
||||
dynamic_cast<vulkan::VKState &>(renderer).pipeline_cache.read_pipeline_cache();
|
||||
}
|
||||
|
||||
fs::ifstream shaders_hashs(shaders_path / hash_file_name, std::ios::in | std::ios::binary);
|
||||
if (shaders_hashs.is_open()) {
|
||||
renderer.shaders_cache_hashs.clear();
|
||||
// Read size of hashes list
|
||||
size_t size;
|
||||
shaders_hashs.read((char *)&size, sizeof(size));
|
||||
|
||||
// Check version of cache
|
||||
uint32_t versionInFile;
|
||||
shaders_hashs.read((char *)&versionInFile, sizeof(uint32_t));
|
||||
if (versionInFile != shader::CURRENT_VERSION) {
|
||||
shaders_hashs.close();
|
||||
fs::remove_all(shaders_path);
|
||||
fs::remove_all(fs::path(renderer.base_path) / "shaderlog" / renderer.title_id / renderer.self_name);
|
||||
LOG_WARN("Current version of cache: {}, is outdated, recreate it.", versionInFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read Hashs info value
|
||||
for (size_t a = 0; a < size; a++) {
|
||||
auto read = [&shaders_hashs]() {
|
||||
Sha256Hash hash;
|
||||
|
||||
shaders_hashs.read(reinterpret_cast<char *>(hash.data()), sizeof(Sha256Hash));
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
ShadersHash hash;
|
||||
hash.frag = read();
|
||||
hash.vert = read();
|
||||
|
||||
renderer.shaders_cache_hashs.push_back({ hash.frag, hash.vert });
|
||||
}
|
||||
|
||||
shaders_hashs.close();
|
||||
}
|
||||
|
||||
return !renderer.shaders_cache_hashs.empty();
|
||||
}
|
||||
|
||||
void save_shaders_cache_hashs(State &renderer, std::vector<ShadersHash> &shaders_cache_hashs) {
|
||||
const auto shaders_path{ fs::path(renderer.base_path) / "cache/shaders" / renderer.title_id / renderer.self_name };
|
||||
if (!fs::exists(shaders_path))
|
||||
fs::create_directory(shaders_path);
|
||||
std::string hash_file_name = fmt::format("hashs-{}.dat", (renderer.current_backend == Backend::OpenGL) ? "gl" : "vk");
|
||||
fs::ofstream shaders_hashs(shaders_path / hash_file_name, std::ios::out | std::ios::binary);
|
||||
|
||||
if (shaders_hashs.is_open()) {
|
||||
// Write Size of shaders cache hashes list
|
||||
const auto size = shaders_cache_hashs.size();
|
||||
shaders_hashs.write((char *)&size, sizeof(size));
|
||||
|
||||
// Write version of cache
|
||||
const uint32_t versionInFile = shader::CURRENT_VERSION;
|
||||
shaders_hashs.write((char *)&versionInFile, sizeof(uint32_t));
|
||||
|
||||
// Write shader hash list
|
||||
for (const auto &hash : shaders_cache_hashs) {
|
||||
auto write = [&shaders_hashs](const Sha256Hash &hash) {
|
||||
shaders_hashs.write(reinterpret_cast<const char *>(hash.data()), sizeof(Sha256Hash));
|
||||
};
|
||||
|
||||
write(hash.frag);
|
||||
write(hash.vert);
|
||||
}
|
||||
shaders_hashs.close();
|
||||
}
|
||||
}
|
||||
|
||||
static bool load_shader(const char *hash, const char *extension, const char *base_path, const char *title_id, const char *self_name, char **destination, std::size_t &size_read) {
|
||||
const auto shader_path = fs_utils::construct_file_name(base_path, (fs::path("cache/shaders") / title_id / self_name).string().c_str(), hash, extension);
|
||||
fs::ifstream is(shader_path, fs::ifstream::binary);
|
||||
if (!is) {
|
||||
return false;
|
||||
}
|
||||
|
||||
is.seekg(0, fs::ifstream::end);
|
||||
size_read = is.tellg();
|
||||
is.seekg(0);
|
||||
|
||||
if (size_read == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (destination == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
is.read(*destination, size_read);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const Sha256Hash get_shader_hash(const SceGxmProgram &program) {
|
||||
const Sha256Hash hash_bytes = sha256(&program, program.size);
|
||||
return hash_bytes;
|
||||
}
|
||||
|
||||
template <typename R>
|
||||
R load_shader_generic(const char *hash_text, const char *base_path, const char *title_id, const char *self_name, const char *shader_type_str) {
|
||||
std::size_t read_size = 0;
|
||||
R source;
|
||||
|
||||
if (load_shader(hash_text, shader_type_str, base_path, title_id, self_name, nullptr, read_size)) {
|
||||
source.resize((read_size + sizeof(typename R::value_type) - 1) / sizeof(typename R::value_type));
|
||||
|
||||
char *dest_pointer = reinterpret_cast<char *>(source.data());
|
||||
load_shader(hash_text, shader_type_str, base_path, title_id, self_name, &dest_pointer, read_size);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
shader::GeneratedShader load_shader_generic(shader::Target target, const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const char *shader_type_str, const std::string &shader_version, bool shader_cache) {
|
||||
// TODO: no need to recompute the hash here
|
||||
const std::string hash_text = hex_string(get_shader_hash(program));
|
||||
// Set Shader Hash with Version
|
||||
const std::string hash_hex_ver = shader_version + "-" + static_cast<std::string>(hash_text.data());
|
||||
|
||||
if (shader_cache) {
|
||||
if (target == shader::Target::GLSLOpenGL) {
|
||||
std::string source = load_shader_generic<std::string>(hash_hex_ver.c_str(), base_path, title_id, self_name, shader_type_str);
|
||||
if (!source.empty()) {
|
||||
return { source, std::vector<uint32_t>() };
|
||||
}
|
||||
} else {
|
||||
std::vector<uint32_t> source = load_shader_generic<std::vector<uint32_t>>(hash_hex_ver.c_str(), base_path, title_id, self_name, shader_type_str);
|
||||
if (!source.empty())
|
||||
return { "", source };
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Generating {} shader {}", shader_type_str, hash_text.data());
|
||||
|
||||
std::string spirv_dump;
|
||||
std::string disasm_dump;
|
||||
|
||||
const fs::path shader_base_dir{ fs::path("shaderlog") / title_id / self_name };
|
||||
if (!fs::exists(base_path / shader_base_dir))
|
||||
fs::create_directories(base_path / shader_base_dir);
|
||||
|
||||
auto shader_base_path = fs_utils::construct_file_name(base_path, shader_base_dir, hash_hex_ver.c_str(), ".gxp");
|
||||
|
||||
// Dump gxp binary
|
||||
fs::ofstream of{ shader_base_path, fs::ofstream::binary };
|
||||
if (!of.fail()) {
|
||||
of.write(reinterpret_cast<const char *>(&program), program.size);
|
||||
of.close();
|
||||
}
|
||||
|
||||
const auto write_data_with_ext = [&](const std::string &ext, const std::string &data) {
|
||||
fs::path out_path{ shader_base_path };
|
||||
out_path.replace_extension(ext);
|
||||
fs::ofstream of{ out_path };
|
||||
if (!of.fail()) {
|
||||
of << data;
|
||||
of.close();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
shader::GeneratedShader source = shader::convert_gxp(program, hash_text.data(), features, target, hint_attributes, maskupdate, false, write_data_with_ext);
|
||||
|
||||
// Copy shader generate to shaders cache
|
||||
const auto shaders_cache_path = fs::path("cache/shaders") / title_id / self_name;
|
||||
if (!fs::exists(base_path / shaders_cache_path))
|
||||
fs::create_directories(base_path / shaders_cache_path);
|
||||
|
||||
if (target == shader::Target::GLSLOpenGL) {
|
||||
shader_base_path.replace_extension(shader_type_str);
|
||||
if (fs::exists(shader_base_path)) {
|
||||
try {
|
||||
const auto shader_dst_path = fs_utils::construct_file_name(base_path, shaders_cache_path, hash_hex_ver.c_str(), shader_type_str);
|
||||
fs::copy_file(shader_base_path, shader_dst_path, fs::copy_option::overwrite_if_exists);
|
||||
fs::remove(shader_base_path);
|
||||
} catch (std::exception &e) {
|
||||
LOG_ERROR("Failed to moved shaders file: \n{}", e.what());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto shader_dst_path = fs_utils::construct_file_name(base_path, shaders_cache_path, hash_hex_ver.c_str(), "spv");
|
||||
fs::ofstream of{ shader_dst_path, fs::ofstream::binary };
|
||||
if (!of.fail()) {
|
||||
of.write(reinterpret_cast<const char *>(source.spirv.data()), sizeof(uint32_t) * source.spirv.size());
|
||||
of.close();
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string load_glsl_shader(const SceGxmProgram &program, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, bool shader_cache) {
|
||||
SceGxmProgramType program_type = program.get_type();
|
||||
|
||||
auto shader_type_to_str = [](SceGxmProgramType type) {
|
||||
return (type == SceGxmProgramType::Vertex) ? "vert" : ((type == SceGxmProgramType::Fragment) ? "frag" : "unknown");
|
||||
};
|
||||
|
||||
const char *shader_type_str = shader_type_to_str(program_type);
|
||||
return load_shader_generic(shader::Target::GLSLOpenGL, program, features, hint_attributes, maskupdate, base_path, title_id, self_name, shader_type_str, shader_version, shader_cache).glsl;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> load_spirv_shader(const SceGxmProgram &program, const FeatureState &features, bool is_vulkan, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, const char *base_path, const char *title_id, const char *self_name, const std::string &shader_version, bool shader_cache) {
|
||||
const shader::Target target = is_vulkan ? shader::Target::SpirVVulkan : shader::Target::SpirVOpenGL;
|
||||
auto shader_type_to_str = [](SceGxmProgramType type) {
|
||||
return (type == SceGxmProgramType::Vertex) ? "vert.spv.txt" : ((type == SceGxmProgramType::Fragment) ? "frag.spv.txt" : "unknown.spv.txt");
|
||||
};
|
||||
const char *shader_type_str = shader_type_to_str(program.get_type());
|
||||
|
||||
return load_shader_generic(target, program, features, hint_attributes, maskupdate, base_path, title_id, self_name, shader_type_str, shader_version, shader_cache).spirv;
|
||||
}
|
||||
|
||||
std::string pre_load_shader_glsl(const char *hash_text, const char *shader_type_str, const char *base_path, const char *title_id, const char *self_name) {
|
||||
return load_shader_generic<std::string>(hash_text, base_path, title_id, self_name, shader_type_str);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> pre_load_shader_spirv(const char *hash_text, const char *shader_type_str, const char *base_path, const char *title_id, const char *self_name) {
|
||||
return load_shader_generic<std::vector<uint32_t>>(hash_text, base_path, title_id, self_name, shader_type_str);
|
||||
}
|
||||
|
||||
} // namespace renderer
|
||||
@@ -25,6 +25,10 @@
|
||||
#include <renderer/gl/state.h>
|
||||
#include <renderer/gl/types.h>
|
||||
|
||||
#include <renderer/vulkan/functions.h>
|
||||
#include <renderer/vulkan/state.h>
|
||||
#include <renderer/vulkan/types.h>
|
||||
|
||||
#include <util/align.h>
|
||||
#include <util/log.h>
|
||||
|
||||
@@ -40,14 +44,18 @@ COMMAND_SET_STATE(region_clip) {
|
||||
|
||||
render_context->record.region_clip_min.x = static_cast<SceInt>(align_down(xMin, SCE_GXM_TILE_SIZEX));
|
||||
render_context->record.region_clip_min.y = static_cast<SceInt>(align_down(yMin, SCE_GXM_TILE_SIZEY));
|
||||
render_context->record.region_clip_max.x = static_cast<SceInt>(align(xMax, SCE_GXM_TILE_SIZEX));
|
||||
render_context->record.region_clip_max.y = static_cast<SceInt>(align(yMax, SCE_GXM_TILE_SIZEY));
|
||||
// borders are inclusive
|
||||
render_context->record.region_clip_max.x = static_cast<SceInt>(align(xMax, SCE_GXM_TILE_SIZEX)) - 1;
|
||||
render_context->record.region_clip_max.y = static_cast<SceInt>(align(yMax, SCE_GXM_TILE_SIZEY)) - 1;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_clipping(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context));
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::sync_clipping(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -61,14 +69,17 @@ COMMAND_SET_STATE(program) {
|
||||
|
||||
if (is_fragment) {
|
||||
render_context->record.fragment_program = program.cast<const SceGxmFragmentProgram>();
|
||||
const bool is_maskupdate = render_context->record.fragment_program.get(mem)->is_maskupdate;
|
||||
render_context->record.is_maskupdate = is_maskupdate;
|
||||
const SceGxmFragmentProgram *gxm_program = render_context->record.fragment_program.get(mem);
|
||||
render_context->record.fragment_program_hash = gxm_program->renderer_data->hash;
|
||||
render_context->record.is_maskupdate = gxm_program->is_maskupdate;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_blending(render_context->record, mem);
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -76,6 +87,12 @@ COMMAND_SET_STATE(program) {
|
||||
}
|
||||
} else {
|
||||
render_context->record.vertex_program = program.cast<const SceGxmVertexProgram>();
|
||||
const SceGxmVertexProgram *gxm_program = render_context->record.vertex_program.get(mem);
|
||||
render_context->record.vertex_program_hash = gxm_program->renderer_data->hash;
|
||||
}
|
||||
|
||||
if (renderer.current_backend == Backend::Vulkan) {
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,41 +101,43 @@ COMMAND_SET_STATE(uniform) {
|
||||
const SceGxmProgramParameter *parameter = helper.pop<SceGxmProgramParameter *>();
|
||||
const void *data = helper.pop<const void *>();
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
gl::UniformSetRequest request{ parameter, data };
|
||||
gl::GLContext *gl_context = reinterpret_cast<gl::GLContext *>(render_context);
|
||||
|
||||
if (is_vertex) {
|
||||
gl_context->vertex_set_requests.push_back(std::move(request));
|
||||
} else {
|
||||
gl_context->fragment_set_requests.push_back(std::move(request));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
UniformSetRequest request{ parameter, data };
|
||||
if (is_vertex) {
|
||||
render_context->vertex_set_requests.push_back(std::move(request));
|
||||
} else {
|
||||
render_context->fragment_set_requests.push_back(std::move(request));
|
||||
}
|
||||
}
|
||||
|
||||
COMMAND_SET_STATE(uniform_buffer) {
|
||||
std::uint8_t *data = helper.pop<std::uint8_t *>();
|
||||
uint8_t *data = helper.pop<std::uint8_t *>();
|
||||
const bool is_vertex = helper.pop<bool>();
|
||||
const int block_num = helper.pop<int>();
|
||||
const std::uint32_t size = helper.pop<std::uint32_t>();
|
||||
|
||||
renderer::ShaderProgram *program = is_vertex ? reinterpret_cast<ShaderProgram *>(render_context->record.vertex_program.get(mem)->renderer_data.get())
|
||||
: reinterpret_cast<ShaderProgram *>(render_context->record.fragment_program.get(mem)->renderer_data.get());
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
gl::set_uniform_buffer(*reinterpret_cast<gl::GLContext *>(render_context), mem, is_vertex, block_num, size, data, config.log_active_shaders);
|
||||
case Backend::OpenGL:
|
||||
gl::set_uniform_buffer(*reinterpret_cast<gl::GLContext *>(render_context), program, is_vertex, block_num, size, data);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::set_uniform_buffer(*reinterpret_cast<vulkan::VKContext *>(render_context), program, is_vertex, block_num, size, data);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto offset = program->uniform_buffer_data_offsets.at(block_num);
|
||||
if (offset != static_cast<uint32_t>(-1) && config.log_active_shaders) {
|
||||
const int base_binding_ubo_relative = is_vertex ? 0 : (SCE_GXM_REAL_MAX_UNIFORM_BUFFER + 1);
|
||||
|
||||
std::vector<uint8_t> my_data((uint8_t *)data, (uint8_t *)data + size);
|
||||
render_context->ubo_data[base_binding_ubo_relative + block_num] = my_data;
|
||||
}
|
||||
|
||||
delete[] data;
|
||||
@@ -126,7 +145,9 @@ COMMAND_SET_STATE(uniform_buffer) {
|
||||
|
||||
COMMAND_SET_STATE(viewport) {
|
||||
const bool flat = helper.pop<bool>();
|
||||
render_context->record.viewport_flat = flat;
|
||||
|
||||
const float previous_flip_y = render_context->record.viewport_flip[1];
|
||||
if (!flat) {
|
||||
const float xOffset = helper.pop<float>();
|
||||
const float yOffset = helper.pop<float>();
|
||||
@@ -135,21 +156,65 @@ COMMAND_SET_STATE(viewport) {
|
||||
const float yScale = helper.pop<float>();
|
||||
const float zScale = helper.pop<float>();
|
||||
|
||||
const float ymin = yOffset + yScale;
|
||||
const float ymax = yOffset - yScale - 1;
|
||||
|
||||
render_context->record.viewport_flip[0] = 1.0f;
|
||||
render_context->record.viewport_flip[1] = (ymin < ymax) ? -1.0f : 1.0f;
|
||||
render_context->record.viewport_flip[2] = 1.0f;
|
||||
render_context->record.viewport_flip[3] = 1.0f;
|
||||
render_context->record.z_offset = zOffset;
|
||||
render_context->record.z_scale = zScale;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_viewport_real(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context), xOffset, yOffset, zOffset, xScale, yScale, zScale);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::sync_viewport_real(*reinterpret_cast<vulkan::VKContext *>(render_context), xOffset, yOffset, zOffset, xScale, yScale, zScale);
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
render_context->record.viewport_flip[0] = 1.0f;
|
||||
render_context->record.viewport_flip[1] = -1.0f;
|
||||
render_context->record.viewport_flip[2] = 1.0f;
|
||||
render_context->record.viewport_flip[3] = 1.0f;
|
||||
render_context->record.z_offset = 0.0f;
|
||||
render_context->record.z_scale = 1.0f;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_viewport_flat(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context));
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::sync_viewport_flat(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (previous_flip_y != render_context->record.viewport_flip[1]) {
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
// We need to sync again state that uses the flip
|
||||
gl::sync_cull(render_context->record);
|
||||
gl::sync_clipping(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context));
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
// We need to sync again state that uses the flip
|
||||
vulkan::sync_clipping(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
@@ -162,16 +227,19 @@ COMMAND_SET_STATE(depth_bias) {
|
||||
const int factor = helper.pop<int>();
|
||||
const int unit = helper.pop<int>();
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
if (is_front) {
|
||||
gl::sync_depth_bias(factor, unit, is_front);
|
||||
} else {
|
||||
// LOG_INFO("AAAA");
|
||||
}
|
||||
render_context->record.depth_bias_unit = unit;
|
||||
render_context->record.depth_bias_slope = factor;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
if (is_front)
|
||||
gl::sync_depth_bias(factor, unit, is_front);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
if (is_front)
|
||||
vulkan::sync_depth_bias(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -190,10 +258,13 @@ COMMAND_SET_STATE(depth_func) {
|
||||
}
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_depth_func(depth_func, is_front);
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -211,10 +282,13 @@ COMMAND_SET_STATE(depth_write_enable) {
|
||||
render_context->record.back_depth_write_mode = mode;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_depth_write_enable(mode, is_front);
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -225,12 +299,20 @@ COMMAND_SET_STATE(depth_write_enable) {
|
||||
COMMAND_SET_STATE(polygon_mode) {
|
||||
const bool is_front = helper.pop<bool>();
|
||||
const SceGxmPolygonMode mode = helper.pop<SceGxmPolygonMode>();
|
||||
if (is_front)
|
||||
render_context->record.front_polygon_mode = mode;
|
||||
else
|
||||
render_context->record.back_polygon_mode = mode;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_polygon_mode(mode, is_front);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
@@ -240,12 +322,17 @@ COMMAND_SET_STATE(polygon_mode) {
|
||||
COMMAND_SET_STATE(point_line_width) {
|
||||
const bool is_front = helper.pop<bool>();
|
||||
const std::uint32_t width = helper.pop<std::uint32_t>();
|
||||
if (is_front)
|
||||
render_context->record.line_width = width;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_point_line_width(width, is_front);
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -256,19 +343,20 @@ COMMAND_SET_STATE(point_line_width) {
|
||||
COMMAND_SET_STATE(stencil_func) {
|
||||
// Is this the pain that driver guys have to suffer?
|
||||
const bool is_front = helper.pop<bool>();
|
||||
GxmStencilState &stencil_state = is_front ? render_context->record.front_stencil_state : render_context->record.back_stencil_state;
|
||||
GxmStencilStateOp &stencil_state_op = is_front ? render_context->record.front_stencil_state_op : render_context->record.back_stencil_state_op;
|
||||
GxmStencilStateValues &stencil_state_vals = is_front ? render_context->record.front_stencil_state_values : render_context->record.back_stencil_state_values;
|
||||
|
||||
stencil_state.func = helper.pop<SceGxmStencilFunc>();
|
||||
stencil_state.stencil_fail = helper.pop<SceGxmStencilOp>();
|
||||
stencil_state.depth_fail = helper.pop<SceGxmStencilOp>();
|
||||
stencil_state.depth_pass = helper.pop<SceGxmStencilOp>();
|
||||
stencil_state.compare_mask = helper.pop<std::uint8_t>();
|
||||
stencil_state.write_mask = helper.pop<std::uint8_t>();
|
||||
stencil_state_op.func = helper.pop<SceGxmStencilFunc>();
|
||||
stencil_state_op.stencil_fail = helper.pop<SceGxmStencilOp>();
|
||||
stencil_state_op.depth_fail = helper.pop<SceGxmStencilOp>();
|
||||
stencil_state_op.depth_pass = helper.pop<SceGxmStencilOp>();
|
||||
stencil_state_vals.compare_mask = helper.pop<std::uint8_t>();
|
||||
stencil_state_vals.write_mask = helper.pop<std::uint8_t>();
|
||||
|
||||
if (render_context->record.is_maskupdate) {
|
||||
if (stencil_state.func == SCE_GXM_STENCIL_FUNC_NEVER) {
|
||||
if (stencil_state_op.func == SCE_GXM_STENCIL_FUNC_NEVER) {
|
||||
render_context->record.writing_mask = 0.0f;
|
||||
} else if (stencil_state.func == SCE_GXM_STENCIL_FUNC_ALWAYS) {
|
||||
} else if (stencil_state_op.func == SCE_GXM_STENCIL_FUNC_ALWAYS) {
|
||||
render_context->record.writing_mask = 1.0f;
|
||||
} else {
|
||||
assert(false);
|
||||
@@ -277,10 +365,14 @@ COMMAND_SET_STATE(stencil_func) {
|
||||
}
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
gl::sync_stencil_func(stencil_state, mem, !is_front);
|
||||
case Backend::OpenGL:
|
||||
gl::sync_stencil_func(stencil_state_op, stencil_state_vals, mem, !is_front);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::refresh_pipeline(dynamic_cast<vulkan::VKContext &>(*render_context));
|
||||
vulkan::sync_stencil_func(dynamic_cast<vulkan::VKContext &>(*render_context), !is_front);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -289,20 +381,22 @@ COMMAND_SET_STATE(stencil_func) {
|
||||
}
|
||||
|
||||
COMMAND_SET_STATE(stencil_ref) {
|
||||
REPORT_STUBBED();
|
||||
|
||||
const bool is_front = helper.pop<bool>();
|
||||
const uint8_t sref = helper.pop<const unsigned char>();
|
||||
|
||||
GxmStencilState &stencil_state = is_front ? render_context->record.front_stencil_state : render_context->record.back_stencil_state;
|
||||
GxmStencilStateOp &stencil_state_op = is_front ? render_context->record.front_stencil_state_op : render_context->record.back_stencil_state_op;
|
||||
GxmStencilStateValues &stencil_state_vals = is_front ? render_context->record.front_stencil_state_values : render_context->record.back_stencil_state_values;
|
||||
|
||||
stencil_state.ref = sref;
|
||||
stencil_state_vals.ref = sref;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
gl::sync_stencil_func(stencil_state, mem, !is_front);
|
||||
case Backend::OpenGL:
|
||||
gl::sync_stencil_func(stencil_state_op, stencil_state_vals, mem, !is_front);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::sync_stencil_func(dynamic_cast<vulkan::VKContext &>(*render_context), !is_front);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -316,7 +410,12 @@ COMMAND_SET_STATE(texture) {
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_texture(static_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context), mem, texture_index, texture,
|
||||
gl::sync_texture(dynamic_cast<gl::GLState &>(renderer), *reinterpret_cast<gl::GLContext *>(render_context), mem, texture_index, texture,
|
||||
config, base_path, title_id);
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::sync_texture(*reinterpret_cast<vulkan::VKContext *>(render_context), mem, texture_index, texture,
|
||||
config, base_path, title_id);
|
||||
break;
|
||||
|
||||
@@ -329,16 +428,36 @@ COMMAND_SET_STATE(texture) {
|
||||
COMMAND_SET_STATE(two_sided) {
|
||||
const SceGxmTwoSidedMode two_sided = helper.pop<SceGxmTwoSidedMode>();
|
||||
render_context->record.two_sided = two_sided;
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
// TODO: something should be done here
|
||||
break;
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
vulkan::sync_stencil_func(dynamic_cast<vulkan::VKContext &>(*render_context), false);
|
||||
// this second call is useless if two_sided is disabled
|
||||
vulkan::sync_stencil_func(dynamic_cast<vulkan::VKContext &>(*render_context), true);
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
COMMAND_SET_STATE(cull_mode) {
|
||||
render_context->record.cull_mode = helper.pop<SceGxmCullMode>();
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
case Backend::OpenGL:
|
||||
gl::sync_cull(render_context->record);
|
||||
break;
|
||||
}
|
||||
|
||||
case Backend::Vulkan:
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
@@ -351,22 +470,12 @@ COMMAND_SET_STATE(vertex_stream) {
|
||||
const std::size_t stream_index = helper.pop<std::size_t>();
|
||||
const std::size_t stream_data_length = helper.pop<std::size_t>();
|
||||
|
||||
switch (renderer.current_backend) {
|
||||
case Backend::OpenGL: {
|
||||
renderer::GXMStreamInfo &info = render_context->record.vertex_streams[stream_index];
|
||||
if (info.data) {
|
||||
delete info.data;
|
||||
}
|
||||
info.data = stream_data;
|
||||
info.size = stream_data_length;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_MISSING(renderer.current_backend);
|
||||
break;
|
||||
renderer::GXMStreamInfo &info = render_context->record.vertex_streams[stream_index];
|
||||
if (info.data) {
|
||||
delete[] info.data;
|
||||
}
|
||||
info.data = stream_data;
|
||||
info.size = stream_data_length;
|
||||
}
|
||||
|
||||
COMMAND_SET_STATE(fragment_program_enable) {
|
||||
@@ -377,6 +486,10 @@ COMMAND_SET_STATE(fragment_program_enable) {
|
||||
render_context->record.front_side_fragment_program_mode = mode;
|
||||
else
|
||||
render_context->record.back_side_fragment_program_mode = mode;
|
||||
|
||||
if (renderer.current_backend == Backend::Vulkan) {
|
||||
vulkan::refresh_pipeline(*reinterpret_cast<vulkan::VKContext *>(render_context));
|
||||
}
|
||||
}
|
||||
|
||||
COMMAND(handle_set_state) {
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <renderer/types.h>
|
||||
|
||||
#include <renderer/gl/functions.h>
|
||||
#include <renderer/vulkan/functions.h>
|
||||
#include <renderer/vulkan/types.h>
|
||||
|
||||
#include <renderer/functions.h>
|
||||
#include <util/log.h>
|
||||
@@ -43,9 +45,17 @@ COMMAND(handle_signal_sync_object) {
|
||||
|
||||
COMMAND(handle_wait_sync_object) {
|
||||
SceGxmSyncObject *sync = helper.pop<Ptr<SceGxmSyncObject>>().get(mem);
|
||||
RenderTarget *target = helper.pop<RenderTarget *>();
|
||||
const uint32_t timestamp = helper.pop<uint32_t>();
|
||||
|
||||
renderer::wishlist(sync, timestamp);
|
||||
|
||||
if (renderer.current_backend == Backend::Vulkan) {
|
||||
vulkan::VKContext *context = reinterpret_cast<vulkan::VKContext *>(renderer.context);
|
||||
if (context->is_recording)
|
||||
context->stop_recording();
|
||||
vulkan::update_sync_target(sync, reinterpret_cast<vulkan::VKRenderTarget *>(target));
|
||||
}
|
||||
}
|
||||
|
||||
COMMAND(handle_notification) {
|
||||
@@ -61,6 +71,12 @@ COMMAND(handle_notification) {
|
||||
renderer.notification_ready.notify_all();
|
||||
}
|
||||
|
||||
COMMAND(new_frame) {
|
||||
if (renderer.current_backend == Backend::Vulkan) {
|
||||
vulkan::new_frame(*reinterpret_cast<vulkan::VKContext *>(renderer.context));
|
||||
}
|
||||
}
|
||||
|
||||
// Client side function
|
||||
void finish(State &state, Context *context) {
|
||||
// Add NOP then wait for it
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/functions.h>
|
||||
|
||||
#include <renderer/profile.h>
|
||||
#include <renderer/pvrt-dec.h>
|
||||
#include <renderer/texture_cache_state.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
@@ -85,6 +87,631 @@ static size_t find_lru(const TextureCacheState &cache, TextureCacheTimestamp cur
|
||||
return oldest_index;
|
||||
}
|
||||
|
||||
bool can_texture_be_unswizzled_without_decode(SceGxmTextureBaseFormat fmt, bool is_vulkan) {
|
||||
return fmt == SCE_GXM_TEXTURE_BASE_FORMAT_P4
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U4U4U4U4
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_P8
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U8
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U5U6U5
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U1U5U5U5
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8
|
||||
|| (is_vulkan && fmt == SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9);
|
||||
}
|
||||
|
||||
static bool is_block_compressed_format(SceGxmTextureBaseFormat fmt) {
|
||||
return (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC1
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC2
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC3
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC4
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_UBC5
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT4BPP
|
||||
|| fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Try to resolve Z-order of block compressed texture
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of align(width, 4) * align(height,4) * 4 (bytes).
|
||||
* \param data Source data to solve.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Void.
|
||||
*/
|
||||
static void resolve_z_order_compressed_texture(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const std::uint32_t width, const std::uint32_t height) {
|
||||
int bc_type = 0;
|
||||
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
bc_type = 1;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
bc_type = 2;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
bc_type = 3;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
bc_type = 4;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
bc_type = 5;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
bc_type = 6;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
bc_type = 7;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unknown compressed format {}", log_hex(fmt));
|
||||
break;
|
||||
}
|
||||
|
||||
if (bc_type)
|
||||
renderer::texture::resolve_z_order_compressed_image(width, height, reinterpret_cast<const std::uint8_t *>(data),
|
||||
reinterpret_cast<std::uint8_t *>(dest), bc_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Try to decompress texture to 32-bit RGBA.
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of align(width, 4) * align(height,4) * 4 (bytes).
|
||||
* \param data Source data to decompress.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Size of source taken.
|
||||
*/
|
||||
static size_t decompress_compressed_swizz_texture(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const std::uint32_t width, const std::uint32_t height) {
|
||||
int bc_type = 0;
|
||||
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
bc_type = 1;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
bc_type = 2;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
bc_type = 3;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
bc_type = 4;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
bc_type = 5;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
bc_type = 6;
|
||||
break;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
bc_type = 7;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bc_type) {
|
||||
decompress_bc_swizz_image(width, height, reinterpret_cast<const std::uint8_t *>(data),
|
||||
reinterpret_cast<std::uint32_t *>(dest), bc_type);
|
||||
return (((width + 3) / 4) * ((height + 3) / 4) * ((bc_type != 1 && bc_type != 4 && bc_type != 5) ? 16 : 8));
|
||||
} else if ((fmt >= SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP) && (fmt <= SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP)) {
|
||||
pvr::PVRTDecompressPVRTC(data, (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP) || (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP), width, height,
|
||||
(fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP) || (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP), reinterpret_cast<uint8_t *>(dest));
|
||||
|
||||
const bool is_2bpp = (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP) || (fmt == SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP);
|
||||
|
||||
const std::uint32_t num_xword = (width + (is_2bpp ? 7 : 3)) / (is_2bpp ? 8 : 4);
|
||||
const std::uint32_t num_yword = (height + 3) / 4;
|
||||
|
||||
return (size_t)num_xword * (size_t)num_yword * 8;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Try to decompress texture to 16-bit RGB floating point color.
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of align(width, 4) * height * 4 (bytes).
|
||||
* \param data Source data to decompress.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Void.
|
||||
*/
|
||||
static void decompress_packed_float_e5m9m9m9(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const uint32_t width, const uint32_t height) {
|
||||
const uint32_t *in = reinterpret_cast<const uint32_t *>(data);
|
||||
uint16_t *out = reinterpret_cast<uint16_t *>(dest);
|
||||
|
||||
for (uint32_t in_offset = 0, out_offset = 0; in_offset < width * height; ++in_offset) {
|
||||
const uint32_t packed = in[in_offset];
|
||||
const uint16_t exponent = static_cast<uint16_t>(packed >> 17);
|
||||
|
||||
out[out_offset++] = exponent | ((packed & (0x1FF << 18)) >> 17);
|
||||
out[out_offset++] = exponent | ((packed & (0x1FF << 9)) >> 8);
|
||||
out[out_offset++] = exponent | ((packed & 0x1FF) << 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_x8u24_to_u24x8(void *dest, const void *data, const uint32_t width, const uint32_t height, const size_t row_length_in_pixels) {
|
||||
auto dst = static_cast<uint32_t *>(dest);
|
||||
auto src = static_cast<const uint32_t *>(data);
|
||||
|
||||
for (uint32_t row = 0; row < height; ++row) {
|
||||
for (uint32_t col = 0; col < width; ++col) {
|
||||
const uint32_t src_value = src[col];
|
||||
const uint32_t value = (src_value << 8) | (src_value >> 24);
|
||||
*dst++ = value;
|
||||
}
|
||||
|
||||
src += row_length_in_pixels;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert x8u24 (or u24x8) format to f32 (only keep the u24 part)
|
||||
// Do not use a depth-stencil format as x8d24 is not supported on all GPUs for Vulkan
|
||||
static void convert_x8u24_to_f32(void *dest, const void *data, const uint32_t width, const uint32_t height, const size_t row_length_in_pixels, const SceGxmTextureFormat format) {
|
||||
const SceGxmTextureSwizzle2ModeAlt swizzle = static_cast<SceGxmTextureSwizzle2ModeAlt>(format & SCE_GXM_TEXTURE_SWIZZLE_MASK);
|
||||
// is the depth in the upper or lower 24 bits of the data?
|
||||
int shift_amount = (swizzle == SCE_GXM_TEXTURE_SWIZZLE2_DS) ? 8 : 0;
|
||||
auto dst = static_cast<float *>(dest);
|
||||
auto src = static_cast<const uint32_t *>(data);
|
||||
|
||||
for (uint32_t row = 0; row < height; ++row) {
|
||||
for (uint32_t col = 0; col < width; ++col) {
|
||||
const uint32_t src_value = src[col];
|
||||
const uint32_t d24 = (src_value >> shift_amount) & ((1U << 24) - 1);
|
||||
*dst++ = static_cast<float>(d24) / ((1U << 24) - 1);
|
||||
}
|
||||
|
||||
src += row_length_in_pixels;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_f32m_to_f32(void *dest, const void *data, const uint32_t width, const uint32_t height, const size_t row_length_in_pixels) {
|
||||
auto dst = static_cast<uint32_t *>(dest);
|
||||
auto src = static_cast<const uint32_t *>(data);
|
||||
|
||||
for (uint32_t row = 0; row < height; ++row) {
|
||||
for (uint32_t col = 0; col < width; ++col) {
|
||||
const uint32_t src_value = src[col];
|
||||
const uint32_t value = src_value & 0x7FFFFFFF;
|
||||
*dst++ = value;
|
||||
}
|
||||
|
||||
src += row_length_in_pixels;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t f10_to_f16(const uint16_t f10) {
|
||||
// f16 has a 10 bit mantissa and a 5 bit exponent
|
||||
// f10 has a 5 bit mantissa and a 5 bit exponent
|
||||
// so we just need to put the exponent in the right location, add zeros to the mantissa
|
||||
// and it should work
|
||||
const uint16_t exponent = (f10 >> 5) & 0b11111;
|
||||
const uint16_t mantissa = f10 & 0b11111;
|
||||
const uint16_t f16 = (exponent << 10) | (mantissa << 5);
|
||||
return f16;
|
||||
}
|
||||
|
||||
static void convert_u2f10f10f10_to_f16f16f16f16(void *dest, const void *data, const uint32_t width, const uint32_t height, const size_t row_length_in_pixels, const SceGxmTextureFormat format) {
|
||||
auto dst = static_cast<std::array<uint16_t, 4> *>(dest);
|
||||
auto src = static_cast<const uint32_t *>(data);
|
||||
|
||||
// are the 2 alpha bits in the upper or lower bits of the pixel ?
|
||||
bool is_alpha_upper = (format == SCE_GXM_TEXTURE_FORMAT_U2F10F10F10_ABGR
|
||||
|| format == SCE_GXM_TEXTURE_FORMAT_U2F10F10F10_ARGB
|
||||
|| format == SCE_GXM_TEXTURE_FORMAT_X2F10F10F10_1BGR
|
||||
|| format == SCE_GXM_TEXTURE_FORMAT_X2F10F10F10_1RGB);
|
||||
|
||||
for (uint32_t row = 0; row < height; ++row) {
|
||||
for (uint32_t col = 0; col < width; ++col) {
|
||||
uint32_t src_value = src[col];
|
||||
uint16_t f1, f2, f3;
|
||||
int dst_idx;
|
||||
// first get the 2 alpha bits
|
||||
if (is_alpha_upper) {
|
||||
(*dst)[3] = (src_value >> 30) / 3.0f;
|
||||
dst_idx = 0;
|
||||
} else {
|
||||
(*dst)[0] = (src_value & 0b11) / 3.0f;
|
||||
dst_idx = 1;
|
||||
src_value >>= 2;
|
||||
}
|
||||
|
||||
// decode the 3 rgb components
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const uint16_t comp = src_value & ((1 << 10) - 1);
|
||||
src_value >>= 10;
|
||||
(*dst)[dst_idx++] = f10_to_f16(comp);
|
||||
}
|
||||
dst++;
|
||||
}
|
||||
|
||||
src += row_length_in_pixels;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Remove arbitraty blocks from block compressed texture
|
||||
*
|
||||
* \param fmt Texture base format.
|
||||
* \param dest Destination texture data. Size must be sufficient enough of ((width + 3) / 4) * ((height + 3) / 4) * 8 or 16 (bytes) depending on texture base format.
|
||||
* \param data Source data to decompress.
|
||||
* \param width Texture width.
|
||||
* \param height Texture height.
|
||||
*
|
||||
* \return Void.
|
||||
*/
|
||||
static void remove_compressed_arbitrary_blocks(SceGxmTextureBaseFormat fmt, void *dest, const void *data, const std::uint32_t width, const std::uint32_t height) {
|
||||
uint32_t w = (width + 3) / 4;
|
||||
uint32_t h = (height + 3) / 4;
|
||||
|
||||
uint32_t a_w = next_power_of_two(w);
|
||||
|
||||
std::size_t block_size;
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
block_size = 8;
|
||||
return;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
block_size = 16;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
w *= block_size;
|
||||
a_w *= block_size;
|
||||
|
||||
const std::uint8_t *src = reinterpret_cast<const std::uint8_t *>(data);
|
||||
std::uint8_t *dst = reinterpret_cast<std::uint8_t *>(dest);
|
||||
|
||||
for (std::size_t j = h; j; j--) {
|
||||
memcpy(dst, src, w);
|
||||
src += a_w;
|
||||
dst += w;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t get_upload_mip(const uint16_t true_mip, const uint16_t width, const uint16_t height, const SceGxmTextureBaseFormat base_format) {
|
||||
uint16_t max_mip_text;
|
||||
|
||||
if (is_block_compressed_format(base_format)) {
|
||||
// blocks size is 4x4, do not try to upload mips whose with or height is not a multiple of 4
|
||||
max_mip_text = std::bit_width(std::min<uint16_t>(width & (-width), height & (-height)));
|
||||
max_mip_text -= 2;
|
||||
} else {
|
||||
max_mip_text = std::bit_width(std::min(width, height));
|
||||
}
|
||||
|
||||
return std::min(true_mip, max_mip_text);
|
||||
}
|
||||
|
||||
void upload_bound_texture(const TextureCacheState &cache, const SceGxmTexture &gxm_texture, const MemState &mem) {
|
||||
R_PROFILE(__func__);
|
||||
|
||||
bool is_vulkan = (*cache.backend == Backend::Vulkan);
|
||||
|
||||
const SceGxmTextureFormat fmt = gxm::get_format(&gxm_texture);
|
||||
const SceGxmTextureBaseFormat base_format = gxm::get_base_format(fmt);
|
||||
|
||||
const bool block_compressed = renderer::texture::is_compressed_format(base_format);
|
||||
auto width = static_cast<uint32_t>(gxm::get_width(&gxm_texture));
|
||||
auto height = static_cast<uint32_t>(gxm::get_height(&gxm_texture));
|
||||
if (block_compressed) {
|
||||
// align width and height to block size
|
||||
width = (width + 3) & ~3;
|
||||
height = (height + 3) & ~3;
|
||||
}
|
||||
|
||||
const Ptr<uint8_t> data(gxm_texture.data_addr << 2);
|
||||
uint8_t *texture_data = data.get(mem);
|
||||
|
||||
if (!texture_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> texture_data_decompressed;
|
||||
std::vector<uint8_t> texture_pixels_lineared; // TODO Move to context to avoid frequent allocation?
|
||||
std::vector<uint32_t> palette_texture_pixels;
|
||||
std::vector<uint8_t> yuv_texture_pixels;
|
||||
|
||||
const void *pixels = nullptr;
|
||||
|
||||
size_t pixels_per_stride = 0;
|
||||
size_t bpp = renderer::texture::bits_per_pixel(base_format);
|
||||
size_t bytes_per_pixel = (bpp + 7) >> 3;
|
||||
|
||||
const auto texture_type = gxm_texture.texture_type();
|
||||
const bool is_swizzled = (texture_type == SCE_GXM_TEXTURE_SWIZZLED) || (texture_type == SCE_GXM_TEXTURE_CUBE) || (texture_type == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY);
|
||||
const bool need_unswizzle = is_swizzled && block_compressed;
|
||||
const bool need_decompress_and_unswizzle_on_cpu = is_swizzled && !block_compressed && !can_texture_be_unswizzled_without_decode(base_format, is_vulkan);
|
||||
|
||||
uint32_t mip_index = 0;
|
||||
uint32_t total_mip = get_upload_mip(gxm_texture.true_mip_count(), width, height, base_format);
|
||||
uint32_t face_uploaded_count = 0;
|
||||
uint32_t face_total_count;
|
||||
size_t source_size = 0;
|
||||
size_t total_source_so_far = 0;
|
||||
|
||||
// Modified during decompression
|
||||
uint32_t org_width = width;
|
||||
uint32_t org_height = height;
|
||||
|
||||
uint32_t org_width_const = width;
|
||||
uint32_t org_height_const = height;
|
||||
|
||||
uint32_t face_align_bytes = 4;
|
||||
|
||||
if (texture_type == SCE_GXM_TEXTURE_LINEAR_STRIDED) {
|
||||
total_mip = 1;
|
||||
}
|
||||
|
||||
// > 0 means texture cube
|
||||
int upload_type = 0;
|
||||
|
||||
face_total_count = 1;
|
||||
if ((texture_type == SCE_GXM_TEXTURE_CUBE) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY)) {
|
||||
upload_type = 1;
|
||||
face_total_count = 6;
|
||||
|
||||
const bool twok_align_cond1 = ((width >= 32) && (height >= 32) && ((bytes_per_pixel == 1) || (is_block_compressed_format(base_format))));
|
||||
const bool twok_align_cond2 = ((width >= 16) && (height >= 16) && ((bytes_per_pixel == 2) || (bytes_per_pixel == 4)));
|
||||
const bool twok_align_cond3 = ((width >= 8) && (height >= 8) && (bytes_per_pixel == 8));
|
||||
|
||||
if (twok_align_cond1 || twok_align_cond2 || twok_align_cond3) {
|
||||
face_align_bytes = 2048;
|
||||
}
|
||||
}
|
||||
|
||||
while ((face_uploaded_count < face_total_count) && org_width && org_height) {
|
||||
width = org_width;
|
||||
height = org_height;
|
||||
pixels = texture_data;
|
||||
|
||||
SceGxmTextureBaseFormat upload_format = base_format;
|
||||
|
||||
// Get pixels per stride
|
||||
switch (texture_type) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY:
|
||||
case SCE_GXM_TEXTURE_CUBE_ARBITRARY:
|
||||
width = next_power_of_two(width);
|
||||
height = next_power_of_two(height);
|
||||
case SCE_GXM_TEXTURE_SWIZZLED:
|
||||
case SCE_GXM_TEXTURE_CUBE:
|
||||
case SCE_GXM_TEXTURE_TILED:
|
||||
pixels_per_stride = static_cast<size_t>(width);
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_LINEAR:
|
||||
pixels_per_stride = static_cast<size_t>((width + 7) & ~7);
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_LINEAR_STRIDED:
|
||||
pixels_per_stride = gxm::get_stride_in_bytes(&gxm_texture) / bytes_per_pixel;
|
||||
if (base_format == SCE_GXM_TEXTURE_BASE_FORMAT_P4) // P4 textures are the only one not byte aligned, therefore bytes_per_pixel should be 0.5 and not 1, correct it here
|
||||
pixels_per_stride *= 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (gxm::is_paletted_format(base_format)) {
|
||||
palette_texture_pixels.resize(width * height * 4);
|
||||
if (base_format == SCE_GXM_TEXTURE_BASE_FORMAT_P8) {
|
||||
renderer::texture::palette_texture_to_rgba_8(palette_texture_pixels.data(),
|
||||
reinterpret_cast<const uint8_t *>(pixels), width, height, pixels_per_stride, renderer::texture::get_texture_palette(gxm_texture, mem));
|
||||
} else {
|
||||
renderer::texture::palette_texture_to_rgba_4(reinterpret_cast<uint32_t *>(palette_texture_pixels.data()),
|
||||
reinterpret_cast<const uint8_t *>(pixels), width, height, pixels_per_stride / 2, renderer::texture::get_texture_palette(gxm_texture, mem));
|
||||
}
|
||||
pixels = palette_texture_pixels.data();
|
||||
bytes_per_pixel = 4;
|
||||
bpp = 32;
|
||||
upload_format = SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8;
|
||||
}
|
||||
|
||||
if (need_unswizzle) {
|
||||
// Must unswizzle them
|
||||
texture_data_decompressed.resize(renderer::texture::get_compressed_size(base_format, width, height));
|
||||
resolve_z_order_compressed_texture(base_format, texture_data_decompressed.data(), pixels, width, height);
|
||||
pixels = texture_data_decompressed.data();
|
||||
} else if (need_decompress_and_unswizzle_on_cpu) {
|
||||
// Must decompress them
|
||||
texture_data_decompressed.resize(align(width, 4) * align(height, 4) * 4);
|
||||
source_size = decompress_compressed_swizz_texture(base_format, texture_data_decompressed.data(), pixels, width, height);
|
||||
bytes_per_pixel = 4;
|
||||
bpp = 32;
|
||||
upload_format = SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8;
|
||||
pixels = texture_data_decompressed.data();
|
||||
}
|
||||
|
||||
switch (base_format) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT4BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP:
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9:
|
||||
// this format is supported on all GPUs with vulkan
|
||||
if (is_vulkan)
|
||||
break;
|
||||
texture_data_decompressed.resize(width * height * 6);
|
||||
decompress_packed_float_e5m9m9m9(base_format, texture_data_decompressed.data(), pixels, width, height);
|
||||
pixels = texture_data_decompressed.data();
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U2F10F10F10:
|
||||
// don't change what openGL is doing (which is completely wrong)
|
||||
if (!is_vulkan)
|
||||
break;
|
||||
texture_data_decompressed.resize(width * height * 8);
|
||||
convert_u2f10f10f10_to_f16f16f16f16(texture_data_decompressed.data(), pixels, width, height, pixels_per_stride, fmt);
|
||||
pixels = texture_data_decompressed.data();
|
||||
upload_format = SCE_GXM_TEXTURE_BASE_FORMAT_F16F16F16F16;
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_X8U24:
|
||||
texture_data_decompressed.resize(width * height * 4);
|
||||
if (is_vulkan) {
|
||||
// d24_u8 or x8_d24 is not supported on all GPUs (thanks AMD)
|
||||
convert_x8u24_to_f32(texture_data_decompressed.data(), pixels, width, height, pixels_per_stride, fmt);
|
||||
upload_format = SCE_GXM_TEXTURE_BASE_FORMAT_F32;
|
||||
} else {
|
||||
// X8 = [24-31], D24 = [0-23], technically this is GL_UNSIGNED_INT_24_8_REV which does not exist
|
||||
// TODO: Requires shader to convert the normalized value read by GL to unsigned int. Just multiply by 2^24-1 when reading and you're done.
|
||||
// TODO: this is wrong, the depth is in the upper or lower 24 bits according to the swizzle
|
||||
convert_x8u24_to_u24x8(texture_data_decompressed.data(), pixels, width, height, pixels_per_stride);
|
||||
}
|
||||
pixels = texture_data_decompressed.data();
|
||||
pixels_per_stride = width;
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32M:
|
||||
// Convert F32M to F32
|
||||
texture_data_decompressed.resize(width * height * 4);
|
||||
convert_f32m_to_f32(texture_data_decompressed.data(), pixels, width, height, pixels_per_stride);
|
||||
pixels = texture_data_decompressed.data();
|
||||
pixels_per_stride = width;
|
||||
upload_format = SCE_GXM_TEXTURE_BASE_FORMAT_F32;
|
||||
break;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC5:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SBC5:
|
||||
source_size = renderer::texture::get_compressed_size(base_format, width, height);
|
||||
|
||||
if ((texture_type == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY)) {
|
||||
size_t compressed_size = renderer::texture::get_compressed_size(base_format, org_width, org_height);
|
||||
|
||||
texture_pixels_lineared.resize(compressed_size);
|
||||
remove_compressed_arbitrary_blocks(base_format, texture_pixels_lineared.data(), pixels, org_width, org_height);
|
||||
|
||||
pixels = texture_pixels_lineared.data();
|
||||
pixels_per_stride = org_width;
|
||||
|
||||
if (need_unswizzle)
|
||||
texture_data_decompressed.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (texture_type == SCE_GXM_TEXTURE_LINEAR || texture_type == SCE_GXM_TEXTURE_LINEAR_STRIDED)
|
||||
break;
|
||||
// Convert data to linear layout
|
||||
texture_pixels_lineared.resize(width * height * bytes_per_pixel);
|
||||
|
||||
if (is_swizzled)
|
||||
renderer::texture::swizzled_texture_to_linear_texture(texture_pixels_lineared.data(), reinterpret_cast<const uint8_t *>(pixels), width, height,
|
||||
static_cast<std::uint8_t>(bpp));
|
||||
else
|
||||
renderer::texture::tiled_texture_to_linear_texture(texture_pixels_lineared.data(), reinterpret_cast<const uint8_t *>(pixels), width, height,
|
||||
static_cast<std::uint8_t>(bpp));
|
||||
|
||||
pixels = texture_pixels_lineared.data();
|
||||
|
||||
if (need_decompress_and_unswizzle_on_cpu)
|
||||
texture_data_decompressed.clear();
|
||||
}
|
||||
|
||||
if ((texture_type == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY)) {
|
||||
width = org_width;
|
||||
height = org_height;
|
||||
}
|
||||
|
||||
if (gxm::is_paletted_format(base_format)) {
|
||||
pixels_per_stride = width;
|
||||
}
|
||||
|
||||
if (gxm::is_yuv_format(base_format)) {
|
||||
switch (fmt) {
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P2_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P2_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P2_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P2_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P3_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P3_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUV420P3_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVU420P3_CSC1: {
|
||||
yuv_texture_pixels.resize(width * height * 3);
|
||||
renderer::texture::yuv420_texture_to_rgb(yuv_texture_pixels.data(),
|
||||
reinterpret_cast<const uint8_t *>(pixels), width, height);
|
||||
pixels = yuv_texture_pixels.data();
|
||||
pixels_per_stride = width;
|
||||
bpp = 24;
|
||||
upload_format = SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8;
|
||||
break;
|
||||
}
|
||||
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUYV422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVYU422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_UYVY422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_VYUY422_CSC0:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YUYV422_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_YVYU422_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_UYVY422_CSC1:
|
||||
case SCE_GXM_TEXTURE_FORMAT_VYUY422_CSC1:
|
||||
LOG_ERROR("Yuv Texture format not implemented: {}", fmt);
|
||||
assert(false);
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!block_compressed && !need_decompress_and_unswizzle_on_cpu) {
|
||||
source_size = (pixels_per_stride * height * ((bpp + 7) >> 3));
|
||||
}
|
||||
|
||||
cache.upload_texture_callback(upload_format, width, height, mip_index, pixels, upload_type, block_compressed, pixels_per_stride);
|
||||
|
||||
mip_index++;
|
||||
org_width /= 2;
|
||||
org_height /= 2;
|
||||
|
||||
texture_data += source_size;
|
||||
total_source_so_far += source_size;
|
||||
|
||||
if (mip_index == total_mip) {
|
||||
mip_index = 0;
|
||||
face_uploaded_count++;
|
||||
|
||||
org_width = org_width_const;
|
||||
org_height = org_height_const;
|
||||
|
||||
upload_type++;
|
||||
|
||||
size_t source_unaligned_size = total_source_so_far;
|
||||
total_source_so_far = align(total_source_so_far, face_align_bytes);
|
||||
|
||||
texture_data += total_source_so_far - source_unaligned_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cache_and_bind_texture(TextureCacheState &cache, const SceGxmTexture &gxm_texture, MemState &mem) {
|
||||
R_PROFILE(__func__);
|
||||
|
||||
@@ -168,10 +795,10 @@ void cache_and_bind_texture(TextureCacheState &cache, const SceGxmTexture &gxm_t
|
||||
cache.select_callback(index, &gxm_texture);
|
||||
|
||||
if (configure) {
|
||||
cache.configure_texture_callback(cache, index, &gxm_texture);
|
||||
cache.configure_texture_callback(cache, &gxm_texture);
|
||||
}
|
||||
if (upload) {
|
||||
cache.upload_texture_callback(index, &gxm_texture, mem);
|
||||
upload_bound_texture(cache, gxm_texture, mem);
|
||||
if (!info->use_hash) {
|
||||
info->dirty = false;
|
||||
add_protect(mem, range_protect_begin, range_protect_end - range_protect_begin, MEM_PERM_READONLY, [&cache, info, gxm_texture](Address, bool) {
|
||||
|
||||
@@ -22,10 +22,27 @@
|
||||
|
||||
#include <gxm/functions.h>
|
||||
#include <gxm/types.h>
|
||||
#include <shader/spirv_recompiler.h>
|
||||
#include <util/log.h>
|
||||
|
||||
namespace renderer::texture {
|
||||
|
||||
float get_integral_query_format(const SceGxmTextureBaseFormat format) {
|
||||
if ((format == SCE_GXM_TEXTURE_BASE_FORMAT_S8) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S8S8) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8S8)) {
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_8BIT_SIGNED;
|
||||
}
|
||||
|
||||
if ((format == SCE_GXM_TEXTURE_BASE_FORMAT_U16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_U16U16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_U16U16U16U16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S16S16) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S16S16S16S16)) {
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_16BIT;
|
||||
}
|
||||
|
||||
if ((format == SCE_GXM_TEXTURE_BASE_FORMAT_U32) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_U32U32) || (format == SCE_GXM_TEXTURE_BASE_FORMAT_S32)) {
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_32BIT;
|
||||
}
|
||||
|
||||
return shader::INTEGRAL_TEX_QUERY_TYPE_8BIT_UNSIGNED;
|
||||
}
|
||||
|
||||
size_t bits_per_pixel(SceGxmTextureBaseFormat base_format) {
|
||||
switch (base_format) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8:
|
||||
@@ -117,6 +134,44 @@ size_t texture_size(const SceGxmTexture &texture) {
|
||||
return size;
|
||||
}
|
||||
|
||||
bool convert_base_texture_format_to_base_color_format(SceGxmTextureBaseFormat format, SceGxmColorBaseFormat &color_format) {
|
||||
static const std::map<std::uint32_t, std::uint32_t> TEXTURE_TO_COLOR_FORMAT_MAPPING = {
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8, SCE_GXM_COLOR_BASE_FORMAT_U8U8U8U8 },
|
||||
// { SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8, SCE_GXM_COLOR_BASE_FORMAT_U8U8U8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U5U6U5, SCE_GXM_COLOR_BASE_FORMAT_U5U6U5 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U1U5U5U5, SCE_GXM_COLOR_BASE_FORMAT_U1U5U5U5 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U4U4U4U4, SCE_GXM_COLOR_BASE_FORMAT_U4U4U4U4 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8U3U3U2, SCE_GXM_COLOR_BASE_FORMAT_U8U3U3U2 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F16, SCE_GXM_COLOR_BASE_FORMAT_F16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F16F16, SCE_GXM_COLOR_BASE_FORMAT_F16F16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F32, SCE_GXM_COLOR_BASE_FORMAT_F32 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S16, SCE_GXM_COLOR_BASE_FORMAT_S16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S16S16, SCE_GXM_COLOR_BASE_FORMAT_S16S16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U16, SCE_GXM_COLOR_BASE_FORMAT_U16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U16U16, SCE_GXM_COLOR_BASE_FORMAT_U16U16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U2U10U10U10, SCE_GXM_COLOR_BASE_FORMAT_U2U10U10U10 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8, SCE_GXM_COLOR_BASE_FORMAT_U8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S8, SCE_GXM_COLOR_BASE_FORMAT_S8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S5S5U6, SCE_GXM_COLOR_BASE_FORMAT_S5S5U6 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U8U8, SCE_GXM_COLOR_BASE_FORMAT_U8U8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S8S8, SCE_GXM_COLOR_BASE_FORMAT_S8S8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8S8, SCE_GXM_COLOR_BASE_FORMAT_S8S8S8S8 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F16F16F16F16, SCE_GXM_COLOR_BASE_FORMAT_F16F16F16F16 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F32F32, SCE_GXM_COLOR_BASE_FORMAT_F32F32 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_F11F11F10, SCE_GXM_COLOR_BASE_FORMAT_F11F11F10 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9, SCE_GXM_COLOR_BASE_FORMAT_SE5M9M9M9 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U2F10F10F10, SCE_GXM_COLOR_BASE_FORMAT_U2F10F10F10 },
|
||||
{ SCE_GXM_TEXTURE_BASE_FORMAT_U32U32, SCE_GXM_COLOR_BASE_FORMAT_F32F32 }
|
||||
};
|
||||
|
||||
auto ite = TEXTURE_TO_COLOR_FORMAT_MAPPING.find(format);
|
||||
if (ite == TEXTURE_TO_COLOR_FORMAT_MAPPING.end())
|
||||
return false;
|
||||
|
||||
color_format = static_cast<SceGxmColorBaseFormat>(ite->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Based on this: http://xen.firefly.nu/up/rearrange.c.html
|
||||
// Thanks daniel from GXTConvert finding this out first
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Build allocator implementations.
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <renderer/vulkan/types.h>
|
||||
|
||||
// Provide storage for the dynamic loader
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/vulkan/types.h>
|
||||
|
||||
#include <renderer/vulkan/functions.h>
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
#include <renderer/vulkan/state.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
|
||||
#include <util/log.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
void set_context(VKContext &context, const MemState &mem, VKRenderTarget *rt, const FeatureState &features) {
|
||||
if (rt) {
|
||||
context.render_target = rt;
|
||||
} else {
|
||||
// TODO: make context.current_render_target non-const instead of doing this
|
||||
context.render_target = const_cast<VKRenderTarget *>(reinterpret_cast<const VKRenderTarget *>(context.current_render_target));
|
||||
}
|
||||
|
||||
context.scene_timestamp++;
|
||||
|
||||
SceGxmColorSurface *color_surface_fin = &context.record.color_surface;
|
||||
// set these values for the pipeline cache
|
||||
context.record.color_base_format = gxm::get_base_format(color_surface_fin->colorFormat);
|
||||
context.record.width = context.render_target->width;
|
||||
context.record.height = context.render_target->height;
|
||||
vk::Format vk_format = color::translate_format(context.record.color_base_format);
|
||||
|
||||
if (color_surface_fin->data.address() == 0) {
|
||||
color_surface_fin = nullptr;
|
||||
vk_format = vk::Format::eR8G8B8A8Unorm;
|
||||
}
|
||||
|
||||
SceGxmDepthStencilSurface *ds_surface_fin = &context.record.depth_stencil_surface;
|
||||
if ((ds_surface_fin->depthData.address() == 0) && (ds_surface_fin->stencilData.address() == 0)) {
|
||||
ds_surface_fin = nullptr;
|
||||
}
|
||||
|
||||
VKState &state = context.state;
|
||||
state.surface_cache.set_render_target(rt);
|
||||
|
||||
context.start_recording();
|
||||
|
||||
context.current_render_pass = context.state.pipeline_cache.retrieve_render_pass(vk_format, context.record.depth_stencil_surface.zlsControl);
|
||||
|
||||
context.current_framebuffer = state.surface_cache.retrieve_framebuffer_handle(
|
||||
mem, color_surface_fin, ds_surface_fin, context.current_render_pass, &context.current_color_attachment, &context.current_ds_attachment, &context.current_framebuffer_height);
|
||||
|
||||
if (state.features.use_mask_bit)
|
||||
sync_mask(context, mem);
|
||||
|
||||
context.start_render_pass();
|
||||
}
|
||||
|
||||
void VKContext::start_recording() {
|
||||
if (is_recording) {
|
||||
LOG_ERROR("Attempt to start recording while already recording");
|
||||
return;
|
||||
}
|
||||
|
||||
if (render_target == nullptr) {
|
||||
LOG_ERROR("Recording started without a set command buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
if (render_target->last_used_frame != frame_timestamp) {
|
||||
// reset idx if we are in a new frame
|
||||
render_target->cmd_buffer_idx = 0;
|
||||
render_target->last_used_frame = frame_timestamp;
|
||||
}
|
||||
|
||||
// safety check
|
||||
if (render_target->cmd_buffer_idx == render_target->cmd_buffers[current_frame_idx].size()) {
|
||||
static bool has_happened = false;
|
||||
LOG_WARN_IF(!has_happened, "Render Target is using more scenes per frame than what was planned!");
|
||||
has_happened = true;
|
||||
|
||||
// add additional cmd buffers, fences and semaphores
|
||||
vk::CommandBufferAllocateInfo cmd_buffer_info{
|
||||
.commandPool = frame().command_pool,
|
||||
.commandBufferCount = 2
|
||||
};
|
||||
for (vk::CommandBuffer cmd_buffer : state.device.allocateCommandBuffers(cmd_buffer_info))
|
||||
render_target->cmd_buffers[current_frame_idx].push_back(cmd_buffer);
|
||||
|
||||
vk::FenceCreateInfo fence_info{};
|
||||
// make sure the next fence used is the one we created
|
||||
render_target->fences.insert(render_target->fences.begin() + render_target->fence_idx, state.device.createFence(fence_info));
|
||||
}
|
||||
|
||||
render_cmd = render_target->cmd_buffers[current_frame_idx][render_target->cmd_buffer_idx++];
|
||||
prerender_cmd = render_target->cmd_buffers[current_frame_idx][render_target->cmd_buffer_idx++];
|
||||
|
||||
vk::CommandBufferBeginInfo begin_info{
|
||||
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
|
||||
};
|
||||
render_cmd.begin(begin_info);
|
||||
prerender_cmd.begin(begin_info);
|
||||
|
||||
is_recording = true;
|
||||
|
||||
// set all the dynamic state here
|
||||
render_cmd.setViewport(0, viewport);
|
||||
render_cmd.setScissor(0, scissor);
|
||||
sync_depth_bias(*this);
|
||||
sync_point_line_width(*this, true);
|
||||
sync_stencil_func(*this, false);
|
||||
if (record.two_sided == SCE_GXM_TWO_SIDED_ENABLED) {
|
||||
sync_stencil_func(*this, true);
|
||||
}
|
||||
}
|
||||
|
||||
void VKContext::start_render_pass() {
|
||||
if (in_renderpass) {
|
||||
LOG_ERROR("Starting render pass while already in render pass");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_recording)
|
||||
start_recording();
|
||||
|
||||
vk::RenderPassBeginInfo pass_info{
|
||||
.renderPass = current_render_pass,
|
||||
.framebuffer = current_framebuffer,
|
||||
.renderArea = {
|
||||
.offset = { 0, 0 },
|
||||
.extent = { render_target->width, render_target->height } }
|
||||
};
|
||||
std::array<vk::ClearValue, 2> clear_values{};
|
||||
// only the depth-stencil attachment may be clear if not force loaded
|
||||
clear_values[1].depthStencil = vk::ClearDepthStencilValue{
|
||||
.depth = record.depth_stencil_surface.backgroundDepth,
|
||||
.stencil = record.depth_stencil_surface.control.content & SceGxmDepthStencilControl::stencil_bits
|
||||
};
|
||||
pass_info.setClearValues(clear_values);
|
||||
render_cmd.beginRenderPass(pass_info, vk::SubpassContents::eInline);
|
||||
|
||||
// create and update the rendertarget descriptor set
|
||||
vk::DescriptorSetAllocateInfo descr_set_info{
|
||||
.descriptorPool = frame().descriptor_pool
|
||||
};
|
||||
descr_set_info.setSetLayouts(state.pipeline_cache.attachments_layout);
|
||||
rendertarget_set = state.device.allocateDescriptorSets(descr_set_info)[0];
|
||||
|
||||
// creat descriptor set for the whole scene with the mask and the color attachment
|
||||
// the mask will only be used if state.features.use_mask_bit is true
|
||||
vk::DescriptorImageInfo descr_mask_info{
|
||||
.sampler = nullptr,
|
||||
.imageView = render_target->mask.view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
vk::DescriptorImageInfo descr_color_info{
|
||||
.sampler = nullptr,
|
||||
.imageView = current_color_attachment->view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> write_descr;
|
||||
|
||||
write_descr[0] = {
|
||||
.dstSet = rendertarget_set,
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = vk::DescriptorType::eInputAttachment,
|
||||
};
|
||||
write_descr[0].setImageInfo(descr_color_info);
|
||||
write_descr[1] = {
|
||||
.dstSet = rendertarget_set,
|
||||
.dstBinding = 1,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
};
|
||||
write_descr[1].setImageInfo(descr_mask_info);
|
||||
state.device.updateDescriptorSets(state.features.use_mask_bit ? 2 : 1, write_descr.data(), 0, nullptr);
|
||||
|
||||
refresh_pipeline = true;
|
||||
current_pipeline = nullptr;
|
||||
in_renderpass = true;
|
||||
}
|
||||
|
||||
void VKContext::stop_render_pass() {
|
||||
if (!in_renderpass) {
|
||||
LOG_ERROR("Stopping render pass while not in render pass");
|
||||
return;
|
||||
}
|
||||
render_cmd.endRenderPass();
|
||||
|
||||
in_renderpass = false;
|
||||
}
|
||||
|
||||
void VKContext::stop_recording() {
|
||||
if (!is_recording) {
|
||||
LOG_ERROR("Stopping recording while not recording");
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_renderpass)
|
||||
stop_render_pass();
|
||||
render_cmd.end();
|
||||
prerender_cmd.end();
|
||||
|
||||
vk::Fence fence = render_target->fences[render_target->fence_idx];
|
||||
render_target->fence_idx++;
|
||||
if (render_target->fence_idx == render_target->fences.size())
|
||||
render_target->fence_idx = 0;
|
||||
|
||||
vk::SubmitInfo submit_info{};
|
||||
// the prerender cmd must be submitted before the render cmd, the pipeline barriers do the rest
|
||||
std::array<vk::CommandBuffer, 2> cmd_buffers = { prerender_cmd, render_cmd };
|
||||
submit_info.setCommandBuffers(cmd_buffers);
|
||||
|
||||
state.general_queue.submit(submit_info, fence);
|
||||
frame().rendered_fences.push_back(fence);
|
||||
|
||||
render_cmd = nullptr;
|
||||
prerender_cmd = nullptr;
|
||||
is_recording = false;
|
||||
}
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,329 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/functions.h>
|
||||
|
||||
#include <xxh3.h>
|
||||
|
||||
#include <renderer/types.h>
|
||||
#include <renderer/vulkan/functions.h>
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
#include <renderer/vulkan/state.h>
|
||||
|
||||
#include <gxm/types.h>
|
||||
|
||||
#include <util/log.h>
|
||||
#include <vkutil/vkutil.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
VKContext::VKContext(VKState &state)
|
||||
: state(state)
|
||||
, vertex_stream_ring_buffer(state.allocator, vk::BufferUsageFlagBits::eVertexBuffer, MiB(/*128*/ 64))
|
||||
, index_stream_ring_buffer(state.allocator, vk::BufferUsageFlagBits::eIndexBuffer, MiB(64))
|
||||
, vertex_uniform_stream_ring_buffer(state.allocator, vk::BufferUsageFlagBits::eStorageBuffer, MiB(/*256*/ 64))
|
||||
, fragment_uniform_stream_ring_buffer(state.allocator, vk::BufferUsageFlagBits::eStorageBuffer, MiB(/*256*/ 64))
|
||||
, vertex_info_uniform_buffer(state.allocator, vk::BufferUsageFlagBits::eUniformBuffer, MiB(8))
|
||||
, fragment_info_uniform_buffer(state.allocator, vk::BufferUsageFlagBits::eUniformBuffer, MiB(8))
|
||||
, default_image(state.allocator, 1, 1, vk::Format::eR8G8B8A8Unorm) {
|
||||
memset(&previous_vert_info, 0, sizeof(shader::RenderVertUniformBlock));
|
||||
memset(&previous_frag_info, 0, sizeof(shader::RenderFragUniformBlock));
|
||||
|
||||
// default values for the viewport and scissors
|
||||
viewport = vk::Viewport{
|
||||
.x = 0.f,
|
||||
.y = 0.f,
|
||||
.width = 960.f * state.res_multiplier,
|
||||
.height = 544.f * state.res_multiplier,
|
||||
.minDepth = 0.f,
|
||||
.maxDepth = 1.f
|
||||
};
|
||||
scissor = vk::Rect2D{
|
||||
.offset = { 0, 0 },
|
||||
.extent = { 960U * state.res_multiplier, 544U * state.res_multiplier }
|
||||
};
|
||||
|
||||
// allocate descriptor pools
|
||||
{
|
||||
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {
|
||||
vk::DescriptorPoolSize{ vk::DescriptorType::eUniformBufferDynamic, 2 },
|
||||
vk::DescriptorPoolSize{ vk::DescriptorType::eStorageBufferDynamic, 2 },
|
||||
};
|
||||
|
||||
vk::DescriptorPoolCreateInfo descriptor_pool_info{
|
||||
.maxSets = 1,
|
||||
.poolSizeCount = static_cast<uint32_t>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data()
|
||||
};
|
||||
|
||||
global_descriptor_pool = state.device.createDescriptorPool(descriptor_pool_info);
|
||||
|
||||
// we can also create the descriptor set now
|
||||
vk::DescriptorSetAllocateInfo descr_set_info{
|
||||
.descriptorPool = global_descriptor_pool
|
||||
};
|
||||
descr_set_info.setSetLayouts(state.pipeline_cache.uniforms_layout);
|
||||
global_set = state.device.allocateDescriptorSets(descr_set_info)[0];
|
||||
|
||||
// update it now (will not be updated after)
|
||||
std::array<vk::DescriptorBufferInfo, 4> buffers_info = {
|
||||
vk::DescriptorBufferInfo{
|
||||
.buffer = vertex_uniform_stream_ring_buffer.handle(),
|
||||
// TODO: get max range of buffer
|
||||
.range = KB(500) },
|
||||
vk::DescriptorBufferInfo{
|
||||
.buffer = fragment_uniform_stream_ring_buffer.handle(),
|
||||
// TODO: get max range of buffer
|
||||
.range = KB(500) },
|
||||
vk::DescriptorBufferInfo{
|
||||
.buffer = vertex_info_uniform_buffer.handle(),
|
||||
.range = sizeof(shader::RenderVertUniformBlock) },
|
||||
vk::DescriptorBufferInfo{
|
||||
.buffer = fragment_info_uniform_buffer.handle(),
|
||||
.range = sizeof(shader::RenderVertUniformBlock) },
|
||||
};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 4> write_descr;
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
write_descr[i] = vk::WriteDescriptorSet{
|
||||
.dstSet = global_set,
|
||||
.dstBinding = i,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = i < 2 ? vk::DescriptorType::eStorageBufferDynamic : vk::DescriptorType::eUniformBufferDynamic
|
||||
};
|
||||
write_descr[i].setBufferInfo(buffers_info[i]);
|
||||
}
|
||||
|
||||
state.device.updateDescriptorSets(write_descr, nullptr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_FRAMES_RENDERING; i++) {
|
||||
FrameObject &frame = frames[i];
|
||||
|
||||
vk::CommandPoolCreateInfo pool_info{
|
||||
.queueFamilyIndex = state.general_family_index
|
||||
};
|
||||
|
||||
frame.command_pool = state.device.createCommandPool(pool_info);
|
||||
|
||||
std::array<vk::DescriptorPoolSize, 3> pool_sizes = {
|
||||
vk::DescriptorPoolSize{ vk::DescriptorType::eStorageImage, 256 },
|
||||
vk::DescriptorPoolSize{ vk::DescriptorType::eInputAttachment, 256 },
|
||||
vk::DescriptorPoolSize{ vk::DescriptorType::eCombinedImageSampler, 8192 },
|
||||
};
|
||||
|
||||
vk::DescriptorPoolCreateInfo descriptor_pool_info{
|
||||
.maxSets = 4096
|
||||
};
|
||||
descriptor_pool_info.setPoolSizes(pool_sizes);
|
||||
|
||||
frame.descriptor_pool = state.device.createDescriptorPool(descriptor_pool_info);
|
||||
|
||||
frame.destroy_queue.init(state.device, state.allocator);
|
||||
}
|
||||
|
||||
{
|
||||
// create the default image
|
||||
default_image.init_image(vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst);
|
||||
vk::CommandBuffer cmd_buffer = vkutil::create_single_time_command(state.device, state.general_command_pool);
|
||||
default_image.transition_to(cmd_buffer, vkutil::ImageLayout::TransferDst);
|
||||
// make it white
|
||||
vk::ClearColorValue white{
|
||||
.float32 = std::array<float, 4>{ 1.0f, 1.0f, 1.0f, 1.0f }
|
||||
};
|
||||
cmd_buffer.clearColorImage(default_image.image, vk::ImageLayout::eTransferDstOptimal, white, vkutil::color_subresource_range);
|
||||
default_image.transition_to(cmd_buffer, vkutil::ImageLayout::SampledImage);
|
||||
vkutil::end_single_time_command(state.device, state.general_queue, state.general_command_pool, cmd_buffer);
|
||||
|
||||
// create the default sampler
|
||||
vk::SamplerCreateInfo sampler_info{
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.mipmapMode = vk::SamplerMipmapMode::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeV = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeW = vk::SamplerAddressMode::eRepeat,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = 0.0f,
|
||||
};
|
||||
default_image.sampler = state.device.createSampler(sampler_info);
|
||||
}
|
||||
}
|
||||
|
||||
VKRenderTarget::VKRenderTarget(VKState &state, vma::Allocator allocator, uint16_t width, uint16_t height, uint16_t samples_per_frame)
|
||||
: color(allocator, width * state.res_multiplier, height * state.res_multiplier, vk::Format::eR8G8B8A8Unorm)
|
||||
, depthstencil(allocator, width * state.res_multiplier, height * state.res_multiplier, vk::Format::eD32SfloatS8Uint)
|
||||
, mask(allocator, width, height, vk::Format::eR8G8B8A8Unorm) {
|
||||
this->width = width * state.res_multiplier;
|
||||
this->height = height * state.res_multiplier;
|
||||
|
||||
if (state.features.use_mask_bit)
|
||||
mask.init_image(vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eStorage);
|
||||
|
||||
color.init_image(vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eInputAttachment);
|
||||
depthstencil.init_image(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc);
|
||||
|
||||
// transition images to their right state (not needed for the mask)
|
||||
vk::CommandBuffer cmd_buffer = vkutil::create_single_time_command(state.device, state.general_command_pool);
|
||||
// color
|
||||
{
|
||||
color.transition_to(cmd_buffer, vkutil::ImageLayout::TransferDst);
|
||||
|
||||
vk::ClearColorValue clear_color{ std::array<float, 4>({ 0.968627450f, 0.776470588f, 0.0f, 0.0f }) };
|
||||
cmd_buffer.clearColorImage(color.image, vk::ImageLayout::eTransferDstOptimal, clear_color, vkutil::color_subresource_range);
|
||||
color.transition_to(cmd_buffer, vkutil::ImageLayout::ColorAttachmentReadWrite);
|
||||
}
|
||||
// depth stencil
|
||||
{
|
||||
depthstencil.transition_to(cmd_buffer, vkutil::ImageLayout::TransferDst, vkutil::ds_subresource_range);
|
||||
vk::ClearDepthStencilValue clear_value{
|
||||
.depth = 1.0,
|
||||
.stencil = 0
|
||||
};
|
||||
cmd_buffer.clearDepthStencilImage(depthstencil.image, vk::ImageLayout::eTransferDstOptimal, clear_value, vkutil::ds_subresource_range);
|
||||
depthstencil.transition_to(cmd_buffer, vkutil::ImageLayout::DepthStencilAttachment, vkutil::ds_subresource_range);
|
||||
}
|
||||
vkutil::end_single_time_command(state.device, state.general_queue, state.general_command_pool, cmd_buffer);
|
||||
|
||||
constexpr uint16_t SCE_GXM_MAX_SCENES_PER_RENDERTARGET = 8;
|
||||
// hopefully this will always be enough
|
||||
samples_per_frame = std::min<uint16_t>(samples_per_frame + 2, SCE_GXM_MAX_SCENES_PER_RENDERTARGET);
|
||||
|
||||
// the maximum number of fence we will ever need simultaneously is samples_per_frame * MAX_FRAMES_RENDERING
|
||||
fences.resize(samples_per_frame * MAX_FRAMES_RENDERING);
|
||||
vk::FenceCreateInfo fence_info{};
|
||||
for (int i = 0; i < fences.size(); i++)
|
||||
fences[i] = state.device.createFence(fence_info);
|
||||
|
||||
vk::SemaphoreCreateInfo semaphore_info{};
|
||||
for (int i = 0; i < MAX_FRAMES_RENDERING; i++) {
|
||||
vk::CommandBufferAllocateInfo buffer_info{
|
||||
.commandPool = reinterpret_cast<VKContext *>(state.context)->frames[i].command_pool,
|
||||
// need to account for both the render cmd and the prerender cmd
|
||||
.commandBufferCount = static_cast<uint32_t>(samples_per_frame * 2)
|
||||
};
|
||||
cmd_buffers[i] = state.device.allocateCommandBuffers(buffer_info);
|
||||
}
|
||||
}
|
||||
|
||||
bool create(VKState &state, std::unique_ptr<Context> &context) {
|
||||
context = std::make_unique<VKContext>(state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create(VKState &state, std::unique_ptr<RenderTarget> &rt, const SceGxmRenderTargetParams ¶ms, const FeatureState &features) {
|
||||
rt = std::make_unique<VKRenderTarget>(state, state.allocator, params.width, params.height, params.scenesPerFrame);
|
||||
|
||||
if (state.features.use_mask_bit) {
|
||||
vkutil::Image &mask = reinterpret_cast<VKRenderTarget *>(rt.get())->mask;
|
||||
|
||||
// transition it to general
|
||||
vk::CommandBuffer cmd_buffer = vkutil::create_single_time_command(state.device, state.general_command_pool);
|
||||
mask.transition_to(cmd_buffer, vkutil::ImageLayout::StorageImage);
|
||||
vkutil::end_single_time_command(state.device, state.general_queue, state.general_command_pool, cmd_buffer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy(VKState &state, std::unique_ptr<RenderTarget> &rt) {
|
||||
VKContext &context = *reinterpret_cast<VKContext *>(state.context);
|
||||
VKRenderTarget &render_target = *reinterpret_cast<VKRenderTarget *>(rt.get());
|
||||
|
||||
// don't forget to destroy the framebuffers
|
||||
state.surface_cache.destroy_associated_framebuffers(&render_target);
|
||||
|
||||
// deferred destroy everything in case some object is still being used
|
||||
FrameObject &frame = context.frame();
|
||||
frame.destroy_queue.add_image(render_target.color);
|
||||
frame.destroy_queue.add_image(render_target.depthstencil);
|
||||
if (state.features.use_mask_bit)
|
||||
frame.destroy_queue.add_image(render_target.mask);
|
||||
|
||||
for (auto fence : render_target.fences)
|
||||
frame.destroy_queue.add(fence);
|
||||
for (int i = 0; i < MAX_FRAMES_RENDERING; i++) {
|
||||
for (auto cmd_buffer : render_target.cmd_buffers[i])
|
||||
frame.destroy_queue.add_cmd_buffer(cmd_buffer, context.frames[i].command_pool);
|
||||
}
|
||||
}
|
||||
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, VKState &state, const SceGxmProgram &program) {
|
||||
vp = std::make_unique<VertexProgram>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void create(SceGxmSyncObject *sync) {
|
||||
sync->extra = new SyncExtraData();
|
||||
}
|
||||
|
||||
void destroy(SceGxmSyncObject *sync) {
|
||||
delete reinterpret_cast<SyncExtraData *>(sync->extra);
|
||||
}
|
||||
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, VKState &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend) {
|
||||
fp = std::make_unique<VKFragmentProgram>();
|
||||
|
||||
VKFragmentProgram *fp_vk = reinterpret_cast<VKFragmentProgram *>(fp.get());
|
||||
|
||||
// Translate blending.
|
||||
// programs using native color can't use traditional blending
|
||||
if (blend != nullptr && !program.is_native_color()) {
|
||||
vk::ColorComponentFlags color_mask{};
|
||||
if (blend->colorMask & SCE_GXM_COLOR_MASK_R)
|
||||
color_mask |= vk::ColorComponentFlagBits::eR;
|
||||
if (blend->colorMask & SCE_GXM_COLOR_MASK_G)
|
||||
color_mask |= vk::ColorComponentFlagBits::eG;
|
||||
if (blend->colorMask & SCE_GXM_COLOR_MASK_B)
|
||||
color_mask |= vk::ColorComponentFlagBits::eB;
|
||||
if (blend->colorMask & SCE_GXM_COLOR_MASK_A)
|
||||
color_mask |= vk::ColorComponentFlagBits::eA;
|
||||
|
||||
fp_vk->blending = vk::PipelineColorBlendAttachmentState{
|
||||
.blendEnable = (blend->colorFunc != SCE_GXM_BLEND_FUNC_NONE) || (blend->alphaFunc != SCE_GXM_BLEND_FUNC_NONE),
|
||||
.srcColorBlendFactor = translate_blend_factor(blend->colorSrc),
|
||||
.dstColorBlendFactor = translate_blend_factor(blend->colorDst),
|
||||
.colorBlendOp = translate_blend_func(blend->colorFunc),
|
||||
.srcAlphaBlendFactor = translate_blend_factor(blend->alphaSrc),
|
||||
.dstAlphaBlendFactor = translate_blend_factor(blend->alphaDst),
|
||||
.alphaBlendOp = translate_blend_func(blend->alphaFunc),
|
||||
.colorWriteMask = color_mask
|
||||
};
|
||||
} else {
|
||||
// default values, only blendEnable and colorWriteMask are useful
|
||||
fp_vk->blending = vk::PipelineColorBlendAttachmentState{
|
||||
.blendEnable = VK_FALSE,
|
||||
.srcColorBlendFactor = vk::BlendFactor::eOne,
|
||||
.dstColorBlendFactor = vk::BlendFactor::eZero,
|
||||
.colorBlendOp = vk::BlendOp::eAdd,
|
||||
.srcAlphaBlendFactor = vk::BlendFactor::eOne,
|
||||
.dstAlphaBlendFactor = vk::BlendFactor::eZero,
|
||||
.alphaBlendOp = vk::BlendOp::eAdd,
|
||||
.colorWriteMask = vk::ColorComponentFlagBits::eR
|
||||
| vk::ColorComponentFlagBits::eG
|
||||
| vk::ColorComponentFlagBits::eB
|
||||
| vk::ColorComponentFlagBits::eA
|
||||
};
|
||||
}
|
||||
|
||||
// compute blending hash, as it will be used for the pipeline hash
|
||||
fp_vk->blending_hash = XXH_INLINE_XXH3_64bits(&fp_vk->blending, sizeof(vk::PipelineColorBlendAttachmentState));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,852 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
vk::Format translate_attribute_format(SceGxmAttributeFormat format, unsigned int component_count, bool is_integer, bool is_signed) {
|
||||
if (component_count == 0 || component_count > 4 || format > SCE_GXM_ATTRIBUTE_FORMAT_UNTYPED)
|
||||
LOG_ERROR("Unsupported attribute format {}x{}", log_hex(format), component_count);
|
||||
|
||||
// Ahhhhh, I don't want to see this function ever again
|
||||
static constexpr vk::Format formats_integer[][4] = {
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_U8*/ { vk::Format::eR8Uint, vk::Format::eR8G8Uint, vk::Format::eR8G8B8Uint, vk::Format::eR8G8B8A8Uint },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_S8*/ { vk::Format::eR8Sint, vk::Format::eR8G8Sint, vk::Format::eR8G8B8Sint, vk::Format::eR8G8B8A8Sint },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_U16*/ { vk::Format::eR16Uint, vk::Format::eR16G16Uint, vk::Format::eR16G16B16Uint, vk::Format::eR8G8B8A8Uint },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_S16*/ { vk::Format::eR16Sint, vk::Format::eR16G16Sint, vk::Format::eR16G16B16Sint, vk::Format::eR8G8B8A8Sint },
|
||||
};
|
||||
|
||||
static constexpr vk::Format formats_integer_as_float[][4] = {
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_U8*/ { vk::Format::eR8Uscaled, vk::Format::eR8G8Uscaled, vk::Format::eR8G8B8Uscaled, vk::Format::eR8G8B8A8Uscaled },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_S8*/ { vk::Format::eR8Sscaled, vk::Format::eR8G8Sscaled, vk::Format::eR8G8B8Sscaled, vk::Format::eR8G8B8A8Sscaled },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_U16*/ { vk::Format::eR16Uscaled, vk::Format::eR16G16Uscaled, vk::Format::eR16G16B16Uscaled, vk::Format::eR8G8B8A8Uscaled },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_S16*/ { vk::Format::eR16Sscaled, vk::Format::eR16G16Sscaled, vk::Format::eR16G16B16Sscaled, vk::Format::eR8G8B8A8Sscaled },
|
||||
};
|
||||
|
||||
static constexpr vk::Format formats_float[][4] = {
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_U8N*/ { vk::Format::eR8Unorm, vk::Format::eR8G8Unorm, vk::Format::eR8G8B8Unorm, vk::Format::eR8G8B8A8Unorm },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_S8N*/ { vk::Format::eR8Snorm, vk::Format::eR8G8Snorm, vk::Format::eR8G8B8Snorm, vk::Format::eR8G8B8A8Snorm },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_U16N*/ { vk::Format::eR16Unorm, vk::Format::eR16G16Unorm, vk::Format::eR16G16B16Unorm, vk::Format::eR16G16B16A16Unorm },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_S16N*/ { vk::Format::eR16Snorm, vk::Format::eR16G16Snorm, vk::Format::eR16G16B16Snorm, vk::Format::eR16G16B16A16Snorm },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_F16*/ { vk::Format::eR16Sfloat, vk::Format::eR16G16Sfloat, vk::Format::eR16G16B16Sfloat, vk::Format::eR16G16B16A16Sfloat },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_F32*/ { vk::Format::eR32Sfloat, vk::Format::eR32G32Sfloat, vk::Format::eR32G32B32Sfloat, vk::Format::eR32G32B32A32Sfloat },
|
||||
};
|
||||
|
||||
static constexpr vk::Format formats_untyped[][4] = {
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_UNTYPED unsigned*/ { vk::Format::eR32Uint, vk::Format::eR32G32Uint, vk::Format::eR32G32B32Uint, vk::Format::eR32G32B32A32Uint },
|
||||
/*SCE_GXM_ATTRIBUTE_FORMAT_UNTYPED signed*/ { vk::Format::eR32Sint, vk::Format::eR32G32Sint, vk::Format::eR32G32B32Sint, vk::Format::eR32G32B32A32Sint },
|
||||
};
|
||||
|
||||
const int format_idx = static_cast<int>(format);
|
||||
if (format_idx < SCE_GXM_ATTRIBUTE_FORMAT_U8N) {
|
||||
if (is_integer)
|
||||
return formats_integer[format_idx][component_count - 1];
|
||||
else
|
||||
return formats_integer_as_float[format_idx][component_count - 1];
|
||||
} else if (format == SCE_GXM_ATTRIBUTE_FORMAT_UNTYPED) {
|
||||
return formats_untyped[is_signed][component_count - 1];
|
||||
} else {
|
||||
return formats_float[format_idx - SCE_GXM_ATTRIBUTE_FORMAT_U8N][component_count - 1];
|
||||
}
|
||||
}
|
||||
|
||||
vk::BlendFactor translate_blend_factor(const SceGxmBlendFactor blend_factor) {
|
||||
switch (blend_factor) {
|
||||
case SCE_GXM_BLEND_FACTOR_ZERO:
|
||||
return vk::BlendFactor::eZero;
|
||||
case SCE_GXM_BLEND_FACTOR_ONE:
|
||||
return vk::BlendFactor::eOne;
|
||||
case SCE_GXM_BLEND_FACTOR_SRC_COLOR:
|
||||
return vk::BlendFactor::eSrcColor;
|
||||
case SCE_GXM_BLEND_FACTOR_ONE_MINUS_SRC_COLOR:
|
||||
return vk::BlendFactor::eOneMinusSrcColor;
|
||||
case SCE_GXM_BLEND_FACTOR_SRC_ALPHA:
|
||||
return vk::BlendFactor::eSrcAlpha;
|
||||
case SCE_GXM_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA:
|
||||
return vk::BlendFactor::eOneMinusSrcAlpha;
|
||||
case SCE_GXM_BLEND_FACTOR_DST_COLOR:
|
||||
return vk::BlendFactor::eDstColor;
|
||||
case SCE_GXM_BLEND_FACTOR_ONE_MINUS_DST_COLOR:
|
||||
return vk::BlendFactor::eOneMinusDstColor;
|
||||
case SCE_GXM_BLEND_FACTOR_DST_ALPHA:
|
||||
return vk::BlendFactor::eDstAlpha;
|
||||
case SCE_GXM_BLEND_FACTOR_ONE_MINUS_DST_ALPHA:
|
||||
return vk::BlendFactor::eOneMinusDstAlpha;
|
||||
case SCE_GXM_BLEND_FACTOR_SRC_ALPHA_SATURATE:
|
||||
return vk::BlendFactor::eSrcAlphaSaturate;
|
||||
case SCE_GXM_BLEND_FACTOR_DST_ALPHA_SATURATE:
|
||||
return vk::BlendFactor::eDstAlpha; // TODO Not supported.
|
||||
default:
|
||||
LOG_ERROR("Unknown blend factor: {}", blend_factor);
|
||||
return vk::BlendFactor::eOne;
|
||||
}
|
||||
}
|
||||
|
||||
vk::BlendOp translate_blend_func(const SceGxmBlendFunc blend_func) {
|
||||
switch (blend_func) {
|
||||
case SCE_GXM_BLEND_FUNC_NONE:
|
||||
case SCE_GXM_BLEND_FUNC_ADD:
|
||||
return vk::BlendOp::eAdd;
|
||||
case SCE_GXM_BLEND_FUNC_SUBTRACT:
|
||||
return vk::BlendOp::eSubtract;
|
||||
case SCE_GXM_BLEND_FUNC_REVERSE_SUBTRACT:
|
||||
return vk::BlendOp::eReverseSubtract;
|
||||
case SCE_GXM_BLEND_FUNC_MIN:
|
||||
return vk::BlendOp::eMin;
|
||||
case SCE_GXM_BLEND_FUNC_MAX:
|
||||
return vk::BlendOp::eMax;
|
||||
default:
|
||||
LOG_ERROR("Unknown blend func: {}", blend_func);
|
||||
return vk::BlendOp::eAdd;
|
||||
}
|
||||
}
|
||||
|
||||
vk::PrimitiveTopology translate_primitive(SceGxmPrimitiveType primitive) {
|
||||
switch (primitive) {
|
||||
case SCE_GXM_PRIMITIVE_TRIANGLES:
|
||||
return vk::PrimitiveTopology::eTriangleList;
|
||||
case SCE_GXM_PRIMITIVE_TRIANGLE_STRIP:
|
||||
return vk::PrimitiveTopology::eTriangleStrip;
|
||||
case SCE_GXM_PRIMITIVE_TRIANGLE_FAN:
|
||||
return vk::PrimitiveTopology::eTriangleFan;
|
||||
case SCE_GXM_PRIMITIVE_LINES:
|
||||
return vk::PrimitiveTopology::eLineList;
|
||||
case SCE_GXM_PRIMITIVE_POINTS:
|
||||
return vk::PrimitiveTopology::ePointList;
|
||||
case SCE_GXM_PRIMITIVE_TRIANGLE_EDGES: // Todo: Implement this
|
||||
LOG_WARN("Unsupported primitive: SCE_GXM_PRIMITIVE_TRIANGLE_EDGES, using SCE_GXM_PRIMITIVE_TRIANGLES instead");
|
||||
return vk::PrimitiveTopology::eTriangleList;
|
||||
default:
|
||||
LOG_ERROR("Unknown primitive: {}", primitive);
|
||||
return vk::PrimitiveTopology::eTriangleList;
|
||||
}
|
||||
}
|
||||
|
||||
vk::CompareOp translate_depth_func(SceGxmDepthFunc depth_func) {
|
||||
switch (depth_func) {
|
||||
case SCE_GXM_DEPTH_FUNC_NEVER:
|
||||
return vk::CompareOp::eNever;
|
||||
case SCE_GXM_DEPTH_FUNC_LESS:
|
||||
return vk::CompareOp::eLess;
|
||||
case SCE_GXM_DEPTH_FUNC_EQUAL:
|
||||
return vk::CompareOp::eEqual;
|
||||
case SCE_GXM_DEPTH_FUNC_LESS_EQUAL:
|
||||
return vk::CompareOp::eLessOrEqual;
|
||||
case SCE_GXM_DEPTH_FUNC_GREATER:
|
||||
return vk::CompareOp::eGreater;
|
||||
case SCE_GXM_DEPTH_FUNC_NOT_EQUAL:
|
||||
return vk::CompareOp::eNotEqual;
|
||||
case SCE_GXM_DEPTH_FUNC_GREATER_EQUAL:
|
||||
return vk::CompareOp::eGreaterOrEqual;
|
||||
case SCE_GXM_DEPTH_FUNC_ALWAYS:
|
||||
return vk::CompareOp::eAlways;
|
||||
default:
|
||||
LOG_ERROR("Unknown depth func {}", log_hex(depth_func));
|
||||
return vk::CompareOp::eAlways;
|
||||
}
|
||||
}
|
||||
|
||||
vk::PolygonMode translate_polygon_mode(SceGxmPolygonMode polygon_mode) {
|
||||
switch (polygon_mode) {
|
||||
case SCE_GXM_POLYGON_MODE_TRIANGLE_FILL:
|
||||
return vk::PolygonMode::eFill;
|
||||
case SCE_GXM_POLYGON_MODE_LINE:
|
||||
case SCE_GXM_POLYGON_MODE_TRIANGLE_LINE:
|
||||
return vk::PolygonMode::eLine;
|
||||
case SCE_GXM_POLYGON_MODE_POINT_10UV:
|
||||
case SCE_GXM_POLYGON_MODE_POINT:
|
||||
case SCE_GXM_POLYGON_MODE_POINT_01UV:
|
||||
case SCE_GXM_POLYGON_MODE_TRIANGLE_POINT:
|
||||
return vk::PolygonMode::ePoint;
|
||||
default:
|
||||
LOG_ERROR("Unknown polygon mode {}", log_hex(polygon_mode));
|
||||
return vk::PolygonMode::eFill;
|
||||
}
|
||||
}
|
||||
vk::CullModeFlags translate_cull_mode(SceGxmCullMode cull_mode) {
|
||||
// In gxm, the font face is always counter clockwise
|
||||
switch (cull_mode) {
|
||||
case SCE_GXM_CULL_NONE:
|
||||
return vk::CullModeFlagBits::eNone;
|
||||
case SCE_GXM_CULL_CW:
|
||||
return vk::CullModeFlagBits::eBack;
|
||||
case SCE_GXM_CULL_CCW:
|
||||
return vk::CullModeFlagBits::eFront;
|
||||
default:
|
||||
LOG_ERROR("Unknown cull mode {}", log_hex(cull_mode));
|
||||
return vk::CullModeFlagBits::eNone;
|
||||
}
|
||||
}
|
||||
|
||||
vk::CompareOp translate_stencil_func(SceGxmStencilFunc stencil_func) {
|
||||
switch (stencil_func) {
|
||||
case SCE_GXM_STENCIL_FUNC_NEVER:
|
||||
return vk::CompareOp::eNever;
|
||||
case SCE_GXM_STENCIL_FUNC_LESS:
|
||||
return vk::CompareOp::eLess;
|
||||
case SCE_GXM_STENCIL_FUNC_EQUAL:
|
||||
return vk::CompareOp::eEqual;
|
||||
case SCE_GXM_STENCIL_FUNC_LESS_EQUAL:
|
||||
return vk::CompareOp::eLessOrEqual;
|
||||
case SCE_GXM_STENCIL_FUNC_GREATER:
|
||||
return vk::CompareOp::eGreater;
|
||||
case SCE_GXM_STENCIL_FUNC_NOT_EQUAL:
|
||||
return vk::CompareOp::eNotEqual;
|
||||
case SCE_GXM_STENCIL_FUNC_GREATER_EQUAL:
|
||||
return vk::CompareOp::eGreaterOrEqual;
|
||||
case SCE_GXM_STENCIL_FUNC_ALWAYS:
|
||||
return vk::CompareOp::eAlways;
|
||||
default:
|
||||
LOG_ERROR("Unknown stencil func {}", log_hex(stencil_func));
|
||||
return vk::CompareOp::eAlways;
|
||||
}
|
||||
}
|
||||
|
||||
vk::StencilOp translate_stencil_op(SceGxmStencilOp stencil_op) {
|
||||
switch (stencil_op) {
|
||||
case SCE_GXM_STENCIL_OP_KEEP:
|
||||
return vk::StencilOp::eKeep;
|
||||
case SCE_GXM_STENCIL_OP_ZERO:
|
||||
return vk::StencilOp::eZero;
|
||||
case SCE_GXM_STENCIL_OP_REPLACE:
|
||||
return vk::StencilOp::eReplace;
|
||||
case SCE_GXM_STENCIL_OP_INCR:
|
||||
return vk::StencilOp::eIncrementAndClamp;
|
||||
case SCE_GXM_STENCIL_OP_DECR:
|
||||
return vk::StencilOp::eDecrementAndClamp;
|
||||
case SCE_GXM_STENCIL_OP_INVERT:
|
||||
return vk::StencilOp::eInvert;
|
||||
case SCE_GXM_STENCIL_OP_INCR_WRAP:
|
||||
return vk::StencilOp::eIncrementAndWrap;
|
||||
case SCE_GXM_STENCIL_OP_DECR_WRAP:
|
||||
return vk::StencilOp::eDecrementAndWrap;
|
||||
default:
|
||||
LOG_ERROR("Unknown stencil op {}", log_hex(stencil_op));
|
||||
return vk::StencilOp::eKeep;
|
||||
}
|
||||
}
|
||||
|
||||
using Swizzle = vk::ComponentSwizzle;
|
||||
|
||||
static constexpr vk::ComponentMapping swizzle_identity = { Swizzle::eIdentity, Swizzle::eIdentity, Swizzle::eIdentity, Swizzle::eIdentity };
|
||||
|
||||
// SceGxmSwizzle1Mode
|
||||
static constexpr vk::ComponentMapping swizzle_r001 = { Swizzle::eR, Swizzle::eZero, Swizzle::eZero, Swizzle::eOne };
|
||||
static constexpr vk::ComponentMapping swizzle_r000 = { Swizzle::eR, Swizzle::eZero, Swizzle::eZero, Swizzle::eZero };
|
||||
static constexpr vk::ComponentMapping swizzle_r111 = { Swizzle::eR, Swizzle::eOne, Swizzle::eOne, Swizzle::eOne };
|
||||
static constexpr vk::ComponentMapping swizzle_rrrr = { Swizzle::eR, Swizzle::eR, Swizzle::eR, Swizzle::eR };
|
||||
static constexpr vk::ComponentMapping swizzle_rrr0 = { Swizzle::eR, Swizzle::eR, Swizzle::eR, Swizzle::eZero };
|
||||
static constexpr vk::ComponentMapping swizzle_rrr1 = { Swizzle::eR, Swizzle::eR, Swizzle::eR, Swizzle::eOne };
|
||||
static constexpr vk::ComponentMapping swizzle_000r = { Swizzle::eZero, Swizzle::eZero, Swizzle::eZero, Swizzle::eR };
|
||||
static constexpr vk::ComponentMapping swizzle_111r = { Swizzle::eOne, Swizzle::eOne, Swizzle::eOne, Swizzle::eR };
|
||||
|
||||
// SceGxmSwizzle2Mode
|
||||
static constexpr vk::ComponentMapping swizzle_rg01 = { Swizzle::eR, Swizzle::eG, Swizzle::eZero, Swizzle::eOne };
|
||||
static constexpr vk::ComponentMapping swizzle_gr01 = { Swizzle::eG, Swizzle::eR, Swizzle::eZero, Swizzle::eOne };
|
||||
static constexpr vk::ComponentMapping swizzle_a00r = { Swizzle::eA, Swizzle::eZero, Swizzle::eZero, Swizzle::eR };
|
||||
static constexpr vk::ComponentMapping swizzle_r00a = { Swizzle::eR, Swizzle::eZero, Swizzle::eZero, Swizzle::eA };
|
||||
static constexpr vk::ComponentMapping swizzle_rg00 = { Swizzle::eR, Swizzle::eG, Swizzle::eZero, Swizzle::eZero };
|
||||
static constexpr vk::ComponentMapping swizzle_rrrg = { Swizzle::eR, Swizzle::eR, Swizzle::eR, Swizzle::eG };
|
||||
static constexpr vk::ComponentMapping swizzle_gggr = { Swizzle::eG, Swizzle::eG, Swizzle::eG, Swizzle::eR };
|
||||
static constexpr vk::ComponentMapping swizzle_rgrg = { Swizzle::eR, Swizzle::eG, Swizzle::eR, Swizzle::eG };
|
||||
static constexpr vk::ComponentMapping swizzle_gr00 = { Swizzle::eG, Swizzle::eR, Swizzle::eZero, Swizzle::eZero };
|
||||
|
||||
// SceGxmSwizzle3Mode
|
||||
static constexpr vk::ComponentMapping swizzle_bgr1 = { Swizzle::eB, Swizzle::eG, Swizzle::eR, Swizzle::eOne };
|
||||
static constexpr vk::ComponentMapping swizzle_rgb1 = { Swizzle::eR, Swizzle::eG, Swizzle::eB, Swizzle::eOne };
|
||||
|
||||
// SceGxmSwizzle4Mode
|
||||
static constexpr vk::ComponentMapping swizzle_rgba = { Swizzle::eR, Swizzle::eG, Swizzle::eB, Swizzle::eA };
|
||||
static constexpr vk::ComponentMapping swizzle_bgra = { Swizzle::eB, Swizzle::eG, Swizzle::eR, Swizzle::eA };
|
||||
static constexpr vk::ComponentMapping swizzle_abgr = { Swizzle::eA, Swizzle::eB, Swizzle::eG, Swizzle::eR };
|
||||
static constexpr vk::ComponentMapping swizzle_gbar = { Swizzle::eG, Swizzle::eB, Swizzle::eA, Swizzle::eR };
|
||||
|
||||
namespace color {
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle1(SceGxmColorSwizzle1Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_COLOR_SWIZZLE1_R:
|
||||
return swizzle_r001;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return swizzle_identity;
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle2(SceGxmColorSwizzle2Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_COLOR_SWIZZLE2_GR:
|
||||
return swizzle_rg01;
|
||||
case SCE_GXM_COLOR_SWIZZLE2_RG:
|
||||
return swizzle_gr01;
|
||||
case SCE_GXM_COLOR_SWIZZLE2_RA:
|
||||
return swizzle_a00r;
|
||||
case SCE_GXM_COLOR_SWIZZLE2_AR:
|
||||
return swizzle_r00a;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return swizzle_identity;
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle3(SceGxmColorSwizzle3Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_COLOR_SWIZZLE3_BGR:
|
||||
return swizzle_rgb1;
|
||||
case SCE_GXM_COLOR_SWIZZLE3_RGB:
|
||||
return swizzle_bgr1;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return swizzle_identity;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the swizzle when the vulkan format has a BGR (or RGB packed) layout
|
||||
static const vk::ComponentMapping translate_swizzle3_bgr(SceGxmColorSwizzle3Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_COLOR_SWIZZLE3_BGR:
|
||||
return swizzle_bgr1;
|
||||
case SCE_GXM_COLOR_SWIZZLE3_RGB:
|
||||
return swizzle_rgb1;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return swizzle_identity;
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle4(SceGxmColorSwizzle4Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_COLOR_SWIZZLE4_ABGR:
|
||||
return swizzle_rgba;
|
||||
case SCE_GXM_COLOR_SWIZZLE4_ARGB:
|
||||
return swizzle_bgra;
|
||||
case SCE_GXM_COLOR_SWIZZLE4_RGBA:
|
||||
return swizzle_abgr;
|
||||
case SCE_GXM_COLOR_SWIZZLE4_BGRA:
|
||||
return swizzle_gbar;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return swizzle_identity;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the swizzle when the vulkan format has a ABGR (or RGBA packed) layout
|
||||
static const vk::ComponentMapping translate_swizzle4_abgr(SceGxmColorSwizzle4Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_COLOR_SWIZZLE4_ABGR:
|
||||
return swizzle_abgr;
|
||||
case SCE_GXM_COLOR_SWIZZLE4_ARGB:
|
||||
return swizzle_gbar;
|
||||
case SCE_GXM_COLOR_SWIZZLE4_RGBA:
|
||||
return swizzle_rgba;
|
||||
case SCE_GXM_COLOR_SWIZZLE4_BGRA:
|
||||
return swizzle_bgra;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
vk::ComponentMapping translate_swizzle(SceGxmColorFormat format) {
|
||||
const SceGxmColorBaseFormat base_format = gxm::get_base_format(format);
|
||||
const uint32_t swizzle = format & SCE_GXM_COLOR_SWIZZLE_MASK;
|
||||
switch (base_format) {
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S8:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U16:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S16:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F16:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F32:
|
||||
return translate_swizzle1(static_cast<SceGxmColorSwizzle1Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8U8:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S8S8:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U16U16:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S16S16:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F16F16:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F32F32:
|
||||
return translate_swizzle2(static_cast<SceGxmColorSwizzle2Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8U8U8:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F11F11F10:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_SE5M9M9M9:
|
||||
return translate_swizzle3(static_cast<SceGxmColorSwizzle3Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U5U6U5:
|
||||
return translate_swizzle3_bgr(static_cast<SceGxmColorSwizzle3Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8U8U8U8:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S8S8S8S8:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F16F16F16F16:
|
||||
// TODO: the swizzle for the following 2 formats is not fully supported
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U1U5U5U5:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U2U10U10U10:
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U2F10F10F10:
|
||||
return translate_swizzle4(static_cast<SceGxmColorSwizzle4Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U4U4U4U4:
|
||||
return translate_swizzle4_abgr(static_cast<SceGxmColorSwizzle4Mode>(swizzle));
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unknown format {}", log_hex(base_format));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
vk::Format translate_format(SceGxmColorBaseFormat format) {
|
||||
// TODO: look if all these formats are available on the GPU
|
||||
switch (format) {
|
||||
// classic unpacked formats
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8:
|
||||
return vk::Format::eR8Unorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S8:
|
||||
return vk::Format::eR8Snorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U16:
|
||||
return vk::Format::eR16Unorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S16:
|
||||
return vk::Format::eR16Snorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F16:
|
||||
return vk::Format::eR16Sfloat;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F32:
|
||||
return vk::Format::eR32Sfloat;
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8U8:
|
||||
return vk::Format::eR8G8Unorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S8S8:
|
||||
return vk::Format::eR8G8Snorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U16U16:
|
||||
return vk::Format::eR16G16Unorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S16S16:
|
||||
return vk::Format::eR16G16Snorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F16F16:
|
||||
return vk::Format::eR16G16Sfloat;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F32F32:
|
||||
return vk::Format::eR32G32Sfloat;
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8U8U8U8:
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_S8S8S8S8:
|
||||
return vk::Format::eR8G8B8A8Snorm;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F16F16F16F16:
|
||||
return vk::Format::eR16G16B16A16Sfloat;
|
||||
|
||||
// packed formats
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U5U6U5:
|
||||
return vk::Format::eR5G6B5UnormPack16;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_F11F11F10:
|
||||
return vk::Format::eB10G11R11UfloatPack32;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_SE5M9M9M9:
|
||||
return vk::Format::eE5B9G9R9UfloatPack32;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U8U8U8:
|
||||
// 24 bit packed RGB is not supported (on many GPUs), use rgba8 instad
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U1U5U5U5:
|
||||
// TODO: we must use either eR5G5B5A1UnormPack16 or eA1R5G5B5UnormPack16 depending on the swizzle
|
||||
// also eR5G5B5A1UnormPack16 is not supported on all GPUs...
|
||||
return vk::Format::eA1R5G5B5UnormPack16;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U4U4U4U4:
|
||||
return vk::Format::eR4G4B4A4UnormPack16;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U2U10U10U10:
|
||||
// TODO: only ABGR or ARGB swizzle is supported with this format
|
||||
return vk::Format::eA2R10G10B10UnormPack32;
|
||||
case SCE_GXM_COLOR_BASE_FORMAT_U2F10F10F10:
|
||||
// This format is not supported on modern GPUs, give something bigger
|
||||
return vk::Format::eR16G16B16A16Sfloat;
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unknown format {}", log_hex(format));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
} // namespace color
|
||||
|
||||
namespace texture {
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle1(SceGxmTextureSwizzle1Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_R:
|
||||
return swizzle_r001;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_000R:
|
||||
return swizzle_r000;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_111R:
|
||||
return swizzle_r111;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_RRRR:
|
||||
return swizzle_rrrr;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_0RRR:
|
||||
return swizzle_rrr0;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_1RRR:
|
||||
return swizzle_rrr1;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_R000:
|
||||
return swizzle_000r;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE1_R111:
|
||||
return swizzle_111r;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle2(SceGxmTextureSwizzle2Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_GR:
|
||||
return swizzle_rg01;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_00GR:
|
||||
return swizzle_rg00;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_GRRR:
|
||||
return swizzle_rrrg;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_RGGG:
|
||||
return swizzle_gggr;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_GRGR:
|
||||
return swizzle_rgrg;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_00RG:
|
||||
return swizzle_gr00;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzleds(SceGxmTextureSwizzle2ModeAlt mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_SD:
|
||||
return swizzle_identity;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE2_DS:
|
||||
return swizzle_identity;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle3(SceGxmTextureSwizzle3Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE3_BGR:
|
||||
return swizzle_rgb1;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE3_RGB:
|
||||
return swizzle_bgr1;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the swizzle when the vulkan format has a BGR (or RGB packed) layout
|
||||
static const vk::ComponentMapping translate_swizzle3_bgr(SceGxmTextureSwizzle3Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE3_BGR:
|
||||
return swizzle_bgr1;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE3_RGB:
|
||||
return swizzle_rgb1;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzle4(SceGxmTextureSwizzle4Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_ABGR:
|
||||
return swizzle_rgba;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_ARGB:
|
||||
return swizzle_bgra;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_RGBA:
|
||||
return swizzle_abgr;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_BGRA:
|
||||
return swizzle_gbar;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_1BGR:
|
||||
return swizzle_rgb1;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the swizzle when the vulkan format has a ABGR (or RGBA packed) layout
|
||||
static const vk::ComponentMapping translate_swizzle4_abgr(SceGxmTextureSwizzle4Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_ABGR:
|
||||
return swizzle_abgr;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_ARGB:
|
||||
return swizzle_gbar;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_RGBA:
|
||||
return swizzle_rgba;
|
||||
case SCE_GXM_TEXTURE_SWIZZLE4_BGRA:
|
||||
return swizzle_bgra;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzleyuv420(SceGxmTextureSwizzleYUV420Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YUV_CSC0:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YVU_CSC0:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YUV_CSC1:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YVU_CSC1:
|
||||
return swizzle_identity;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return swizzle_identity;
|
||||
}
|
||||
}
|
||||
|
||||
static const vk::ComponentMapping translate_swizzleyuv422(SceGxmTextureSwizzleYUV422Mode mode) {
|
||||
switch (mode) {
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YUYV_CSC0:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YVYU_CSC0:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_UYVY_CSC0:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_VYUY_CSC0:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YUYV_CSC1:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_YVYU_CSC1:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_UYVY_CSC1:
|
||||
case SCE_GXM_TEXTURE_SWIZZLE_VYUY_CSC1:
|
||||
return swizzle_identity;
|
||||
default:
|
||||
LOG_ERROR("Unknown swizzle mode {}", log_hex(mode));
|
||||
return swizzle_identity;
|
||||
}
|
||||
}
|
||||
|
||||
vk::ComponentMapping translate_swizzle(SceGxmTextureFormat format) {
|
||||
const SceGxmTextureBaseFormat base_format = gxm::get_base_format(format);
|
||||
const uint32_t swizzle = format & SCE_GXM_TEXTURE_SWIZZLE_MASK;
|
||||
switch (base_format) {
|
||||
// 1 Component.
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U32:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S32:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32M:
|
||||
return translate_swizzle1(static_cast<SceGxmTextureSwizzle1Mode>(swizzle));
|
||||
|
||||
// 2 components
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8U8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S8S8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U16U16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S16S16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F16F16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U32U32:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32F32:
|
||||
return translate_swizzle2(static_cast<SceGxmTextureSwizzle2Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_X8U24:
|
||||
return translate_swizzleds(static_cast<SceGxmTextureSwizzle2ModeAlt>(swizzle));
|
||||
|
||||
// 3 components
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F11F11F10:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9:
|
||||
return translate_swizzle3(static_cast<SceGxmTextureSwizzle3Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U5U6U5:
|
||||
return translate_swizzle3_bgr(static_cast<SceGxmTextureSwizzle3Mode>(swizzle));
|
||||
|
||||
// 4 components
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8S8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U16U16U16U16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S16S16S16S16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F16F16F16F16:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_P8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_P4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT4BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP:
|
||||
// TODO: the following 2 are not fully supported
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U1U5U5U5:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U2U10U10U10:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U2F10F10F10:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
return translate_swizzle4(static_cast<SceGxmTextureSwizzle4Mode>(swizzle));
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U4U4U4U4:
|
||||
return translate_swizzle4_abgr(static_cast<SceGxmTextureSwizzle4Mode>(swizzle));
|
||||
|
||||
// YUV420.
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_YUV420P2:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_YUV420P3:
|
||||
return translate_swizzleyuv420(static_cast<SceGxmTextureSwizzleYUV420Mode>(swizzle));
|
||||
|
||||
// YUV422
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_YUV422:
|
||||
return translate_swizzleyuv422(static_cast<SceGxmTextureSwizzleYUV422Mode>(swizzle));
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unknown format {}", log_hex(base_format));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
vk::Format translate_format(SceGxmTextureBaseFormat base_format) {
|
||||
switch (base_format) {
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8:
|
||||
return vk::Format::eR8Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S8:
|
||||
return vk::Format::eR8Snorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U16:
|
||||
return vk::Format::eR16Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S16:
|
||||
return vk::Format::eR16Snorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F16:
|
||||
return vk::Format::eR16Sfloat;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U32:
|
||||
return vk::Format::eR32Uint;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S32:
|
||||
return vk::Format::eR32Sint;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_X8U24:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32M:
|
||||
return vk::Format::eR32Sfloat;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8U8:
|
||||
return vk::Format::eR8G8Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S8S8:
|
||||
return vk::Format::eR8G8Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U16U16:
|
||||
return vk::Format::eR8G8Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S16S16:
|
||||
return vk::Format::eR8G8Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F16F16:
|
||||
return vk::Format::eR16G16Sfloat;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U32U32:
|
||||
return vk::Format::eR32G32Uint;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F32F32:
|
||||
return vk::Format::eR32G32Sfloat;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8:
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S8S8S8S8:
|
||||
return vk::Format::eR8G8B8A8Snorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U16U16U16U16:
|
||||
return vk::Format::eR16G16B16A16Unorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_S16S16S16S16:
|
||||
return vk::Format::eR16G16B16A16Snorm;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F16F16F16F16:
|
||||
return vk::Format::eR16G16B16A16Sfloat;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U5U6U5:
|
||||
return vk::Format::eR5G6B5UnormPack16;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_F11F11F10:
|
||||
return vk::Format::eB10G11R11UfloatPack32;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_SE5M9M9M9:
|
||||
return vk::Format::eE5B9G9R9UfloatPack32;
|
||||
|
||||
// the following formats are all decompressed to u8u8u8u8
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_P8:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_P4:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRT4BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII2BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_PVRTII4BPP:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_YUV420P2:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_YUV420P3:
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_YUV422:
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U4U4U4U4:
|
||||
return vk::Format::eR4G4B4A4UnormPack16;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U1U5U5U5:
|
||||
// TODO: same as for the color format
|
||||
return vk::Format::eA1R5G5B5UnormPack16;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U2U10U10U10:
|
||||
// TODO: same as for the color format
|
||||
return vk::Format::eA2R10G10B10UnormPack32;
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_U2F10F10F10:
|
||||
// not supported by modern GPUs
|
||||
return vk::Format::eR16G16B16A16Sfloat;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC1:
|
||||
return vk::Format::eBc1RgbaUnormBlock;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC2:
|
||||
return vk::Format::eBc2UnormBlock;
|
||||
|
||||
case SCE_GXM_TEXTURE_BASE_FORMAT_UBC3:
|
||||
return vk::Format::eBc3UnormBlock;
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unknown format {}", log_hex(base_format));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
vk::SamplerAddressMode translate_address_mode(SceGxmTextureAddrMode src) {
|
||||
switch (src) {
|
||||
case SCE_GXM_TEXTURE_ADDR_REPEAT:
|
||||
return vk::SamplerAddressMode::eRepeat;
|
||||
case SCE_GXM_TEXTURE_ADDR_MIRROR:
|
||||
return vk::SamplerAddressMode::eMirroredRepeat;
|
||||
case SCE_GXM_TEXTURE_ADDR_CLAMP:
|
||||
return vk::SamplerAddressMode::eClampToEdge;
|
||||
case SCE_GXM_TEXTURE_ADDR_MIRROR_CLAMP:
|
||||
return vk::SamplerAddressMode::eMirrorClampToEdge;
|
||||
case SCE_GXM_TEXTURE_ADDR_REPEAT_IGNORE_BORDER:
|
||||
return vk::SamplerAddressMode::eRepeat; // FIXME: Is this correct?
|
||||
case SCE_GXM_TEXTURE_ADDR_CLAMP_FULL_BORDER:
|
||||
return vk::SamplerAddressMode::eClampToBorder;
|
||||
case SCE_GXM_TEXTURE_ADDR_CLAMP_IGNORE_BORDER:
|
||||
return vk::SamplerAddressMode::eClampToBorder; // FIXME: Is this correct?
|
||||
case SCE_GXM_TEXTURE_ADDR_CLAMP_HALF_BORDER:
|
||||
return vk::SamplerAddressMode::eClampToBorder; // FIXME: Is this correct?
|
||||
default:
|
||||
LOG_ERROR("Unknown address mode {}", log_hex(src));
|
||||
return vk::SamplerAddressMode::eClampToEdge;
|
||||
}
|
||||
}
|
||||
|
||||
vk::Filter translate_filter(SceGxmTextureFilter src) {
|
||||
switch (src) {
|
||||
case SCE_GXM_TEXTURE_FILTER_POINT:
|
||||
case SCE_GXM_TEXTURE_FILTER_MIPMAP_POINT:
|
||||
return vk::Filter::eNearest;
|
||||
case SCE_GXM_TEXTURE_FILTER_LINEAR:
|
||||
case SCE_GXM_TEXTURE_FILTER_MIPMAP_LINEAR:
|
||||
return vk::Filter::eLinear;
|
||||
default:
|
||||
LOG_ERROR("Unknown texture filter {}", log_hex(src));
|
||||
return vk::Filter::eNearest;
|
||||
}
|
||||
}
|
||||
|
||||
vk::SamplerMipmapMode translate_mimpmap_mode(SceGxmTextureFilter src) {
|
||||
switch (src) {
|
||||
case SCE_GXM_TEXTURE_FILTER_POINT:
|
||||
case SCE_GXM_TEXTURE_FILTER_MIPMAP_POINT:
|
||||
return vk::SamplerMipmapMode::eNearest;
|
||||
case SCE_GXM_TEXTURE_FILTER_LINEAR:
|
||||
case SCE_GXM_TEXTURE_FILTER_MIPMAP_LINEAR:
|
||||
return vk::SamplerMipmapMode::eLinear;
|
||||
default:
|
||||
LOG_ERROR("Unknown mipmap mode {}", log_hex(src));
|
||||
return vk::SamplerMipmapMode::eNearest;
|
||||
}
|
||||
}
|
||||
} // namespace texture
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,621 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/vulkan/pipeline_cache.h>
|
||||
|
||||
#include <xxh3.h>
|
||||
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
#include <renderer/vulkan/state.h>
|
||||
#include <renderer/vulkan/types.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
#include <gxm/types.h>
|
||||
#include <renderer/shaders.h>
|
||||
#include <shader/spirv_recompiler.h>
|
||||
#include <util/fs.h>
|
||||
|
||||
#include <util/log.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
PipelineCache::PipelineCache(VKState &state)
|
||||
: state(state) {
|
||||
}
|
||||
|
||||
void PipelineCache::init() {
|
||||
vk::PipelineCacheCreateInfo pipeline_info{};
|
||||
pipeline_cache = state.device.createPipelineCache(pipeline_info);
|
||||
|
||||
// the layout for uniforms buffer can be made here as it will always be the same
|
||||
{
|
||||
std::array<vk::DescriptorSetLayoutBinding, 4> layout_bindings;
|
||||
// GXM vertex uniform
|
||||
layout_bindings[0] = vk::DescriptorSetLayoutBinding{
|
||||
.binding = 0,
|
||||
.descriptorType = vk::DescriptorType::eStorageBufferDynamic,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eVertex,
|
||||
};
|
||||
// GXM Fragment uniform
|
||||
layout_bindings[1] = vk::DescriptorSetLayoutBinding{
|
||||
.binding = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageBufferDynamic,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
||||
};
|
||||
// Our vertex uniform (GXMRenderVertUniformBlock)
|
||||
layout_bindings[2] = vk::DescriptorSetLayoutBinding{
|
||||
.binding = 2,
|
||||
.descriptorType = vk::DescriptorType::eUniformBufferDynamic,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eVertex,
|
||||
};
|
||||
// Our fragment uniform (GXMRenderFragUniformBlock)
|
||||
layout_bindings[3] = vk::DescriptorSetLayoutBinding{
|
||||
.binding = 3,
|
||||
.descriptorType = vk::DescriptorType::eUniformBufferDynamic,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info{};
|
||||
descriptor_info.setBindings(layout_bindings);
|
||||
uniforms_layout = state.device.createDescriptorSetLayout(descriptor_info);
|
||||
}
|
||||
|
||||
{
|
||||
// layout for the mask, color attachment as input
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> layout_binding;
|
||||
layout_binding[0] = vk::DescriptorSetLayoutBinding{
|
||||
.binding = 0,
|
||||
.descriptorType = vk::DescriptorType::eInputAttachment,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment
|
||||
};
|
||||
layout_binding[1] = vk::DescriptorSetLayoutBinding{
|
||||
.binding = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info{
|
||||
.bindingCount = state.features.use_mask_bit ? 2U : 1U,
|
||||
.pBindings = layout_binding.data()
|
||||
};
|
||||
attachments_layout = state.device.createDescriptorSetLayout(descriptor_info);
|
||||
}
|
||||
|
||||
{
|
||||
// texture layout
|
||||
// first vertex
|
||||
std::array<vk::DescriptorSetLayoutBinding, 16> layout_bindings;
|
||||
for (uint32_t i = 0; i < 16; i++) {
|
||||
layout_bindings[i] = {
|
||||
.binding = i,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eVertex
|
||||
};
|
||||
}
|
||||
for (uint32_t i = 0; i < 17; i++) {
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info{
|
||||
.bindingCount = i,
|
||||
.pBindings = layout_bindings.data()
|
||||
};
|
||||
vertex_textures_layout[i] = state.device.createDescriptorSetLayout(descriptor_info);
|
||||
}
|
||||
|
||||
// then fragment
|
||||
for (uint32_t i = 0; i < 16; i++) {
|
||||
layout_bindings[i].stageFlags = vk::ShaderStageFlagBits::eFragment;
|
||||
}
|
||||
for (uint32_t i = 0; i < 17; i++) {
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info{
|
||||
.bindingCount = i,
|
||||
.pBindings = layout_bindings.data()
|
||||
};
|
||||
fragment_textures_layout[i] = state.device.createDescriptorSetLayout(descriptor_info);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// look for rgb vertex attribute support
|
||||
// we only look at one format and assume it is the same for all usual 3-component formats
|
||||
vk::FormatProperties rgb_property = state.physical_device.getFormatProperties(vk::Format::eR16G16B16Unorm);
|
||||
this->support_rgb_vertex_attribute = static_cast<bool>(rgb_property.bufferFeatures & vk::FormatFeatureFlagBits::eVertexBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::read_pipeline_cache() {
|
||||
const auto shaders_path{ fs::path(state.base_path) / "cache/shaders" / state.title_id / state.self_name };
|
||||
const std::string pipeline_cache_name = fmt::format("pipeline-cache-vk{}.dat", shader::CURRENT_VERSION);
|
||||
const fs::path path = shaders_path / pipeline_cache_name;
|
||||
|
||||
fs::ifstream pipeline_cache_file(path, std::ios::in | std::ios::binary);
|
||||
if (!pipeline_cache_file.is_open())
|
||||
return;
|
||||
|
||||
LOG_INFO("Found pipeline cache, reading...");
|
||||
|
||||
pipeline_cache_file.seekg(0, fs::ifstream::end);
|
||||
const size_t pipeline_size = pipeline_cache_file.tellg();
|
||||
pipeline_cache_file.seekg(0);
|
||||
|
||||
std::vector<char> pipeline_data(pipeline_size);
|
||||
pipeline_cache_file.read(pipeline_data.data(), pipeline_size);
|
||||
pipeline_cache_file.close();
|
||||
|
||||
vk::PipelineCacheCreateInfo cache_info{
|
||||
.initialDataSize = pipeline_size,
|
||||
.pInitialData = pipeline_data.data()
|
||||
};
|
||||
|
||||
state.device.destroyPipelineCache(pipeline_cache);
|
||||
pipeline_cache = state.device.createPipelineCache(cache_info);
|
||||
LOG_INFO("Pipeline cache read and loaded");
|
||||
}
|
||||
|
||||
void PipelineCache::save_pipeline_cache() {
|
||||
size_t pipeline_size;
|
||||
state.device.getPipelineCacheData(pipeline_cache, &pipeline_size, nullptr);
|
||||
if (pipeline_size == 0)
|
||||
// No pipeline was created
|
||||
return;
|
||||
|
||||
const auto shaders_path{ fs::path(state.base_path) / "cache/shaders" / state.title_id / state.self_name };
|
||||
const std::string pipeline_cache_name = fmt::format("pipeline-cache-vk{}.dat", shader::CURRENT_VERSION);
|
||||
const fs::path path = shaders_path / pipeline_cache_name;
|
||||
|
||||
fs::ofstream pipeline_cache_file(path, std::ios::out | std::ios::binary);
|
||||
if (!pipeline_cache_file.is_open())
|
||||
return;
|
||||
|
||||
LOG_INFO("Saving pipeline cache...");
|
||||
std::vector<char> pipeline_data(pipeline_size);
|
||||
state.device.getPipelineCacheData(pipeline_cache, &pipeline_size, pipeline_data.data());
|
||||
|
||||
pipeline_cache_file.write(pipeline_data.data(), pipeline_size);
|
||||
pipeline_cache_file.close();
|
||||
LOG_INFO("Pipeline cache saved");
|
||||
}
|
||||
|
||||
static const Sha256Hash get_shader_hash(const SceGxmProgram &program) {
|
||||
const Sha256Hash hash_bytes = sha256(&program, program.size);
|
||||
return hash_bytes;
|
||||
}
|
||||
|
||||
vk::PipelineShaderStageCreateInfo PipelineCache::retrieve_shader(const SceGxmProgram *program, const Sha256Hash &hash, bool is_vertex, bool maskupdate, MemState &mem, const std::vector<SceGxmVertexAttribute> *hint_attributes) {
|
||||
if (maskupdate)
|
||||
LOG_CRITICAL("Mask not implemented in the vulkan renderer!");
|
||||
|
||||
auto it = shaders.find(hash);
|
||||
|
||||
if (it == shaders.end()) {
|
||||
// look if it is in the cache
|
||||
if (precompile_shader(hash))
|
||||
it = shaders.find(hash);
|
||||
}
|
||||
|
||||
if (it != shaders.end()) {
|
||||
vk::PipelineShaderStageCreateInfo shader_stage_info{
|
||||
.stage = is_vertex ? vk::ShaderStageFlagBits::eVertex : vk::ShaderStageFlagBits::eFragment,
|
||||
.module = it->second,
|
||||
.pName = is_vertex ? "main_vs" : "main_fs"
|
||||
};
|
||||
return shader_stage_info;
|
||||
}
|
||||
|
||||
if (!state.features.support_unknown_format)
|
||||
shader::last_color_format = gxm::get_base_format(current_context->record.color_surface.colorFormat);
|
||||
|
||||
const char *base_path = state.base_path;
|
||||
const char *title_id = state.title_id;
|
||||
const char *self_name = state.self_name;
|
||||
|
||||
const std::string hash_text = hex_string(hash);
|
||||
|
||||
LOG_INFO("Generating vulkan spv shader {}", hash_text.data());
|
||||
const std::string shader_version = fmt::format("vk{}", shader::CURRENT_VERSION);
|
||||
shader::usse::SpirvCode source = load_spirv_shader(*program, state.features, true, hint_attributes, maskupdate, base_path, title_id, self_name, shader_version, true);
|
||||
|
||||
vk::ShaderModuleCreateInfo shader_info{
|
||||
.codeSize = sizeof(uint32_t) * source.size(),
|
||||
.pCode = source.data()
|
||||
};
|
||||
|
||||
vk::ShaderModule shader = current_context->state.device.createShaderModule(shader_info);
|
||||
shaders[hash] = shader;
|
||||
|
||||
// Save shader cache haches
|
||||
// vertex and fragment shaders are not linked together so no need to associate them
|
||||
Sha256Hash empty_hash{};
|
||||
if (is_vertex) {
|
||||
state.shaders_cache_hashs.push_back({ hash, empty_hash });
|
||||
} else {
|
||||
state.shaders_cache_hashs.push_back({ empty_hash, hash });
|
||||
}
|
||||
renderer::save_shaders_cache_hashs(state, state.shaders_cache_hashs);
|
||||
|
||||
const auto time_s = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
next_pipeline_cache_save = time_s + pipeline_cache_save_delay;
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stage_info{
|
||||
.stage = is_vertex ? vk::ShaderStageFlagBits::eVertex : vk::ShaderStageFlagBits::eFragment,
|
||||
.module = shader,
|
||||
.pName = is_vertex ? "main_vs" : "main_fs"
|
||||
};
|
||||
|
||||
state.shaders_count_compiled++;
|
||||
|
||||
return shader_stage_info;
|
||||
}
|
||||
|
||||
vk::PipelineLayout PipelineCache::retrieve_pipeline_layout(const uint16_t vert_texture_count, const uint16_t frag_texture_count) {
|
||||
if (!pipeline_layouts[vert_texture_count][frag_texture_count]) {
|
||||
// create matching pipeline layout
|
||||
vk::PipelineLayoutCreateInfo layout_info{};
|
||||
vk::DescriptorSetLayout set_layouts[] = { uniforms_layout, attachments_layout, vertex_textures_layout[vert_texture_count], fragment_textures_layout[frag_texture_count] };
|
||||
layout_info.setSetLayouts(set_layouts);
|
||||
pipeline_layouts[vert_texture_count][frag_texture_count] = state.device.createPipelineLayout(layout_info);
|
||||
}
|
||||
|
||||
return pipeline_layouts[vert_texture_count][frag_texture_count];
|
||||
}
|
||||
|
||||
vk::RenderPass PipelineCache::retrieve_render_pass(vk::Format format, uint32_t zls_control) {
|
||||
bool force_loaded = (zls_control & SCE_GXM_DEPTH_STENCIL_FORCE_LOAD_ENABLED) != 0;
|
||||
bool force_stored = (zls_control & SCE_GXM_DEPTH_STENCIL_FORCE_STORE_ENABLED) != 0;
|
||||
|
||||
auto &render_passes_map = render_passes[force_loaded][force_stored];
|
||||
|
||||
auto it = render_passes_map.find(format);
|
||||
|
||||
if (it != render_passes_map.end())
|
||||
return it->second;
|
||||
|
||||
// create a new render pass for this format
|
||||
|
||||
vk::AttachmentReference color_ref{
|
||||
.attachment = 0,
|
||||
.layout = vk::ImageLayout::eGeneral
|
||||
};
|
||||
vk::AttachmentReference ds_ref{
|
||||
.attachment = 1,
|
||||
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal
|
||||
};
|
||||
vk::SubpassDescription subpass{
|
||||
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics
|
||||
};
|
||||
subpass.setColorAttachments(color_ref);
|
||||
subpass.setPDepthStencilAttachment(&ds_ref);
|
||||
subpass.setInputAttachments(color_ref);
|
||||
|
||||
vk::AttachmentDescription color_attachment{
|
||||
.format = format,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.loadOp = vk::AttachmentLoadOp::eLoad,
|
||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
||||
.initialLayout = vk::ImageLayout::eGeneral,
|
||||
.finalLayout = vk::ImageLayout::eGeneral
|
||||
};
|
||||
|
||||
vk::AttachmentLoadOp load_op = force_loaded ? vk::AttachmentLoadOp::eLoad : vk::AttachmentLoadOp::eClear;
|
||||
vk::AttachmentStoreOp store_op = force_stored ? vk::AttachmentStoreOp::eStore : vk::AttachmentStoreOp::eDontCare;
|
||||
vk::AttachmentDescription ds_attachment{
|
||||
.format = vk::Format::eD32SfloatS8Uint,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.loadOp = load_op,
|
||||
.storeOp = store_op,
|
||||
.stencilLoadOp = load_op,
|
||||
.stencilStoreOp = store_op,
|
||||
.initialLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal
|
||||
};
|
||||
|
||||
std::array<vk::SubpassDependency, 3> dependencies;
|
||||
|
||||
// external dependency
|
||||
// we want the previous render pass to be done when we reach the fragment stage / stencil*depth testing
|
||||
dependencies[0] = {
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentRead
|
||||
};
|
||||
|
||||
// if an attachment is sampled from, we want it to be done before the next render pass fragment shader
|
||||
dependencies[1] = {
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eFragmentShader,
|
||||
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
.srcAccessMask = vk::AccessFlagBits::eShaderRead,
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite
|
||||
};
|
||||
|
||||
// self-dependency
|
||||
// this allows us to use a pipeline barrier in the render pass for programmable blending
|
||||
dependencies[2] = {
|
||||
.srcSubpass = 0,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
.dstStageMask = vk::PipelineStageFlagBits::eFragmentShader,
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eInputAttachmentRead,
|
||||
.dependencyFlags = vk::DependencyFlagBits::eByRegion
|
||||
};
|
||||
|
||||
vk::RenderPassCreateInfo pass_info{};
|
||||
vk::AttachmentDescription attachments[] = { color_attachment, ds_attachment };
|
||||
pass_info.setAttachments(attachments);
|
||||
pass_info.setSubpasses(subpass);
|
||||
pass_info.setDependencies(dependencies);
|
||||
|
||||
render_passes_map[format] = state.device.createRenderPass(pass_info);
|
||||
|
||||
return render_passes_map[format];
|
||||
}
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo PipelineCache::get_vertex_input_state(MemState &mem) {
|
||||
// Vertex attributes.
|
||||
const GxmRecordState &state = current_context->record;
|
||||
const SceGxmVertexProgram &vertex_program = *state.vertex_program.get(mem);
|
||||
VertexProgram *vkvert = vertex_program.renderer_data.get();
|
||||
|
||||
if (!vkvert->stripped_symbols_checked) {
|
||||
// Insert some symbols here
|
||||
const SceGxmProgram *vertex_program_body = vertex_program.program.get(mem);
|
||||
if (vertex_program_body && (vertex_program_body->primary_reg_count != 0)) {
|
||||
for (std::size_t i = 0; i < vertex_program.attributes.size(); i++) {
|
||||
vkvert->attribute_infos.emplace(vertex_program.attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<std::uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
vkvert->stripped_symbols_checked = true;
|
||||
}
|
||||
|
||||
binding_descr.clear();
|
||||
attr_descr.clear();
|
||||
|
||||
uint32_t used_streams = 0;
|
||||
|
||||
for (const SceGxmVertexAttribute &attribute : vertex_program.attributes) {
|
||||
if (vkvert->attribute_infos.find(attribute.regIndex) == vkvert->attribute_infos.end())
|
||||
continue;
|
||||
|
||||
used_streams |= (1 << attribute.streamIndex);
|
||||
|
||||
const SceGxmVertexStream &stream = vertex_program.streams[attribute.streamIndex];
|
||||
const SceGxmAttributeFormat attribute_format = static_cast<SceGxmAttributeFormat>(attribute.format);
|
||||
shader::usse::AttributeInformation info = vkvert->attribute_infos.at(attribute.regIndex);
|
||||
|
||||
uint8_t component_count = attribute.componentCount;
|
||||
vk::Format format;
|
||||
if (info.regformat) {
|
||||
const int comp_size = gxm::attribute_format_size(attribute_format);
|
||||
component_count = (comp_size * component_count + 3) / 4;
|
||||
|
||||
if (!support_rgb_vertex_attribute && component_count == 3)
|
||||
component_count = 4;
|
||||
|
||||
// regformat attributes are int32
|
||||
format = translate_attribute_format(SCE_GXM_ATTRIBUTE_FORMAT_UNTYPED, component_count, true, true);
|
||||
} else {
|
||||
// some AMD GPUs do not support rgb vertex attributes, so just put it as rgba
|
||||
// the 4th component will contain garbage but this is not an issue because the input
|
||||
// in the shader will be vec3 (or ivec3) and the 4th component will be discarded
|
||||
if (!support_rgb_vertex_attribute && component_count == 3)
|
||||
component_count = 4;
|
||||
|
||||
format = translate_attribute_format(attribute_format, component_count, info.is_integer, info.is_signed);
|
||||
}
|
||||
|
||||
attr_descr.push_back(vk::VertexInputAttributeDescription{
|
||||
.location = info.location(),
|
||||
.binding = attribute.streamIndex,
|
||||
.format = format,
|
||||
.offset = attribute.offset });
|
||||
}
|
||||
|
||||
for (unsigned int stream_index = 0; stream_index < SCE_GXM_MAX_VERTEX_STREAMS; stream_index++) {
|
||||
if (!(used_streams & (1 << stream_index)))
|
||||
continue;
|
||||
|
||||
const SceGxmVertexStream &stream = vertex_program.streams[stream_index];
|
||||
|
||||
const bool is_instanced = gxm::is_stream_instancing(static_cast<SceGxmIndexSource>(stream.indexSource));
|
||||
|
||||
binding_descr.push_back(vk::VertexInputBindingDescription{
|
||||
.binding = stream_index,
|
||||
.stride = stream.stride,
|
||||
.inputRate = is_instanced ? vk::VertexInputRate::eInstance : vk::VertexInputRate::eVertex });
|
||||
}
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input{};
|
||||
vertex_input.setVertexBindingDescriptions(binding_descr);
|
||||
vertex_input.setVertexAttributeDescriptions(attr_descr);
|
||||
return vertex_input;
|
||||
}
|
||||
|
||||
static vk::StencilOpState convert_op_state(const GxmStencilStateOp &state) {
|
||||
return vk::StencilOpState{
|
||||
.failOp = translate_stencil_op(state.depth_fail),
|
||||
.passOp = translate_stencil_op(state.depth_pass),
|
||||
.depthFailOp = translate_stencil_op(state.depth_fail),
|
||||
.compareOp = translate_stencil_func(state.func)
|
||||
};
|
||||
}
|
||||
|
||||
vk::Pipeline PipelineCache::retrieve_pipeline(VKContext &context, SceGxmPrimitiveType &type, MemState &mem) {
|
||||
current_context = &context;
|
||||
const GxmRecordState &record = context.record;
|
||||
// get the hash of the current context
|
||||
constexpr size_t record_pipeline_len = offsetof(GxmRecordState, vertex_streams);
|
||||
uint64_t key = XXH_INLINE_XXH3_64bits(&record, record_pipeline_len);
|
||||
|
||||
// add the hash of the blending
|
||||
const SceGxmFragmentProgram &fragment_program_gxm = *record.fragment_program.get(mem);
|
||||
const VKFragmentProgram &fragment_program = *reinterpret_cast<VKFragmentProgram *>(
|
||||
fragment_program_gxm.renderer_data.get());
|
||||
key ^= fragment_program.blending_hash;
|
||||
|
||||
// add the hash of the attribute and stream layout
|
||||
const SceGxmVertexProgram &vertex_program_gxm = *record.vertex_program.get(mem);
|
||||
key ^= vertex_program_gxm.key_hash;
|
||||
|
||||
// and also add the primitive type
|
||||
key ^= static_cast<uint64_t>(type);
|
||||
auto it = pipelines.find(key);
|
||||
if (it != pipelines.end())
|
||||
return it->second;
|
||||
|
||||
const VertexProgram &vertex_program = *reinterpret_cast<VertexProgram *>(
|
||||
vertex_program_gxm.renderer_data.get());
|
||||
|
||||
// the vertex input state must be computed before shader are retrieved in case symbols are stripped
|
||||
const vk::PipelineVertexInputStateCreateInfo vertex_input = get_vertex_input_state(mem);
|
||||
|
||||
const vk::PipelineShaderStageCreateInfo vertex_shader = retrieve_shader(vertex_program_gxm.program.get(mem), vertex_program.hash, true, fragment_program_gxm.is_maskupdate, mem, &vertex_program_gxm.attributes);
|
||||
const vk::PipelineShaderStageCreateInfo fragment_shader = retrieve_shader(fragment_program_gxm.program.get(mem), fragment_program.hash, false, fragment_program_gxm.is_maskupdate, mem, nullptr);
|
||||
const vk::PipelineShaderStageCreateInfo shader_stages[] = { vertex_shader, fragment_shader };
|
||||
const uint32_t shader_stage_count = (record.front_side_fragment_program_mode == SCE_GXM_FRAGMENT_PROGRAM_DISABLED) ? 1U : 2U;
|
||||
|
||||
const vk::PipelineInputAssemblyStateCreateInfo input_assembly{
|
||||
.topology = translate_primitive(type)
|
||||
};
|
||||
|
||||
const bool two_sided = (record.two_sided == SCE_GXM_TWO_SIDED_ENABLED);
|
||||
|
||||
const vk::PipelineRasterizationStateCreateInfo rasterizer{
|
||||
.polygonMode = translate_polygon_mode(record.front_polygon_mode),
|
||||
.cullMode = translate_cull_mode(record.cull_mode),
|
||||
// front face is always counter clockwise
|
||||
.frontFace = vk::FrontFace::eCounterClockwise,
|
||||
.depthBiasEnable = VK_TRUE
|
||||
};
|
||||
const vk::PipelineMultisampleStateCreateInfo multisampling{
|
||||
.rasterizationSamples = vk::SampleCountFlagBits::e1
|
||||
};
|
||||
// depth and stencil tests are always enabled on the ps vita as there is almost no cost in doing so
|
||||
// on a tiled renderer
|
||||
const vk::PipelineDepthStencilStateCreateInfo ds_info{
|
||||
.depthTestEnable = VK_TRUE,
|
||||
.depthWriteEnable = (record.front_depth_write_mode == SCE_GXM_DEPTH_WRITE_ENABLED),
|
||||
.depthCompareOp = translate_depth_func(record.front_depth_func),
|
||||
.depthBoundsTestEnable = VK_FALSE,
|
||||
.stencilTestEnable = VK_TRUE,
|
||||
.front = convert_op_state(record.front_stencil_state_op),
|
||||
.back = convert_op_state(two_sided ? record.back_stencil_state_op : record.front_stencil_state_op)
|
||||
};
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo color_blending{};
|
||||
if (record.front_side_fragment_program_mode == SCE_GXM_FRAGMENT_PROGRAM_DISABLED) {
|
||||
// The write mask must be empty as the lack of a fragment shader results in undefined values
|
||||
static const vk::PipelineColorBlendAttachmentState blending = {
|
||||
.blendEnable = VK_FALSE,
|
||||
.colorWriteMask = vk::ColorComponentFlags()
|
||||
};
|
||||
color_blending.setAttachments(blending);
|
||||
} else {
|
||||
const vk::PipelineColorBlendAttachmentState &blending = fragment_program.blending;
|
||||
color_blending.setAttachments(blending);
|
||||
}
|
||||
|
||||
vk::PipelineLayout pipeline_layout = retrieve_pipeline_layout(vertex_program.texture_count, fragment_program.texture_count);
|
||||
|
||||
// all of these can be changed at any time using the vita graphics api (like opengl)
|
||||
// Because each one can take a lot of different values, it's better to set them as dynamic
|
||||
static vk::DynamicState dynamic_states[] = {
|
||||
vk::DynamicState::eViewport,
|
||||
vk::DynamicState::eScissor,
|
||||
vk::DynamicState::eLineWidth,
|
||||
vk::DynamicState::eStencilCompareMask,
|
||||
vk::DynamicState::eStencilReference,
|
||||
vk::DynamicState::eStencilWriteMask,
|
||||
vk::DynamicState::eDepthBias
|
||||
};
|
||||
vk::PipelineDynamicStateCreateInfo dynamic_info{};
|
||||
dynamic_info.setDynamicStates(dynamic_states);
|
||||
|
||||
// we still need to specifiy the viewport and scissor count even though they are dynamic
|
||||
vk::PipelineViewportStateCreateInfo viewport{
|
||||
.viewportCount = 1,
|
||||
.scissorCount = 1
|
||||
};
|
||||
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info{
|
||||
.stageCount = shader_stage_count,
|
||||
.pStages = shader_stages,
|
||||
.pVertexInputState = &vertex_input,
|
||||
.pInputAssemblyState = &input_assembly,
|
||||
.pViewportState = &viewport,
|
||||
.pRasterizationState = &rasterizer,
|
||||
.pMultisampleState = &multisampling,
|
||||
.pDepthStencilState = &ds_info,
|
||||
.pColorBlendState = &color_blending,
|
||||
.pDynamicState = &dynamic_info,
|
||||
.layout = pipeline_layout,
|
||||
.renderPass = context.current_render_pass,
|
||||
.subpass = 0
|
||||
};
|
||||
|
||||
const auto result = state.device.createGraphicsPipeline(pipeline_cache, pipeline_info);
|
||||
if (result.result != vk::Result::eSuccess) {
|
||||
LOG_CRITICAL("Failed to create pipeline.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pipelines[key] = result.value;
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
bool PipelineCache::precompile_shader(const Sha256Hash &hash) {
|
||||
const auto shader_path{ fs::path(state.base_path) / "cache/shaders" / state.title_id / state.self_name };
|
||||
|
||||
auto it = shaders.find(hash);
|
||||
if (it != shaders.end())
|
||||
return true;
|
||||
|
||||
if (!fs::exists(shader_path) || fs::is_empty(shader_path))
|
||||
return false;
|
||||
|
||||
Sha256Hash shader_hash;
|
||||
memcpy(shader_hash.data(), hash.data(), sizeof(Sha256Hash));
|
||||
const std::string hash_ver = fmt::format("vk{}-{}", shader::CURRENT_VERSION, hex_string(shader_hash));
|
||||
|
||||
const std::vector<uint32_t> source = renderer::pre_load_shader_spirv(hash_ver.c_str(), "spv", state.base_path, state.title_id, state.self_name);
|
||||
|
||||
if (source.empty())
|
||||
return false;
|
||||
|
||||
vk::ShaderModuleCreateInfo shader_info{
|
||||
.codeSize = sizeof(uint32_t) * source.size(),
|
||||
.pCode = source.data()
|
||||
};
|
||||
|
||||
vk::ShaderModule shader = state.device.createShaderModule(shader_info);
|
||||
shaders[hash] = shader;
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace renderer::vulkan
|
||||
@@ -1,5 +1,5 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
@@ -21,95 +21,52 @@
|
||||
#include <renderer/vulkan/state.h>
|
||||
|
||||
#include <config/version.h>
|
||||
#include <display/state.h>
|
||||
#include <shader/spirv_recompiler.h>
|
||||
#include <util/log.h>
|
||||
#include <vkutil/vkutil.h>
|
||||
|
||||
#include <SDL_vulkan.h>
|
||||
|
||||
// Setting a default value for now.
|
||||
// In the future, it might be a good idea to take the host's device memory into account.
|
||||
constexpr static size_t private_allocation_size = MiB(1);
|
||||
static vk::DebugUtilsMessengerEXT debug_messenger;
|
||||
|
||||
// Some random number as value. It will likely be very different. There are probably SCE fields for this, I will look later.
|
||||
constexpr static uint32_t max_sets = 192;
|
||||
constexpr static uint32_t max_buffers = 64;
|
||||
constexpr static uint32_t max_images = 64;
|
||||
constexpr static uint32_t max_samplers = 64;
|
||||
|
||||
constexpr static vk::Format screen_format = vk::Format::eB8G8R8A8Unorm;
|
||||
|
||||
#ifdef __APPLE__
|
||||
const char *surface_macos_extension = "VK_MVK_macos_surface";
|
||||
const char *metal_surface_extension = "VK_EXT_metal_surface";
|
||||
|
||||
const char *get_surface_extension() {
|
||||
const std::vector<vk::ExtensionProperties> extensions = vk::enumerateInstanceExtensionProperties(nullptr);
|
||||
|
||||
if (std::find_if(extensions.begin(), extensions.end(), [](const vk::ExtensionProperties &props) {
|
||||
return strcmp(props.extensionName, metal_surface_extension) == 0;
|
||||
})
|
||||
!= extensions.end())
|
||||
return metal_surface_extension;
|
||||
|
||||
if (std::find_if(extensions.begin(), extensions.end(), [](const vk::ExtensionProperties &props) {
|
||||
return strcmp(props.extensionName, surface_macos_extension) == 0;
|
||||
})
|
||||
!= extensions.end())
|
||||
return surface_macos_extension;
|
||||
|
||||
return nullptr;
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT message_type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
|
||||
void *pUserData) {
|
||||
if (message_severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
|
||||
// for now we are not interested by performance warnings
|
||||
&& (message_type & ~VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)) {
|
||||
std::string message = callback_data->pMessage;
|
||||
// ignore this message for now
|
||||
if (message.find("VUID-vkCmdDrawIndexed-None-02721") == std::string::npos)
|
||||
LOG_ERROR("Validation layer: {}", callback_data->pMessage);
|
||||
}
|
||||
return VK_FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
const static std::vector<const char *> instance_layers = {
|
||||
#ifndef NDEBUG
|
||||
"VK_LAYER_LUNARG_standard_validation",
|
||||
#endif
|
||||
};
|
||||
|
||||
const static std::vector<const char *> instance_extensions = {
|
||||
"VK_KHR_surface",
|
||||
#ifdef __APPLE__
|
||||
get_surface_extension(),
|
||||
#endif
|
||||
#ifdef WIN32
|
||||
"VK_KHR_win32_surface",
|
||||
#endif
|
||||
};
|
||||
|
||||
const static std::vector<const char *> device_layers = {
|
||||
// Nothing yet.
|
||||
};
|
||||
|
||||
const static std::vector<const char *> device_extensions = {
|
||||
"VK_KHR_swapchain",
|
||||
};
|
||||
|
||||
const static vk::PhysicalDeviceFeatures required_features({
|
||||
// .vertexPipelineStoresAndAtomics = true
|
||||
// etc.
|
||||
});
|
||||
|
||||
const static std::vector<vk::DescriptorPoolSize> descriptor_pool_sizes = {
|
||||
vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, max_buffers),
|
||||
vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, max_images),
|
||||
vk::DescriptorPoolSize(vk::DescriptorType::eSampler, max_samplers),
|
||||
};
|
||||
|
||||
const static std::vector<vk::Format> acceptable_surface_formats = {
|
||||
vk::Format::eB8G8R8A8Unorm, // Molten VK
|
||||
const static std::vector<const char *> required_device_extensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
// needed in order to use storage buffers
|
||||
VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME,
|
||||
// needed in order to use negative viewport height
|
||||
VK_KHR_MAINTENANCE1_EXTENSION_NAME
|
||||
};
|
||||
|
||||
namespace renderer::vulkan {
|
||||
static bool device_is_compatible(
|
||||
vk::PhysicalDeviceProperties &properties,
|
||||
vk::PhysicalDeviceFeatures &features,
|
||||
vk::SurfaceCapabilitiesKHR &capabilities) {
|
||||
// TODO: Do any required checks here. Should check against required_features.
|
||||
|
||||
return true;
|
||||
static bool device_is_compatible(const vk::PhysicalDevice &device) {
|
||||
const std::vector<vk::ExtensionProperties> available_extensions = device.enumerateDeviceExtensionProperties();
|
||||
|
||||
std::set<std::string> required_extensions(required_device_extensions.begin(), required_device_extensions.end());
|
||||
for (const auto &extension : available_extensions)
|
||||
required_extensions.erase(extension.extensionName);
|
||||
|
||||
return required_extensions.empty();
|
||||
}
|
||||
|
||||
static bool select_queues(VulkanState &vulkan_state,
|
||||
static bool select_queues(VKState &vk_state,
|
||||
std::vector<vk::DeviceQueueCreateInfo> &queue_infos, std::vector<std::vector<float>> &queue_priorities) {
|
||||
// TODO: Better queue allocation.
|
||||
|
||||
@@ -125,33 +82,39 @@ static bool select_queues(VulkanState &vulkan_state,
|
||||
|
||||
bool found_graphics = false, found_transfer = false;
|
||||
|
||||
for (size_t a = 0; a < vulkan_state.physical_device_queue_families.size(); a++) {
|
||||
const auto &queue_family = vulkan_state.physical_device_queue_families[a];
|
||||
for (uint32_t i = 0; i < vk_state.physical_device_queue_families.size(); i++) {
|
||||
const auto &queue_family = vk_state.physical_device_queue_families[i];
|
||||
|
||||
// MoltenVK does not accept nullptr a pPriorities for some reason.
|
||||
std::vector<float> &priorities = queue_priorities.emplace_back(queue_family.queueCount, 1.0f);
|
||||
|
||||
// Only one DeviceQueueCreateInfo should be created per family.
|
||||
if (!found_graphics && queue_family.queueFlags & vk::QueueFlagBits::eGraphics
|
||||
&& queue_family.queueFlags & vk::QueueFlagBits::eTransfer) {
|
||||
queue_infos.emplace_back(
|
||||
vk::DeviceQueueCreateFlagBits(), // No Flags
|
||||
a, // Queue Family Index
|
||||
queue_family.queueCount, // Queue Count
|
||||
priorities.data() // Priorities
|
||||
);
|
||||
vulkan_state.general_family_index = a;
|
||||
if (!found_graphics && (queue_family.queueFlags & vk::QueueFlagBits::eGraphics)
|
||||
&& (queue_family.queueFlags & vk::QueueFlagBits::eTransfer)
|
||||
&& vk_state.physical_device.getSurfaceSupportKHR(i, vk_state.screen_renderer.surface)) {
|
||||
vk::DeviceQueueCreateInfo queue_create_info{
|
||||
.queueFamilyIndex = i,
|
||||
.queueCount = queue_family.queueCount,
|
||||
.pQueuePriorities = priorities.data()
|
||||
};
|
||||
queue_infos.emplace_back(std::move(queue_create_info));
|
||||
vk_state.general_family_index = i;
|
||||
vk_state.transfer_family_index = i;
|
||||
found_graphics = true;
|
||||
} else if (!found_transfer && queue_family.queueFlags & vk::QueueFlagBits::eTransfer) {
|
||||
queue_infos.emplace_back(
|
||||
vk::DeviceQueueCreateFlagBits(), // No Flags
|
||||
a, // Queue Family Index
|
||||
queue_family.queueCount, // Queue Count
|
||||
priorities.data() // Priorities
|
||||
);
|
||||
vulkan_state.transfer_family_index = a;
|
||||
found_transfer = true;
|
||||
}
|
||||
// for now use the same queue for graphics and transfer, to be improved on later
|
||||
/* else if (!found_transfer && queue_family.queueFlags & vk::QueueFlagBits::eTransfer) {
|
||||
vk::DeviceQueueCreateInfo queue_create_info{
|
||||
.queueFamilyIndex = i,
|
||||
.queueCount = queue_family.queueCount,
|
||||
.pQueuePriorities = priorities.data()
|
||||
};
|
||||
queue_infos.emplace_back(std::move(queue_create_info));
|
||||
vk_state.transfer_family_index = i;
|
||||
found_transfer = true;
|
||||
}
|
||||
*/
|
||||
|
||||
if (found_graphics && found_transfer)
|
||||
break;
|
||||
@@ -160,435 +123,375 @@ static bool select_queues(VulkanState &vulkan_state,
|
||||
return found_graphics && found_transfer;
|
||||
}
|
||||
|
||||
static vk::Format select_surface_format(std::vector<vk::SurfaceFormatKHR> &formats) {
|
||||
for (const auto &format : formats) {
|
||||
if (std::find(acceptable_surface_formats.begin(), acceptable_surface_formats.end(), format.format)
|
||||
!= acceptable_surface_formats.end())
|
||||
return format.format;
|
||||
}
|
||||
// Adapted from https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/master/includes/functions.php
|
||||
std::string get_driver_version(uint32_t vendor_id, uint32_t version_raw) {
|
||||
// NVIDIA
|
||||
if (vendor_id == 4318)
|
||||
return fmt::format("{}.{}.{}.{}", (version_raw >> 22) & 0x3ff, (version_raw >> 14) & 0x0ff, (version_raw >> 6) & 0x0ff, (version_raw)&0x003f);
|
||||
|
||||
assert(false);
|
||||
#ifdef WIN32
|
||||
// Intel drivers on Windows
|
||||
if (vendor_id == 0x8086)
|
||||
return fmt::format("{}.{}", version_raw >> 14, (version_raw)&0x3fff);
|
||||
#endif
|
||||
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
// Use Vulkan version conventions if vendor mapping is not available
|
||||
return fmt::format("{}.{}.{}", (version_raw >> 22) & 0x3ff, (version_raw >> 12) & 0x3ff, (version_raw)&0xfff);
|
||||
}
|
||||
|
||||
vk::Queue select_queue(VulkanState &state, CommandType type) {
|
||||
vk::Queue queue;
|
||||
bool create(SDL_Window *window, std::unique_ptr<renderer::State> &state, const char *base_path) {
|
||||
auto &vk_state = dynamic_cast<VKState &>(*state);
|
||||
|
||||
switch (type) {
|
||||
case CommandType::General:
|
||||
queue = state.general_queues[state.general_queue_last % state.general_queues.size()];
|
||||
state.general_queue_last++;
|
||||
break;
|
||||
case CommandType::Transfer:
|
||||
queue = state.transfer_queues[state.transfer_queue_last % state.transfer_queues.size()];
|
||||
state.transfer_queue_last++;
|
||||
break;
|
||||
}
|
||||
|
||||
return queue;
|
||||
return vk_state.create(window, state, base_path);
|
||||
}
|
||||
|
||||
void present(VulkanState &state, uint32_t image_index) { // this needs semaphore, image index etc.
|
||||
// The general queue family is guaranteed (by an assert) to have present support.
|
||||
vk::Queue present_queue = select_queue(state, CommandType::General);
|
||||
|
||||
vk::PresentInfoKHR present_info(
|
||||
0, nullptr, // No Semaphores
|
||||
1, &state.swapchain, &image_index, nullptr // Swapchain
|
||||
);
|
||||
|
||||
present_queue.presentKHR(present_info);
|
||||
VKState::VKState(int gpu_idx)
|
||||
: screen_renderer(*this)
|
||||
, surface_cache(*this)
|
||||
, pipeline_cache(*this)
|
||||
, texture_cache(*this)
|
||||
, gpu_idx(gpu_idx) {
|
||||
}
|
||||
|
||||
bool resize_swapchain(VulkanState &state, vk::Extent2D size) {
|
||||
state.swapchain_width = size.width;
|
||||
state.swapchain_height = size.height;
|
||||
|
||||
if (state.swapchain) {
|
||||
for (vk::ImageView view : state.swapchain_views)
|
||||
state.device.destroy(view);
|
||||
state.device.destroy(state.swapchain);
|
||||
}
|
||||
|
||||
// Create Swapchain
|
||||
{
|
||||
// TODO: Extents should be based on surface capabilities.
|
||||
vk::SwapchainCreateInfoKHR swapchain_info(
|
||||
vk::SwapchainCreateFlagsKHR(), // No Flags
|
||||
state.surface, // Surface
|
||||
2, // Double Buffering
|
||||
screen_format, // Image Format, BGRA is supported by MoltenVK
|
||||
vk::ColorSpaceKHR::eSrgbNonlinear, // Color Space
|
||||
size, // Image Extent
|
||||
1, // Image Array Length
|
||||
vk::ImageUsageFlagBits::eColorAttachment, // Image Usage, consider VK_IMAGE_USAGE_STORAGE_BIT?
|
||||
vk::SharingMode::eExclusive,
|
||||
0, nullptr, // Unused when sharing mode is exclusive
|
||||
vk::SurfaceTransformFlagBitsKHR::eIdentity, // Transform
|
||||
vk::CompositeAlphaFlagBitsKHR::eOpaque, // Alpha
|
||||
vk::PresentModeKHR::eFifo, // Present Mode, FIFO and Immediate are supported on MoltenVK. Would've chosen Mailbox otherwise.
|
||||
true, // Clipping
|
||||
vk::SwapchainKHR() // No old swapchain.
|
||||
);
|
||||
|
||||
state.swapchain = state.device.createSwapchainKHR(swapchain_info);
|
||||
if (!state.swapchain) {
|
||||
LOG_ERROR("Failed to create Vulkan swapchain.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get Swapchain Images
|
||||
uint32_t swapchain_image_count = 2;
|
||||
state.device.getSwapchainImagesKHR(state.swapchain, &swapchain_image_count, state.swapchain_images);
|
||||
|
||||
for (uint32_t a = 0; a < 2 /*vulkan_state.swapchain_images.size()*/; a++) {
|
||||
const auto &image = state.swapchain_images[a];
|
||||
vk::ImageViewCreateInfo view_info(
|
||||
vk::ImageViewCreateFlags(), // No Flags
|
||||
image, // Image
|
||||
vk::ImageViewType::e2D, // Image View Type
|
||||
select_surface_format(state.physical_device_surface_formats), // Format
|
||||
vk::ComponentMapping(), // Default Component Mapping
|
||||
vk::ImageSubresourceRange(
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
0, // Mipmap Level
|
||||
1, // Level Count
|
||||
0, // Base Array Index
|
||||
1 // Layer Count
|
||||
));
|
||||
|
||||
vk::ImageView view = state.device.createImageView(view_info);
|
||||
if (!view) {
|
||||
LOG_ERROR("Failed to Vulkan image view for swpachain image id {}.", a);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.swapchain_views[a] = view;
|
||||
}
|
||||
|
||||
bool VKState::init(const char *base_path, const bool hashless_texture_cache) {
|
||||
shader_version = fmt::format("v{}", shader::CURRENT_VERSION);
|
||||
return true;
|
||||
}
|
||||
|
||||
vk::CommandBuffer create_command_buffer(VulkanState &state, CommandType type) {
|
||||
vk::CommandBuffer buffer;
|
||||
|
||||
switch (type) {
|
||||
case CommandType::General: {
|
||||
vk::CommandBufferAllocateInfo command_buffer_info(
|
||||
state.general_command_pool, // Command Pool
|
||||
vk::CommandBufferLevel::ePrimary, // Level
|
||||
1 // Count
|
||||
);
|
||||
|
||||
state.device.allocateCommandBuffers(&command_buffer_info, &buffer);
|
||||
break;
|
||||
}
|
||||
case CommandType::Transfer: {
|
||||
vk::CommandBufferAllocateInfo command_buffer_info(
|
||||
state.transfer_command_pool, // Command Pool
|
||||
vk::CommandBufferLevel::ePrimary, // Level
|
||||
1 // Count
|
||||
);
|
||||
|
||||
state.device.allocateCommandBuffers(&command_buffer_info, &buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void free_command_buffer(VulkanState &state, CommandType type, vk::CommandBuffer buffer) {
|
||||
vk::CommandPool pool;
|
||||
|
||||
switch (type) {
|
||||
case CommandType::General:
|
||||
pool = state.general_command_pool;
|
||||
break;
|
||||
case CommandType::Transfer:
|
||||
pool = state.transfer_command_pool;
|
||||
break;
|
||||
}
|
||||
|
||||
state.device.freeCommandBuffers(pool, 1, &buffer);
|
||||
}
|
||||
|
||||
vk::Buffer create_buffer(VulkanState &state, const vk::BufferCreateInfo &buffer_info, MemoryType type, VmaAllocation &allocation) {
|
||||
VmaMemoryUsage memory_usage;
|
||||
switch (type) {
|
||||
case MemoryType::Mappable:
|
||||
memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
break;
|
||||
case MemoryType::Device:
|
||||
memory_usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
break;
|
||||
}
|
||||
|
||||
VmaAllocationCreateInfo allocation_info = {};
|
||||
allocation_info.flags = 0;
|
||||
allocation_info.usage = memory_usage;
|
||||
// Usage is specified via usage field. Others are ignored.
|
||||
allocation_info.requiredFlags = 0;
|
||||
allocation_info.preferredFlags = 0;
|
||||
allocation_info.memoryTypeBits = 0;
|
||||
allocation_info.pool = VK_NULL_HANDLE;
|
||||
allocation_info.pUserData = nullptr;
|
||||
|
||||
VkBuffer buffer;
|
||||
VkResult result = vmaCreateBuffer(state.allocator,
|
||||
reinterpret_cast<const VkBufferCreateInfo *>(&buffer_info),
|
||||
&allocation_info, &buffer, &allocation, nullptr);
|
||||
assert(result == VK_SUCCESS);
|
||||
assert(allocation != VK_NULL_HANDLE);
|
||||
assert(buffer != VK_NULL_HANDLE);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void destroy_buffer(VulkanState &state, vk::Buffer buffer, VmaAllocation allocation) {
|
||||
vmaDestroyBuffer(state.allocator, buffer, allocation);
|
||||
}
|
||||
|
||||
vk::Image create_image(VulkanState &state, const vk::ImageCreateInfo &image_info, MemoryType type, VmaAllocation &allocation) {
|
||||
VmaMemoryUsage memory_usage;
|
||||
switch (type) {
|
||||
case MemoryType::Mappable:
|
||||
memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
break;
|
||||
case MemoryType::Device:
|
||||
memory_usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
break;
|
||||
}
|
||||
|
||||
VmaAllocationCreateInfo allocation_info = {};
|
||||
allocation_info.flags = 0;
|
||||
allocation_info.usage = memory_usage;
|
||||
// Usage is specified via usage field. Others are ignored.
|
||||
allocation_info.requiredFlags = 0;
|
||||
allocation_info.preferredFlags = 0;
|
||||
allocation_info.memoryTypeBits = 0;
|
||||
allocation_info.pool = VK_NULL_HANDLE;
|
||||
allocation_info.pUserData = nullptr;
|
||||
|
||||
VkImage image;
|
||||
VkResult result = vmaCreateImage(state.allocator,
|
||||
reinterpret_cast<const VkImageCreateInfo *>(&image_info),
|
||||
&allocation_info, &image, &allocation, nullptr);
|
||||
assert(result == VK_SUCCESS);
|
||||
assert(allocation != VK_NULL_HANDLE);
|
||||
assert(image != VK_NULL_HANDLE);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void destroy_image(VulkanState &state, vk::Image image, VmaAllocation allocation) {
|
||||
vmaDestroyImage(state.allocator, image, allocation);
|
||||
}
|
||||
|
||||
bool create(SDL_Window *window, std::unique_ptr<renderer::State> &state) {
|
||||
auto &vulkan_state = dynamic_cast<VulkanState &>(*state);
|
||||
|
||||
bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state, const char *base_path) {
|
||||
// Create Instance
|
||||
{
|
||||
vk::ApplicationInfo app_info(
|
||||
app_name, // App Name
|
||||
0, // App Version
|
||||
org_name, // Engine Name, using org instead.
|
||||
0, // Engine Version
|
||||
VK_API_VERSION_1_0);
|
||||
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
|
||||
|
||||
vk::InstanceCreateInfo instance_info(
|
||||
vk::InstanceCreateFlags(), // No Flags
|
||||
&app_info, // App Info
|
||||
instance_layers.size(), instance_layers.data(), // No Layers
|
||||
instance_extensions.size(), instance_extensions.data() // No Extensions
|
||||
);
|
||||
vk::ApplicationInfo app_info{
|
||||
.pApplicationName = app_name, // App Name
|
||||
.applicationVersion = VK_MAKE_API_VERSION(0, 0, 0, 1), // App Version
|
||||
.pEngineName = org_name, // Engine Name, using org instead.
|
||||
.engineVersion = VK_MAKE_API_VERSION(0, 0, 0, 1), // Engine Version
|
||||
.apiVersion = VK_API_VERSION_1_0
|
||||
};
|
||||
|
||||
vulkan_state.instance = vk::createInstance(instance_info);
|
||||
if (!vulkan_state.instance) {
|
||||
LOG_ERROR("Failed to create Vulkan instance.");
|
||||
unsigned int instance_req_ext_count;
|
||||
if (!SDL_Vulkan_GetInstanceExtensions(window, &instance_req_ext_count, nullptr)) {
|
||||
LOG_ERROR("Could not get required extensions");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create Surface
|
||||
{
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
bool surface_error = SDL_Vulkan_CreateSurface(window, vulkan_state.instance, &surface);
|
||||
if (!surface_error) {
|
||||
const char *error = SDL_GetError();
|
||||
LOG_ERROR("Failed to create vulkan surface. SDL Error: {}.", error);
|
||||
return false;
|
||||
std::vector<const char *> instance_extensions;
|
||||
instance_extensions.resize(instance_req_ext_count);
|
||||
SDL_Vulkan_GetInstanceExtensions(window, &instance_req_ext_count, instance_extensions.data());
|
||||
|
||||
// look if we can use the validation layer
|
||||
bool has_debug_extension = false;
|
||||
bool has_validation_layer = false;
|
||||
const std::string debug_extension = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
|
||||
for (const vk::ExtensionProperties &prop : vk::enumerateInstanceExtensionProperties()) {
|
||||
if (std::string(prop.extensionName.data()) == debug_extension) {
|
||||
has_debug_extension = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
vulkan_state.surface = vk::SurfaceKHR(surface);
|
||||
if (!vulkan_state.surface) {
|
||||
LOG_ERROR("Failed to create Vulkan surface.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Select Physical Device
|
||||
{
|
||||
std::vector<vk::PhysicalDevice> physical_devices = vulkan_state.instance.enumeratePhysicalDevices();
|
||||
|
||||
for (const auto &device : physical_devices) {
|
||||
vk::PhysicalDeviceProperties properties = device.getProperties();
|
||||
vk::PhysicalDeviceFeatures features = device.getFeatures();
|
||||
vk::SurfaceCapabilitiesKHR capabilities = device.getSurfaceCapabilitiesKHR(vulkan_state.surface);
|
||||
if (device_is_compatible(properties, features, capabilities)) {
|
||||
vulkan_state.physical_device = device;
|
||||
vulkan_state.physical_device_properties = properties;
|
||||
vulkan_state.physical_device_features = features;
|
||||
vulkan_state.physical_device_surface_capabilities = capabilities;
|
||||
vulkan_state.physical_device_surface_formats = device.getSurfaceFormatsKHR(vulkan_state.surface);
|
||||
vulkan_state.physical_device_memory = device.getMemoryProperties();
|
||||
vulkan_state.physical_device_queue_families = device.getQueueFamilyProperties();
|
||||
const std::string validation_layer = "VK_LAYER_KHRONOS_validation";
|
||||
for (const vk::LayerProperties &layer : vk::enumerateInstanceLayerProperties()) {
|
||||
if (std::string(layer.layerName.data()) == validation_layer) {
|
||||
has_validation_layer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vulkan_state.physical_device) {
|
||||
LOG_ERROR("Failed to select Vulkan physical device.");
|
||||
return false;
|
||||
std::vector<const char *> instance_layers;
|
||||
if (has_debug_extension && has_validation_layer) {
|
||||
LOG_INFO("Enabling vulkan validation layers (has a performance impact but allows better error messages)");
|
||||
instance_extensions.push_back(debug_extension.c_str());
|
||||
instance_layers.push_back(validation_layer.c_str());
|
||||
}
|
||||
|
||||
vk::InstanceCreateInfo instance_info{
|
||||
.pApplicationInfo = &app_info
|
||||
};
|
||||
instance_info.setPEnabledLayerNames(instance_layers);
|
||||
instance_info.setPEnabledExtensionNames(instance_extensions);
|
||||
|
||||
instance = vk::createInstance(instance_info);
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance);
|
||||
|
||||
if (has_debug_extension && has_validation_layer) {
|
||||
vk::DebugUtilsMessengerCreateInfoEXT debug_info{
|
||||
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose
|
||||
| vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
|
||||
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral
|
||||
| vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
|
||||
.pfnUserCallback = debug_callback
|
||||
};
|
||||
debug_messenger = instance.createDebugUtilsMessengerEXT(debug_info);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Surface
|
||||
if (!screen_renderer.create(window))
|
||||
return false;
|
||||
|
||||
// Select Physical Device
|
||||
{
|
||||
std::vector<vk::PhysicalDevice> physical_devices = instance.enumeratePhysicalDevices();
|
||||
|
||||
if (gpu_idx != 0) {
|
||||
// force choose the gpu
|
||||
physical_device = physical_devices[gpu_idx - 1];
|
||||
} else {
|
||||
// choose a suitable gpu
|
||||
for (const auto &device : physical_devices) {
|
||||
if (device_is_compatible(device)) {
|
||||
physical_device = device;
|
||||
|
||||
// if it is an integrated gpu, try to find a discrete one
|
||||
if (device.getProperties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
physical_device_properties = physical_device.getProperties();
|
||||
physical_device_features = physical_device.getFeatures();
|
||||
physical_device_memory = physical_device.getMemoryProperties();
|
||||
physical_device_queue_families = physical_device.getQueueFamilyProperties();
|
||||
|
||||
if (!physical_device) {
|
||||
LOG_ERROR("Failed to select Vulkan physical device.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("Vulkan device: {}", physical_device_properties.deviceName);
|
||||
LOG_INFO("Driver version: {}", get_driver_version(physical_device_properties.vendorID, physical_device_properties.driverVersion));
|
||||
}
|
||||
|
||||
bool support_dedicated_allocations = false;
|
||||
// Create Device
|
||||
{
|
||||
std::vector<vk::DeviceQueueCreateInfo> queue_infos;
|
||||
std::vector<std::vector<float>> queue_priorities;
|
||||
if (!select_queues(vulkan_state, queue_infos, queue_priorities)) {
|
||||
if (!select_queues(*this, queue_infos, queue_priorities)) {
|
||||
LOG_ERROR("Failed to select proper Vulkan queues. This is likely a bug.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vulkan_state.physical_device.getSurfaceSupportKHR(
|
||||
vulkan_state.general_family_index, vulkan_state.surface)) {
|
||||
if (!physical_device.getSurfaceSupportKHR(
|
||||
general_family_index, screen_renderer.surface)) {
|
||||
LOG_ERROR("Failed to select a Vulkan queue that supports presentation. This is likely a bug.");
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::DeviceCreateInfo device_info(
|
||||
vk::DeviceCreateFlags(), // No Flags
|
||||
queue_infos.size(), queue_infos.data(), // No Queues
|
||||
device_layers.size(), device_layers.data(), // No Layers
|
||||
device_extensions.size(), device_extensions.data(), // No Extensions
|
||||
&required_features);
|
||||
// use these features (because they are used by the vita GPU) if they are available
|
||||
vk::PhysicalDeviceFeatures enabled_features{
|
||||
.fillModeNonSolid = physical_device_features.fillModeNonSolid,
|
||||
.wideLines = physical_device_features.wideLines,
|
||||
.samplerAnisotropy = physical_device_features.samplerAnisotropy,
|
||||
};
|
||||
|
||||
vulkan_state.device = vulkan_state.physical_device.createDevice(device_info);
|
||||
if (!vulkan_state.device) {
|
||||
LOG_ERROR("Failed to create a Vulkan device.");
|
||||
return false;
|
||||
// look for optional extensions
|
||||
std::vector<const char *> device_extensions(required_device_extensions);
|
||||
bool temp_bool;
|
||||
const std::map<std::string, bool *> optional_extensions = {
|
||||
{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, &temp_bool },
|
||||
// can be used by vma to improve perfomance
|
||||
{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, &support_dedicated_allocations },
|
||||
};
|
||||
|
||||
for (const vk::ExtensionProperties &ext : physical_device.enumerateDeviceExtensionProperties()) {
|
||||
const std::string extension_name = std::string(ext.extensionName.data());
|
||||
auto it = optional_extensions.find(extension_name);
|
||||
if (it != optional_extensions.end()) {
|
||||
// this extension is available on the GPU
|
||||
*it->second = true;
|
||||
device_extensions.push_back(it->first.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// We use subpass input to get something similar to direct fragcolor access (there is no difference for the shader)
|
||||
features.direct_fragcolor = true;
|
||||
|
||||
vk::DeviceCreateInfo device_info{
|
||||
.pEnabledFeatures = &enabled_features
|
||||
};
|
||||
device_info.setQueueCreateInfos(queue_infos);
|
||||
device_info.setPEnabledExtensionNames(device_extensions);
|
||||
|
||||
device = physical_device.createDevice(device_info);
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(device);
|
||||
}
|
||||
|
||||
// Get Queues
|
||||
for (uint32_t a = 0; a < vulkan_state.physical_device_queue_families[vulkan_state.general_family_index].queueCount; a++) {
|
||||
vulkan_state.general_queues.push_back(
|
||||
vulkan_state.device.getQueue(vulkan_state.general_family_index, a));
|
||||
}
|
||||
|
||||
for (uint32_t a = 0; a < vulkan_state.physical_device_queue_families[vulkan_state.transfer_family_index].queueCount; a++) {
|
||||
vulkan_state.transfer_queues.push_back(
|
||||
vulkan_state.device.getQueue(vulkan_state.transfer_family_index, a));
|
||||
}
|
||||
general_queue = device.getQueue(general_family_index, 0);
|
||||
transfer_queue = device.getQueue(transfer_family_index, 0);
|
||||
|
||||
// Create Command Pools
|
||||
{
|
||||
vk::CommandPoolCreateInfo general_pool_info(
|
||||
vk::CommandPoolCreateFlagBits::eResetCommandBuffer, // Flags
|
||||
vulkan_state.general_family_index // Queue Family Index
|
||||
);
|
||||
vk::CommandPoolCreateInfo general_pool_info{
|
||||
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, // Flags
|
||||
.queueFamilyIndex = general_family_index // Queue Family Index
|
||||
};
|
||||
|
||||
vk::CommandPoolCreateInfo transfer_pool_info(
|
||||
vk::CommandPoolCreateFlagBits::eTransient, // Flags
|
||||
vulkan_state.transfer_family_index // Queue Family Index
|
||||
);
|
||||
vk::CommandPoolCreateInfo transfer_pool_info{
|
||||
.flags = vk::CommandPoolCreateFlagBits::eTransient, // Flags
|
||||
.queueFamilyIndex = transfer_family_index // Queue Family Index
|
||||
};
|
||||
|
||||
vulkan_state.general_command_pool = vulkan_state.device.createCommandPool(general_pool_info);
|
||||
if (!vulkan_state.general_command_pool) {
|
||||
LOG_ERROR("Failed to create general command pool.");
|
||||
return false;
|
||||
}
|
||||
vulkan_state.transfer_command_pool = vulkan_state.device.createCommandPool(transfer_pool_info);
|
||||
if (!vulkan_state.transfer_command_pool) {
|
||||
LOG_ERROR("Failed to create transfer command pool.");
|
||||
return false;
|
||||
}
|
||||
|
||||
vulkan_state.general_command_buffer = create_command_buffer(vulkan_state, CommandType::General);
|
||||
general_command_pool = device.createCommandPool(general_pool_info);
|
||||
transfer_command_pool = device.createCommandPool(transfer_pool_info);
|
||||
}
|
||||
|
||||
// Allocate Memory for Images and Buffers
|
||||
{
|
||||
VmaAllocatorCreateInfo allocator_info = {};
|
||||
allocator_info.flags = 0;
|
||||
allocator_info.physicalDevice = vulkan_state.physical_device;
|
||||
allocator_info.device = vulkan_state.device;
|
||||
allocator_info.preferredLargeHeapBlockSize = 0;
|
||||
allocator_info.pAllocationCallbacks = nullptr;
|
||||
allocator_info.pDeviceMemoryCallbacks = nullptr;
|
||||
allocator_info.frameInUseCount = 0;
|
||||
allocator_info.pHeapSizeLimit = nullptr;
|
||||
allocator_info.pVulkanFunctions = nullptr; // VMA_STATIC_VULKAN_FUNCTIONS 1 is default I think
|
||||
allocator_info.pRecordSettings = nullptr;
|
||||
vma::VulkanFunctions vulkan_functions{
|
||||
.vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr
|
||||
};
|
||||
|
||||
VkResult result = vmaCreateAllocator(&allocator_info, &vulkan_state.allocator);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create VMA allocator. VMA result: {}.", static_cast<uint32_t>(result));
|
||||
return false;
|
||||
}
|
||||
if (vulkan_state.allocator == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Failed to create VMA allocator.");
|
||||
return false;
|
||||
}
|
||||
vma::AllocatorCreateInfo allocator_info = {
|
||||
.physicalDevice = physical_device,
|
||||
.device = device,
|
||||
.pVulkanFunctions = &vulkan_functions,
|
||||
.instance = instance,
|
||||
.vulkanApiVersion = VK_API_VERSION_1_0,
|
||||
};
|
||||
|
||||
if (support_dedicated_allocations)
|
||||
allocator_info.flags |= vma::AllocatorCreateFlagBits::eKhrDedicatedAllocation;
|
||||
|
||||
allocator = vma::createAllocator(allocator_info);
|
||||
}
|
||||
|
||||
int width, height;
|
||||
SDL_Vulkan_GetDrawableSize(window, &width, &height);
|
||||
resize_swapchain(vulkan_state, vk::Extent2D(width, height));
|
||||
if (!screen_renderer.setup(base_path))
|
||||
return false;
|
||||
|
||||
pipeline_cache.init();
|
||||
texture_cache.backend = ¤t_backend;
|
||||
texture::init(texture_cache, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void close(std::unique_ptr<renderer::State> &state) {
|
||||
auto &vulkan_state = reinterpret_cast<VulkanState &>(*state);
|
||||
void VKState::cleanup() {
|
||||
device.waitIdle();
|
||||
|
||||
vulkan_state.device.waitIdle();
|
||||
screen_renderer.cleanup();
|
||||
|
||||
vmaDestroyAllocator(vulkan_state.allocator);
|
||||
allocator.destroy();
|
||||
|
||||
vulkan_state.device.destroy(vulkan_state.swapchain);
|
||||
vulkan_state.instance.destroy(vulkan_state.surface);
|
||||
device.destroy(general_command_pool);
|
||||
device.destroy(transfer_command_pool);
|
||||
|
||||
free_command_buffer(vulkan_state, CommandType::General, vulkan_state.general_command_buffer);
|
||||
|
||||
vulkan_state.device.destroy(vulkan_state.general_command_pool);
|
||||
vulkan_state.device.destroy(vulkan_state.transfer_command_pool);
|
||||
|
||||
vulkan_state.device.destroy();
|
||||
vulkan_state.instance.destroy();
|
||||
device.destroy();
|
||||
instance.destroy();
|
||||
}
|
||||
|
||||
bool VulkanState::init(const char *base_path, const bool hashless_texture_cache) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanState::render_frame(const SceFVector2 &viewport_pos, const SceFVector2 &viewport_size, const DisplayState &display,
|
||||
void VKState::render_frame(const SceFVector2 &viewport_pos, const SceFVector2 &viewport_size, const DisplayState &display,
|
||||
const GxmState &gxm, MemState &mem) {
|
||||
if (!display.frame.base)
|
||||
return;
|
||||
|
||||
if (!screen_renderer.acquire_swapchain_image())
|
||||
return;
|
||||
|
||||
// Check if the surface exists
|
||||
std::array<float, 4> uvs = { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
SceFVector2 texture_size;
|
||||
|
||||
vk::ImageLayout layout = vk::ImageLayout::eGeneral;
|
||||
vk::ImageView surface_handle = surface_cache.sourcing_color_surface_for_presentation(
|
||||
display.frame.base, display.frame.image_size.x, display.frame.image_size.y, display.frame.pitch, uvs, this->res_multiplier, texture_size);
|
||||
|
||||
if (!surface_handle) {
|
||||
vkutil::Image &vita_surface = screen_renderer.vita_surface[screen_renderer.swapchain_image_idx];
|
||||
if (display.frame.image_size.x != vita_surface.width || display.frame.image_size.y != vita_surface.height) {
|
||||
// re-create the image
|
||||
vita_surface.destroy();
|
||||
vita_surface = vkutil::Image(allocator, display.frame.image_size.x, display.frame.image_size.y, vk::Format::eR8G8B8A8Unorm);
|
||||
vita_surface.init_image(vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst);
|
||||
}
|
||||
|
||||
// copy surface to staging buffer
|
||||
const vk::DeviceSize texture_data_size = display.frame.pitch * display.frame.image_size.y * 4;
|
||||
memcpy(screen_renderer.vita_surface_staging_info.pMappedData, display.frame.base.get(mem), texture_data_size);
|
||||
|
||||
// copy staging buffer to image
|
||||
auto &cmd_buffer = screen_renderer.current_cmd_buffer;
|
||||
vita_surface.transition_to_discard(cmd_buffer, vkutil::ImageLayout::TransferDst);
|
||||
vk::BufferImageCopy region{
|
||||
.bufferOffset = 0,
|
||||
.bufferRowLength = display.frame.pitch,
|
||||
.bufferImageHeight = static_cast<uint32_t>(display.frame.image_size.y),
|
||||
.imageSubresource = vkutil::color_subresource_layer,
|
||||
.imageOffset = { 0, 0, 0 },
|
||||
.imageExtent = { static_cast<uint32_t>(display.frame.image_size.x), static_cast<uint32_t>(display.frame.image_size.y), 1 }
|
||||
};
|
||||
cmd_buffer.copyBufferToImage(screen_renderer.vita_surface_staging, vita_surface.image, vk::ImageLayout::eTransferDstOptimal, region);
|
||||
|
||||
vita_surface.transition_to(cmd_buffer, vkutil::ImageLayout::SampledImage);
|
||||
|
||||
surface_handle = vita_surface.view;
|
||||
texture_size = { static_cast<float>(display.frame.image_size.x), static_cast<float>(display.frame.image_size.y) };
|
||||
layout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
|
||||
screen_renderer.render(surface_handle, layout, uvs, texture_size);
|
||||
}
|
||||
|
||||
void VulkanState::set_fxaa(bool enable_fxaa) {
|
||||
void VKState::swap_window(SDL_Window *window) {
|
||||
screen_renderer.swap_window();
|
||||
|
||||
// look once a frame if we need to save the pipeline cache
|
||||
const auto time_s = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
if (time_s >= pipeline_cache.next_pipeline_cache_save) {
|
||||
pipeline_cache.save_pipeline_cache();
|
||||
|
||||
pipeline_cache.next_pipeline_cache_save = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
}
|
||||
|
||||
int VulkanState::get_max_anisotropic_filtering() {
|
||||
return 1;
|
||||
void VKState::set_fxaa(bool enable_fxaa) {
|
||||
screen_renderer.enable_fxaa = enable_fxaa;
|
||||
}
|
||||
|
||||
void VulkanState::set_anisotropic_filtering(int anisotropic_filtering) {
|
||||
int VKState::get_max_anisotropic_filtering() {
|
||||
return static_cast<int>(physical_device_properties.limits.maxSamplerAnisotropy);
|
||||
}
|
||||
|
||||
void VKState::set_anisotropic_filtering(int anisotropic_filtering) {
|
||||
texture_cache.anisotropic_filtering = anisotropic_filtering;
|
||||
}
|
||||
|
||||
std::vector<std::string> VKState::get_gpu_list() {
|
||||
const std::vector<vk::PhysicalDevice> gpus = instance.enumeratePhysicalDevices();
|
||||
|
||||
std::vector<std::string> gpu_list;
|
||||
// First value is always automatic
|
||||
gpu_list.push_back("Automatic");
|
||||
for (const vk::PhysicalDevice gpu : gpus)
|
||||
gpu_list.push_back(std::string(gpu.getProperties().deviceName.data()));
|
||||
|
||||
return gpu_list;
|
||||
}
|
||||
|
||||
void VKState::precompile_shader(const ShadersHash &hash) {
|
||||
Sha256Hash empty_hash{};
|
||||
if (hash.vert != empty_hash) {
|
||||
pipeline_cache.precompile_shader(hash.vert);
|
||||
}
|
||||
if (hash.frag != empty_hash) {
|
||||
pipeline_cache.precompile_shader(hash.frag);
|
||||
}
|
||||
|
||||
programs_count_pre_compiled++;
|
||||
LOG_INFO("Program Compiled {}/{}", programs_count_pre_compiled, shaders_cache_hashs.size());
|
||||
}
|
||||
|
||||
void VKState::preclose_action() {
|
||||
// make sure we are in a game
|
||||
if (!title_id[0])
|
||||
return;
|
||||
|
||||
pipeline_cache.save_pipeline_cache();
|
||||
}
|
||||
} // namespace renderer::vulkan
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/vulkan/functions.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
|
||||
#include <config/state.h>
|
||||
#include <spdlog/fmt/bin_to_hex.h>
|
||||
#include <util/log.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
void set_uniform_buffer(VKContext &context, const ShaderProgram *program, const bool vertex_shader, const int block_num, const int size, const uint8_t *data) {
|
||||
auto offset = program->uniform_buffer_data_offsets.at(block_num);
|
||||
if (offset == static_cast<std::uint32_t>(-1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t data_size_upload = std::min<uint32_t>(size, program->uniform_buffer_sizes.at(block_num) * 4);
|
||||
const uint32_t offset_start_upload = offset * 4;
|
||||
|
||||
if (vertex_shader) {
|
||||
if (!context.vertex_uniform_storage_allocated) {
|
||||
// Allocate a region for it. Don't worry though, when the shader program is changed
|
||||
context.vertex_uniform_stream_ring_buffer.allocate(program->max_total_uniform_buffer_storage * 4);
|
||||
context.vertex_uniform_storage_allocated = true;
|
||||
}
|
||||
|
||||
context.vertex_uniform_stream_ring_buffer.copy(context.prerender_cmd, data_size_upload, data, offset_start_upload);
|
||||
} else {
|
||||
if (!context.fragment_uniform_storage_allocated) {
|
||||
// Allocate a region for it. Don't worry though, when the shader program is changed
|
||||
context.fragment_uniform_stream_ring_buffer.allocate(program->max_total_uniform_buffer_storage * 4);
|
||||
context.fragment_uniform_storage_allocated = true;
|
||||
}
|
||||
|
||||
context.fragment_uniform_stream_ring_buffer.copy(context.prerender_cmd, data_size_upload, data, offset_start_upload);
|
||||
}
|
||||
}
|
||||
|
||||
void new_frame(VKContext &context) {
|
||||
context.frame_timestamp++;
|
||||
context.current_frame_idx = context.frame_timestamp % MAX_FRAMES_RENDERING;
|
||||
|
||||
vk::Device device = context.state.device;
|
||||
FrameObject &frame = context.frame();
|
||||
|
||||
// wait on all fences still present to make sure
|
||||
if (!frame.rendered_fences.empty()) {
|
||||
// wait for the fences, then reset them
|
||||
constexpr uint64_t max_time = std::numeric_limits<uint64_t>::max();
|
||||
device.waitForFences(frame.rendered_fences, VK_TRUE, max_time);
|
||||
device.resetFences(frame.rendered_fences);
|
||||
frame.rendered_fences.clear();
|
||||
}
|
||||
|
||||
device.resetCommandPool(frame.command_pool);
|
||||
device.resetDescriptorPool(frame.descriptor_pool);
|
||||
|
||||
// deferred destruction of the objects
|
||||
frame.destroy_queue.destroy_objects();
|
||||
|
||||
context.last_vert_texture_count = ~0;
|
||||
context.last_frag_texture_count = ~0;
|
||||
}
|
||||
|
||||
void update_sync_target(SceGxmSyncObject *sync, VKRenderTarget *target) {
|
||||
SyncExtraData *extra = reinterpret_cast<SyncExtraData *>(sync->extra);
|
||||
extra->render_target = target;
|
||||
}
|
||||
|
||||
void update_sync_signal(SceGxmSyncObject *sync) {
|
||||
SyncExtraData *extra = reinterpret_cast<SyncExtraData *>(sync->extra);
|
||||
if (extra->render_target) {
|
||||
// add the render_target current fence to the list of fences
|
||||
// this should not need a mutex (the display thread should not be able to clear the fence list now)
|
||||
extra->fences.push_back(extra->render_target->fences[extra->render_target->fence_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_bind_descriptors(VKContext &context, MemState &mem) {
|
||||
VKState &state = context.state;
|
||||
|
||||
std::array<vk::DescriptorSet, 4> descriptors;
|
||||
descriptors[0] = context.global_set;
|
||||
descriptors[1] = context.rendertarget_set;
|
||||
|
||||
const uint16_t vertex_textures_count = reinterpret_cast<VertexProgram *>(
|
||||
context.record.vertex_program.get(mem)->renderer_data.get())
|
||||
->texture_count;
|
||||
const uint16_t fragment_texture_count = reinterpret_cast<VKFragmentProgram *>(
|
||||
context.record.fragment_program.get(mem)->renderer_data.get())
|
||||
->texture_count;
|
||||
|
||||
vk::PipelineLayout pipeline_layout = state.pipeline_cache.pipeline_layouts[vertex_textures_count][fragment_texture_count];
|
||||
|
||||
// try to use last descriptor if it still matches
|
||||
bool need_vert_descr = (vertex_textures_count != context.last_vert_texture_count);
|
||||
bool need_frag_descr = (fragment_texture_count != context.last_frag_texture_count);
|
||||
|
||||
context.last_vert_texture_count = vertex_textures_count;
|
||||
context.last_frag_texture_count = fragment_texture_count;
|
||||
|
||||
{
|
||||
vk::DescriptorSetAllocateInfo descr_set_info{
|
||||
.descriptorPool = context.frame().descriptor_pool
|
||||
};
|
||||
std::vector<vk::DescriptorSetLayout> layouts;
|
||||
if (need_vert_descr)
|
||||
layouts.push_back(state.pipeline_cache.vertex_textures_layout[vertex_textures_count]);
|
||||
if (need_frag_descr)
|
||||
layouts.push_back(state.pipeline_cache.fragment_textures_layout[fragment_texture_count]);
|
||||
|
||||
std::vector<vk::DescriptorSet> sets;
|
||||
if (!layouts.empty()) {
|
||||
descr_set_info.setSetLayouts(layouts);
|
||||
sets = state.device.allocateDescriptorSets(descr_set_info);
|
||||
}
|
||||
|
||||
int set_idx = 0;
|
||||
if (need_vert_descr) {
|
||||
context.last_vert_texture_descriptor = sets[set_idx++];
|
||||
}
|
||||
descriptors[2] = context.last_vert_texture_descriptor;
|
||||
|
||||
if (need_frag_descr) {
|
||||
context.last_frag_texture_descriptor = sets[set_idx++];
|
||||
}
|
||||
descriptors[3] = context.last_frag_texture_descriptor;
|
||||
}
|
||||
|
||||
// bind textures
|
||||
std::array<vk::WriteDescriptorSet, 16> write_descrs;
|
||||
// some default sampler in case a slot has never been set and we read a slot with higher idx
|
||||
vk::DescriptorImageInfo default_image_info{
|
||||
.sampler = context.default_image.sampler,
|
||||
.imageView = context.default_image.view,
|
||||
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal
|
||||
};
|
||||
|
||||
// vertex
|
||||
if (need_vert_descr) {
|
||||
for (uint32_t i = 0; i < vertex_textures_count; i++) {
|
||||
write_descrs[i] = vk::WriteDescriptorSet{
|
||||
.dstSet = descriptors[2],
|
||||
.dstBinding = i,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
};
|
||||
write_descrs[i].setImageInfo(context.vertex_textures[i].sampler ? context.vertex_textures[i] : default_image_info);
|
||||
}
|
||||
state.device.updateDescriptorSets(vertex_textures_count, write_descrs.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
// fragment
|
||||
if (need_frag_descr) {
|
||||
for (uint32_t i = 0; i < fragment_texture_count; i++) {
|
||||
write_descrs[i] = vk::WriteDescriptorSet{
|
||||
.dstSet = descriptors[3],
|
||||
.dstBinding = i,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
};
|
||||
write_descrs[i].setImageInfo(context.fragment_textures[i].sampler ? context.fragment_textures[i] : default_image_info);
|
||||
}
|
||||
state.device.updateDescriptorSets(fragment_texture_count, write_descrs.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
uint32_t dynamic_offsets[] = {
|
||||
// vertex uniform
|
||||
context.vertex_uniform_stream_ring_buffer.data_offset,
|
||||
// fragment uniform
|
||||
context.fragment_uniform_stream_ring_buffer.data_offset,
|
||||
// GXMRenderVertUniformBlock
|
||||
context.vertex_info_uniform_buffer.data_offset,
|
||||
// GXMRenderFragUniformBlock
|
||||
context.fragment_info_uniform_buffer.data_offset
|
||||
};
|
||||
|
||||
context.render_cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptors, dynamic_offsets);
|
||||
}
|
||||
|
||||
static void bind_vertex_streams(VKContext &context, MemState &mem) {
|
||||
GxmRecordState &state = context.record;
|
||||
const SceGxmVertexProgram &vertex_program = *state.vertex_program.get(mem);
|
||||
VertexProgram *vkvert = vertex_program.renderer_data.get();
|
||||
|
||||
// we need to do another check here (the same is done in pipeline_cache)
|
||||
// because if a game (like Secret of Mana) uses two programs with the same shaders and the same vertex input stripped
|
||||
// the pipeline cache won't add stripped symbols for the second program
|
||||
if (!vkvert->stripped_symbols_checked) {
|
||||
// Insert some symbols here
|
||||
const SceGxmProgram *vertex_program_body = vertex_program.program.get(mem);
|
||||
if (vertex_program_body && (vertex_program_body->primary_reg_count != 0)) {
|
||||
for (std::size_t i = 0; i < vertex_program.attributes.size(); i++) {
|
||||
vkvert->attribute_infos.emplace(vertex_program.attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<std::uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
vkvert->stripped_symbols_checked = true;
|
||||
}
|
||||
|
||||
int max_stream_idx = -1;
|
||||
|
||||
for (const SceGxmVertexAttribute &attribute : vertex_program.attributes) {
|
||||
if (vkvert->attribute_infos.find(attribute.regIndex) == vkvert->attribute_infos.end())
|
||||
continue;
|
||||
max_stream_idx = std::max<int>(max_stream_idx, attribute.streamIndex);
|
||||
}
|
||||
max_stream_idx++;
|
||||
|
||||
if (max_stream_idx == 0)
|
||||
return;
|
||||
|
||||
for (std::size_t i = 0; i < max_stream_idx; i++) {
|
||||
if (state.vertex_streams[i].data) {
|
||||
context.vertex_stream_ring_buffer.allocate(context.prerender_cmd, state.vertex_streams[i].size, state.vertex_streams[i].data);
|
||||
|
||||
context.vertex_buffer_offsets[i] = context.vertex_stream_ring_buffer.data_offset;
|
||||
delete[] state.vertex_streams[i].data;
|
||||
|
||||
state.vertex_streams[i].data = nullptr;
|
||||
state.vertex_streams[i].size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
vk::Buffer buffers[SCE_GXM_MAX_VERTEX_STREAMS];
|
||||
std::fill_n(buffers, max_stream_idx, context.vertex_stream_ring_buffer.handle());
|
||||
|
||||
context.render_cmd.bindVertexBuffers(0, max_stream_idx, buffers, context.vertex_buffer_offsets.data());
|
||||
}
|
||||
|
||||
void draw(VKContext &context, SceGxmPrimitiveType type, SceGxmIndexFormat format,
|
||||
void *indices, size_t count, uint32_t instance_count, MemState &mem, const Config &config) {
|
||||
// do we need to check for a pipeline change?
|
||||
if (context.refresh_pipeline || !context.in_renderpass || type != context.last_primitive) {
|
||||
context.refresh_pipeline = false;
|
||||
context.last_primitive = type;
|
||||
vk::Pipeline new_pipeline = context.state.pipeline_cache.retrieve_pipeline(context, type, mem);
|
||||
|
||||
if (!context.in_renderpass || new_pipeline != context.current_pipeline) {
|
||||
context.current_pipeline = new_pipeline;
|
||||
if (!context.in_renderpass)
|
||||
context.start_render_pass();
|
||||
|
||||
context.render_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, context.current_pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
const SceGxmFragmentProgram &gxm_fragment_program = *context.record.fragment_program.get(mem);
|
||||
const SceGxmProgram &fragment_program_gxp = *gxm_fragment_program.program.get(mem);
|
||||
if (fragment_program_gxp.is_native_color()) {
|
||||
// the fragment shader is using programmable blending
|
||||
vk::ImageMemoryBarrier barrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eInputAttachmentRead,
|
||||
.oldLayout = vk::ImageLayout::eGeneral,
|
||||
.newLayout = vk::ImageLayout::eGeneral,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = context.current_color_attachment->image,
|
||||
.subresourceRange = vkutil::color_subresource_range
|
||||
};
|
||||
context.render_cmd.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barrier);
|
||||
}
|
||||
|
||||
if (config.log_active_shaders) {
|
||||
const std::string hash_text_f = hex_string(context.record.fragment_program.get(mem)->renderer_data->hash);
|
||||
const std::string hash_text_v = hex_string(context.record.vertex_program.get(mem)->renderer_data->hash);
|
||||
|
||||
LOG_DEBUG("\nVertex : {}\nFragment: {}", hash_text_v, hash_text_f);
|
||||
LOG_DEBUG("Vertex default uniform buffer: {:a}\n", spdlog::to_hex(context.ubo_data[0].begin(), context.ubo_data[0].end(), 16));
|
||||
LOG_DEBUG("Fragment default uniform buffer: {:a}\n", spdlog::to_hex(context.ubo_data[SCE_GXM_REAL_MAX_UNIFORM_BUFFER].begin(), context.ubo_data[SCE_GXM_REAL_MAX_UNIFORM_BUFFER].end(), 16));
|
||||
}
|
||||
|
||||
shader::RenderVertUniformBlock &vert_ublock = context.current_vert_render_info;
|
||||
vert_ublock.viewport_flip = context.record.viewport_flip;
|
||||
vert_ublock.viewport_flag = (context.record.viewport_flat) ? 0.0f : 1.0f;
|
||||
vert_ublock.z_offset = context.record.z_offset;
|
||||
vert_ublock.z_scale = context.record.z_scale;
|
||||
vert_ublock.screen_width = static_cast<float>(context.render_target->width);
|
||||
vert_ublock.screen_height = static_cast<float>(context.render_target->height);
|
||||
|
||||
if (memcmp(&context.previous_vert_info, &vert_ublock, sizeof(shader::RenderVertUniformBlock)) != 0) {
|
||||
context.vertex_info_uniform_buffer.allocate(context.prerender_cmd, sizeof(shader::RenderVertUniformBlock), &vert_ublock);
|
||||
context.previous_vert_info = vert_ublock;
|
||||
}
|
||||
|
||||
shader::RenderFragUniformBlock &frag_ublock = context.current_frag_render_info;
|
||||
|
||||
frag_ublock.writing_mask = context.record.writing_mask;
|
||||
frag_ublock.use_raw_image = 0;
|
||||
frag_ublock.res_multiplier = context.state.res_multiplier;
|
||||
|
||||
if (memcmp(&context.previous_frag_info, &frag_ublock, sizeof(shader::RenderFragUniformBlock)) != 0) {
|
||||
context.fragment_info_uniform_buffer.allocate(context.prerender_cmd, sizeof(shader::RenderFragUniformBlock), &frag_ublock);
|
||||
context.previous_frag_info = frag_ublock;
|
||||
}
|
||||
|
||||
// create, update and bind descriptors (uniforms and textures)
|
||||
draw_bind_descriptors(context, mem);
|
||||
// bind the vertex streams
|
||||
bind_vertex_streams(context, mem);
|
||||
|
||||
// Upload index data.
|
||||
vk::IndexType index_type = (format == SCE_GXM_INDEX_FORMAT_U16) ? vk::IndexType::eUint16 : vk::IndexType::eUint32;
|
||||
const size_t index_size = (format == SCE_GXM_INDEX_FORMAT_U16) ? 2 : 4;
|
||||
const size_t index_buffer_size = index_size * count;
|
||||
|
||||
context.index_stream_ring_buffer.allocate(context.prerender_cmd, index_buffer_size, indices);
|
||||
|
||||
// we can now destroy the indices
|
||||
std::uint8_t *indices_u8 = reinterpret_cast<std::uint8_t *>(indices);
|
||||
delete[] indices_u8;
|
||||
|
||||
context.render_cmd.bindIndexBuffer(context.index_stream_ring_buffer.handle(), context.index_stream_ring_buffer.data_offset, index_type);
|
||||
|
||||
context.render_cmd.drawIndexed(count, instance_count, 0, 0, 0);
|
||||
|
||||
context.vertex_uniform_storage_allocated = false;
|
||||
context.fragment_uniform_storage_allocated = false;
|
||||
}
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,638 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "renderer/vulkan/screen_renderer.h"
|
||||
|
||||
#include <SDL_vulkan.h>
|
||||
|
||||
#include "renderer/vulkan/state.h"
|
||||
#include "util/log.h"
|
||||
#include "vkutil/vkutil.h"
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
struct screen_vertex {
|
||||
float pos[3];
|
||||
float uv[2];
|
||||
};
|
||||
|
||||
static constexpr size_t screen_vertex_size = sizeof(screen_vertex);
|
||||
static constexpr uint32_t screen_vertex_count = 4;
|
||||
|
||||
using screen_vertices_t = screen_vertex[screen_vertex_count];
|
||||
|
||||
ScreenRenderer::ScreenRenderer(VKState &state)
|
||||
: state(state) {
|
||||
}
|
||||
|
||||
bool ScreenRenderer::create(SDL_Window *window) {
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
bool surface_error = SDL_Vulkan_CreateSurface(window, state.instance, &surface);
|
||||
if (!surface_error) {
|
||||
const char *error = SDL_GetError();
|
||||
LOG_ERROR("Failed to create vulkan surface. SDL Error: {}.", error);
|
||||
return false;
|
||||
}
|
||||
this->window = window;
|
||||
this->surface = vk::SurfaceKHR(surface);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScreenRenderer::setup(const char *base_path) {
|
||||
vk::SemaphoreCreateInfo semaphore_info{};
|
||||
image_acquired_semaphore = state.device.createSemaphore(semaphore_info);
|
||||
image_ready_semaphore = state.device.createSemaphore(semaphore_info);
|
||||
|
||||
const auto surface_formats = state.physical_device.getSurfaceFormatsKHR(surface);
|
||||
bool surface_format_found = false;
|
||||
for (const auto &format : surface_formats) {
|
||||
// actually we don't care that much because we will just be copying what the game rendered
|
||||
// rgba8 or bgra8 should be the best as it matches the format output from the vita (we don't care about the swizzle)
|
||||
if ((format.format == vk::Format::eB8G8R8A8Unorm || format.format == vk::Format::eR8G8B8A8Unorm)
|
||||
&& format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
|
||||
surface_format = format;
|
||||
surface_format_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!surface_format_found)
|
||||
surface_format = surface_formats[0];
|
||||
|
||||
// preferred order : mailbox > fifo_relaxed > fifo > whatever
|
||||
// the only drawback for mailbox is that it draws more power, so maybe on a portable device use something else
|
||||
const auto present_modes = state.physical_device.getSurfacePresentModesKHR(surface);
|
||||
// this one should always be available
|
||||
present_mode = vk::PresentModeKHR::eImmediate;
|
||||
for (const auto &mode : present_modes) {
|
||||
if (mode == vk::PresentModeKHR::eMailbox) {
|
||||
present_mode = mode;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mode == vk::PresentModeKHR::eFifoRelaxed) {
|
||||
present_mode = mode;
|
||||
}
|
||||
if (present_mode == vk::PresentModeKHR::eFifoRelaxed)
|
||||
continue;
|
||||
|
||||
if (mode == vk::PresentModeKHR::eFifo) {
|
||||
present_mode = mode;
|
||||
}
|
||||
}
|
||||
LOG_INFO("Present mode: {}", vk::to_string(present_mode));
|
||||
|
||||
// part of the swapchain that does not need to be rebuilt every time
|
||||
const auto builtin_shaders_path = std::string(base_path) + "shaders-builtin/vulkan/";
|
||||
shader_vertex = vkutil::load_shader(state.device, builtin_shaders_path + "render_main.vert.spv");
|
||||
shader_fragment = vkutil::load_shader(state.device, builtin_shaders_path + "render_main.frag.spv");
|
||||
shader_fragment_fxaa = vkutil::load_shader(state.device, builtin_shaders_path + "render_main_fxaa.frag.spv");
|
||||
create_render_pass();
|
||||
|
||||
create_swapchain();
|
||||
|
||||
// these functions do not need to be called when the swapchain is resized
|
||||
create_layout_sync();
|
||||
create_surface_image();
|
||||
|
||||
if (!create_graphics_pipelines())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenRenderer::create_swapchain() {
|
||||
// refresh the capabilities
|
||||
surface_capabilities = state.physical_device.getSurfaceCapabilitiesKHR(surface);
|
||||
|
||||
if (surface_capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
|
||||
extent = surface_capabilities.currentExtent;
|
||||
} else {
|
||||
int width, height;
|
||||
SDL_Vulkan_GetDrawableSize(window, &width, &height);
|
||||
extent.width = std::clamp<uint32_t>(width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
|
||||
extent.height = std::clamp<uint32_t>(height, surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
|
||||
}
|
||||
|
||||
swapchain_size = surface_capabilities.minImageCount + 1;
|
||||
if (surface_capabilities.maxImageCount != 0)
|
||||
swapchain_size = std::min(swapchain_size, surface_capabilities.maxImageCount);
|
||||
|
||||
// Create Swapchain
|
||||
{
|
||||
vk::SwapchainCreateInfoKHR swapchain_info{
|
||||
.surface = surface,
|
||||
.minImageCount = swapchain_size,
|
||||
.imageFormat = surface_format.format,
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
.imageExtent = extent,
|
||||
.imageArrayLayers = 1,
|
||||
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
|
||||
.imageSharingMode = vk::SharingMode::eExclusive,
|
||||
.preTransform = surface_capabilities.currentTransform,
|
||||
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
||||
.presentMode = present_mode,
|
||||
.clipped = true,
|
||||
};
|
||||
|
||||
swapchain = state.device.createSwapchainKHR(swapchain_info);
|
||||
}
|
||||
|
||||
// Get Swapchain Images
|
||||
swapchain_images = state.device.getSwapchainImagesKHR(swapchain);
|
||||
swapchain_size = swapchain_images.size();
|
||||
|
||||
// Get Image views
|
||||
swapchain_views.resize(swapchain_size);
|
||||
for (uint32_t i = 0; i < swapchain_size; i++) {
|
||||
vk::ImageViewCreateInfo view_info{
|
||||
.image = swapchain_images[i],
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = surface_format.format,
|
||||
.components = vkutil::default_comp_mapping,
|
||||
.subresourceRange = vkutil::color_subresource_range
|
||||
};
|
||||
|
||||
swapchain_views[i] = state.device.createImageView(view_info);
|
||||
}
|
||||
|
||||
swapchain_framebuffers.resize(swapchain_size);
|
||||
for (int i = 0; i < swapchain_size; i++) {
|
||||
vk::FramebufferCreateInfo fb_info{
|
||||
.renderPass = render_pass,
|
||||
.width = extent.width,
|
||||
.height = extent.height,
|
||||
.layers = 1
|
||||
};
|
||||
fb_info.setAttachments(swapchain_views[i]);
|
||||
|
||||
swapchain_framebuffers[i] = state.device.createFramebuffer(fb_info);
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenRenderer::destroy_swapchain() {
|
||||
state.device.destroy(pipeline);
|
||||
|
||||
for (vk::Framebuffer framebuffer : swapchain_framebuffers)
|
||||
state.device.destroy(framebuffer);
|
||||
for (vk::ImageView view : swapchain_views)
|
||||
state.device.destroy(view);
|
||||
|
||||
state.device.destroySwapchainKHR(swapchain);
|
||||
}
|
||||
|
||||
void ScreenRenderer::cleanup() {
|
||||
for (vk::Framebuffer fb : swapchain_framebuffers)
|
||||
state.device.destroy(fb);
|
||||
|
||||
state.device.destroy(pipeline);
|
||||
state.device.destroy(pipeline_layout);
|
||||
state.device.destroy(shader_fragment);
|
||||
state.device.destroy(shader_vertex);
|
||||
state.device.destroy(render_pass);
|
||||
|
||||
for (vk::ImageView view : swapchain_views)
|
||||
state.device.destroy(view);
|
||||
state.device.destroy(swapchain);
|
||||
|
||||
state.device.destroy(image_acquired_semaphore);
|
||||
state.allocator.destroyBuffer(vao, vao_allocation);
|
||||
|
||||
state.instance.destroy(surface);
|
||||
}
|
||||
|
||||
static constexpr uint64_t next_image_timeout = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
bool ScreenRenderer::acquire_swapchain_image(bool start_render_pass) {
|
||||
vk::Result acquire_result = state.device.acquireNextImageKHR(swapchain,
|
||||
next_image_timeout, image_acquired_semaphore, vk::Fence(), &swapchain_image_idx);
|
||||
|
||||
if (acquire_result != vk::Result::eSuccess) {
|
||||
if (acquire_result == vk::Result::eErrorOutOfDateKHR || acquire_result == vk::Result::eSuboptimalKHR) {
|
||||
state.device.waitIdle();
|
||||
int width, height;
|
||||
SDL_Vulkan_GetDrawableSize(window, &width, &height);
|
||||
destroy_swapchain();
|
||||
create_swapchain();
|
||||
create_graphics_pipelines();
|
||||
need_rebuild = true;
|
||||
} else {
|
||||
LOG_WARN("Failed to get next image. Error: {}", vk::to_string(acquire_result));
|
||||
}
|
||||
|
||||
// don't set it to ~0 so that imgui can differentiate when we are in game selection and when acquiring the image failed
|
||||
swapchain_image_idx = 0xDEADBEAF;
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait for the previous frame using this image to finish
|
||||
state.device.waitForFences(fences[swapchain_image_idx], VK_TRUE, next_image_timeout);
|
||||
state.device.resetFences(fences[swapchain_image_idx]);
|
||||
|
||||
// begin the render command
|
||||
current_cmd_buffer = command_buffers[swapchain_image_idx];
|
||||
current_cmd_buffer.reset();
|
||||
|
||||
{
|
||||
vk::CommandBufferBeginInfo begin_info{
|
||||
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
|
||||
};
|
||||
current_cmd_buffer.begin(begin_info);
|
||||
}
|
||||
|
||||
if (start_render_pass) {
|
||||
vk::RenderPassBeginInfo pass_info{
|
||||
.renderPass = render_pass,
|
||||
.framebuffer = swapchain_framebuffers[swapchain_image_idx],
|
||||
.renderArea = {
|
||||
.offset = { 0, 0 },
|
||||
.extent = extent }
|
||||
};
|
||||
vk::ClearValue clear_color{
|
||||
.color = std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }
|
||||
};
|
||||
pass_info.setClearValues(clear_color);
|
||||
current_cmd_buffer.beginRenderPass(pass_info, vk::SubpassContents::eInline);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenRenderer::render(vk::ImageView image_view, vk::ImageLayout layout, std::array<float, 4> &uvs, SceFVector2 &texture_size) {
|
||||
if (swapchain_image_idx == ~0 && !acquire_swapchain_image())
|
||||
return;
|
||||
|
||||
{
|
||||
// if necessary update vao (should not happen often)
|
||||
if (uvs != last_uvs[swapchain_image_idx]) {
|
||||
screen_vertices_t vertex_buffer_data = {
|
||||
{ { 1.f, -1.f, 0.0f }, { 1.f, 0.f } },
|
||||
{ { -1.f, -1.f, 0.0f }, { 0.f, 0.f } },
|
||||
{ { 1.f, 1.f, 0.0f }, { 1.f, 1.f } },
|
||||
{ { -1.f, 1.f, 0.0f }, { 0.f, 1.f } },
|
||||
};
|
||||
vertex_buffer_data[0].uv[0] = uvs[2];
|
||||
vertex_buffer_data[0].uv[1] = uvs[1];
|
||||
|
||||
vertex_buffer_data[1].uv[0] = uvs[0];
|
||||
vertex_buffer_data[1].uv[1] = uvs[1];
|
||||
|
||||
vertex_buffer_data[2].uv[0] = uvs[2];
|
||||
vertex_buffer_data[2].uv[1] = uvs[3];
|
||||
|
||||
vertex_buffer_data[3].uv[0] = uvs[0];
|
||||
vertex_buffer_data[3].uv[1] = uvs[3];
|
||||
|
||||
current_cmd_buffer.updateBuffer(vao, swapchain_image_idx * sizeof(screen_vertices_t), sizeof(screen_vertices_t), &vertex_buffer_data);
|
||||
last_uvs[swapchain_image_idx] = uvs;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// update descriptor set
|
||||
vk::DescriptorImageInfo descr_image_info{
|
||||
.sampler = vita_surface_sampler,
|
||||
.imageView = image_view,
|
||||
.imageLayout = layout,
|
||||
};
|
||||
vk::WriteDescriptorSet write_descr{
|
||||
.dstSet = descriptor_sets[swapchain_image_idx],
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
};
|
||||
write_descr.setImageInfo(descr_image_info);
|
||||
state.device.updateDescriptorSets(write_descr, {});
|
||||
}
|
||||
|
||||
{
|
||||
vk::RenderPassBeginInfo pass_info{
|
||||
.renderPass = render_pass,
|
||||
.framebuffer = swapchain_framebuffers[swapchain_image_idx],
|
||||
.renderArea = {
|
||||
.offset = { 0, 0 },
|
||||
.extent = extent }
|
||||
};
|
||||
vk::ClearValue clear_color{
|
||||
.color = std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }
|
||||
};
|
||||
pass_info.setClearValues(clear_color);
|
||||
current_cmd_buffer.beginRenderPass(pass_info, vk::SubpassContents::eInline);
|
||||
|
||||
vk::DeviceSize offset = swapchain_image_idx * sizeof(screen_vertices_t);
|
||||
current_cmd_buffer.bindVertexBuffers(0, vao, offset);
|
||||
|
||||
current_cmd_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, enable_fxaa ? pipeline_fxaa : pipeline);
|
||||
current_cmd_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_sets[swapchain_image_idx], {});
|
||||
|
||||
if (enable_fxaa) {
|
||||
std::array<float, 2> inv_size = { 1 / texture_size.x, 1 / texture_size.y };
|
||||
current_cmd_buffer.pushConstants(pipeline_layout, vk::ShaderStageFlagBits::eFragment, 0, 2 * sizeof(float), inv_size.data());
|
||||
}
|
||||
|
||||
current_cmd_buffer.draw(4, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenRenderer::swap_window() {
|
||||
if (!current_cmd_buffer)
|
||||
return;
|
||||
|
||||
// first submit the command buffer
|
||||
current_cmd_buffer.endRenderPass();
|
||||
current_cmd_buffer.end();
|
||||
vk::SubmitInfo submit_info{};
|
||||
std::array<vk::Semaphore, 1> wait_semaphores = { image_acquired_semaphore };
|
||||
std::array<vk::PipelineStageFlags, 1> dst_masks
|
||||
= { vk::PipelineStageFlagBits::eColorAttachmentOutput };
|
||||
submit_info.setWaitSemaphores(wait_semaphores);
|
||||
submit_info.setWaitDstStageMask(dst_masks);
|
||||
submit_info.setSignalSemaphores(image_ready_semaphore);
|
||||
submit_info.setCommandBuffers(current_cmd_buffer);
|
||||
state.general_queue.submit(submit_info, fences[swapchain_image_idx]);
|
||||
|
||||
// then present the surface
|
||||
vk::PresentInfoKHR present_info{
|
||||
.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &image_ready_semaphore,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &swapchain,
|
||||
.pImageIndices = &swapchain_image_idx,
|
||||
};
|
||||
try {
|
||||
state.general_queue.presentKHR(present_info);
|
||||
} catch (vk::OutOfDateKHRError) {
|
||||
state.device.waitIdle();
|
||||
int width, height;
|
||||
SDL_Vulkan_GetDrawableSize(window, &width, &height);
|
||||
destroy_swapchain();
|
||||
create_swapchain();
|
||||
create_graphics_pipelines();
|
||||
need_rebuild = true;
|
||||
}
|
||||
|
||||
swapchain_image_idx = ~0;
|
||||
current_cmd_buffer = nullptr;
|
||||
}
|
||||
|
||||
void ScreenRenderer::create_layout_sync() {
|
||||
vk::DescriptorSetLayoutBinding sampler_layout_binding{
|
||||
.binding = 0,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
||||
};
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info{};
|
||||
descriptor_info.setBindings(sampler_layout_binding);
|
||||
descriptor_set_layout = state.device.createDescriptorSetLayout(descriptor_info);
|
||||
|
||||
vk::DescriptorPoolSize pool_size{
|
||||
.type = vk::DescriptorType::eCombinedImageSampler,
|
||||
.descriptorCount = swapchain_size
|
||||
};
|
||||
vk::DescriptorPoolCreateInfo pool_info{
|
||||
.maxSets = swapchain_size,
|
||||
};
|
||||
pool_info.setPoolSizes(pool_size);
|
||||
descriptor_pool = state.device.createDescriptorPool(pool_info);
|
||||
|
||||
vk::DescriptorSetAllocateInfo descr_set_info{
|
||||
.descriptorPool = descriptor_pool,
|
||||
};
|
||||
std::vector<vk::DescriptorSetLayout> descr_set_layouts(swapchain_size, descriptor_set_layout);
|
||||
descr_set_info.setSetLayouts(descr_set_layouts);
|
||||
descriptor_sets = state.device.allocateDescriptorSets(descr_set_info);
|
||||
|
||||
vk::PipelineLayoutCreateInfo layout_info{};
|
||||
layout_info.setSetLayouts(descriptor_set_layout);
|
||||
// add push constant for fxaa pipeline, not used by the normal pipeline
|
||||
vk::PushConstantRange push_constant{
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
||||
.offset = 0,
|
||||
.size = 2 * sizeof(float),
|
||||
};
|
||||
layout_info.setPushConstantRanges(push_constant);
|
||||
pipeline_layout = state.device.createPipelineLayout(layout_info);
|
||||
|
||||
vk::CommandBufferAllocateInfo cmd_buffer_info{
|
||||
.commandPool = state.general_command_pool,
|
||||
.level = vk::CommandBufferLevel::ePrimary,
|
||||
.commandBufferCount = swapchain_size
|
||||
};
|
||||
command_buffers = state.device.allocateCommandBuffers(cmd_buffer_info);
|
||||
|
||||
// create fences (in signaled state)
|
||||
vk::FenceCreateInfo fence_info{
|
||||
.flags = vk::FenceCreateFlagBits::eSignaled
|
||||
};
|
||||
fences.resize(swapchain_size);
|
||||
for (uint32_t i = 0; i < swapchain_size; i++)
|
||||
fences[i] = state.device.createFence(fence_info);
|
||||
|
||||
// create vao
|
||||
vk::BufferCreateInfo buffer_info{
|
||||
.size = sizeof(screen_vertices_t) * swapchain_size,
|
||||
.usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
|
||||
.sharingMode = vk::SharingMode::eExclusive
|
||||
};
|
||||
std::tie(vao, vao_allocation) = state.allocator.createBuffer(buffer_info, vkutil::vma_auto_alloc);
|
||||
|
||||
// create and zero-fill uvs
|
||||
last_uvs.resize(swapchain_size);
|
||||
std::fill(last_uvs.begin(), last_uvs.end(), std::array<float, 4>());
|
||||
}
|
||||
|
||||
void ScreenRenderer::create_render_pass() {
|
||||
vk::AttachmentDescription color_attachment{
|
||||
.format = surface_format.format,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
.finalLayout = vk::ImageLayout::ePresentSrcKHR
|
||||
};
|
||||
vk::AttachmentReference attachment_ref{
|
||||
.attachment = 0,
|
||||
.layout = vk::ImageLayout::eColorAttachmentOptimal
|
||||
};
|
||||
vk::SubpassDescription subpass{
|
||||
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics
|
||||
};
|
||||
subpass.setColorAttachments(attachment_ref);
|
||||
|
||||
vk::SubpassDependency dependency{
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
.srcAccessMask = vk::AccessFlags(),
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite
|
||||
};
|
||||
|
||||
vk::RenderPassCreateInfo pass_info{};
|
||||
pass_info.setAttachments(color_attachment);
|
||||
pass_info.setSubpasses(subpass);
|
||||
pass_info.setDependencies(dependency);
|
||||
|
||||
render_pass = state.device.createRenderPass(pass_info);
|
||||
}
|
||||
|
||||
vk::Pipeline ScreenRenderer::create_graphics_pipeline_impl(std::array<vk::PipelineShaderStageCreateInfo, 2> &shader_stages) {
|
||||
vk::VertexInputBindingDescription binding_descr{
|
||||
.binding = 0,
|
||||
.stride = screen_vertex_size,
|
||||
.inputRate = vk::VertexInputRate::eVertex
|
||||
};
|
||||
std::array<vk::VertexInputAttributeDescription, 2> attr_descr;
|
||||
// pos
|
||||
attr_descr[0] = vk::VertexInputAttributeDescription{
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = vk::Format::eR32G32B32Sfloat,
|
||||
.offset = offsetof(screen_vertex, pos)
|
||||
};
|
||||
// uv
|
||||
attr_descr[1] = vk::VertexInputAttributeDescription{
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = vk::Format::eR32G32Sfloat,
|
||||
.offset = offsetof(screen_vertex, uv)
|
||||
};
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input{};
|
||||
vertex_input.setVertexBindingDescriptions(binding_descr);
|
||||
vertex_input.setVertexAttributeDescriptions(attr_descr);
|
||||
|
||||
vk::PipelineInputAssemblyStateCreateInfo input_assembly{
|
||||
.topology = vk::PrimitiveTopology::eTriangleStrip
|
||||
};
|
||||
vk::Viewport viewport{
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f
|
||||
};
|
||||
{
|
||||
// compute viewport now
|
||||
const float window_aspect = static_cast<float>(extent.width) / extent.height;
|
||||
const float vita_aspect = static_cast<float>(DEFAULT_RES_WIDTH) / DEFAULT_RES_HEIGHT;
|
||||
if (window_aspect > vita_aspect) {
|
||||
// Window is wide. Pin top and bottom.
|
||||
viewport.width = extent.height * vita_aspect;
|
||||
viewport.height = static_cast<float>(extent.height);
|
||||
viewport.x = (extent.width - viewport.width) / 2.0f;
|
||||
viewport.y = 0.0f;
|
||||
} else {
|
||||
// Window is tall. Pin left and right.
|
||||
viewport.width = static_cast<float>(extent.width);
|
||||
viewport.height = extent.width / vita_aspect;
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = (extent.height - viewport.height) / 2;
|
||||
}
|
||||
}
|
||||
vk::Rect2D scissor{
|
||||
.offset = { 0, 0 },
|
||||
.extent = extent
|
||||
};
|
||||
vk::PipelineViewportStateCreateInfo viewport_state{};
|
||||
viewport_state.setViewports(viewport);
|
||||
viewport_state.setScissors(scissor);
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizer{
|
||||
.polygonMode = vk::PolygonMode::eFill,
|
||||
.cullMode = vk::CullModeFlagBits::eNone,
|
||||
.frontFace = vk::FrontFace::eClockwise,
|
||||
.lineWidth = 1.0f
|
||||
};
|
||||
vk::PipelineMultisampleStateCreateInfo multisampling{
|
||||
.rasterizationSamples = vk::SampleCountFlagBits::e1
|
||||
};
|
||||
vk::PipelineColorBlendAttachmentState blend_attachment{
|
||||
.blendEnable = VK_FALSE,
|
||||
.colorWriteMask = vkutil::default_color_mask
|
||||
};
|
||||
vk::PipelineColorBlendStateCreateInfo color_blending{};
|
||||
color_blending.setAttachments(blend_attachment);
|
||||
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info{
|
||||
.pVertexInputState = &vertex_input,
|
||||
.pInputAssemblyState = &input_assembly,
|
||||
.pViewportState = &viewport_state,
|
||||
.pRasterizationState = &rasterizer,
|
||||
.pMultisampleState = &multisampling,
|
||||
.pColorBlendState = &color_blending,
|
||||
.layout = pipeline_layout,
|
||||
.renderPass = render_pass,
|
||||
.subpass = 0
|
||||
};
|
||||
pipeline_info.setStages(shader_stages);
|
||||
|
||||
const auto result = state.device.createGraphicsPipeline(VK_NULL_HANDLE, pipeline_info);
|
||||
if (result.result != vk::Result::eSuccess) {
|
||||
LOG_CRITICAL("Failed to create pipeline.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
bool ScreenRenderer::create_graphics_pipelines() {
|
||||
vk::PipelineShaderStageCreateInfo vert_info{
|
||||
.stage = vk::ShaderStageFlagBits::eVertex,
|
||||
.module = shader_vertex,
|
||||
.pName = "main"
|
||||
};
|
||||
vk::PipelineShaderStageCreateInfo frag_info{
|
||||
.stage = vk::ShaderStageFlagBits::eFragment,
|
||||
.module = shader_fragment,
|
||||
.pName = "main"
|
||||
};
|
||||
std::array<vk::PipelineShaderStageCreateInfo, 2> shader_stages = { vert_info, frag_info };
|
||||
|
||||
pipeline = create_graphics_pipeline_impl(shader_stages);
|
||||
if (!pipeline)
|
||||
return false;
|
||||
|
||||
shader_stages[1].module = shader_fragment_fxaa;
|
||||
pipeline_fxaa = create_graphics_pipeline_impl(shader_stages);
|
||||
if (!pipeline_fxaa)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenRenderer::create_surface_image() {
|
||||
vita_surface.resize(swapchain_size);
|
||||
|
||||
vk::SamplerCreateInfo sampler_info{
|
||||
// use linear as it renders a lot better compared to nearest (although it adds a slight blur)
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeW = vk::SamplerAddressMode::eClampToEdge,
|
||||
};
|
||||
vita_surface_sampler = state.device.createSampler(sampler_info);
|
||||
|
||||
vk::BufferCreateInfo buffer_info{
|
||||
// make sure it is big enough
|
||||
.size = 1024 * 720 * sizeof(uint32_t),
|
||||
.usage = vk::BufferUsageFlagBits::eTransferSrc,
|
||||
.sharingMode = vk::SharingMode::eExclusive
|
||||
};
|
||||
std::tie(vita_surface_staging, vita_surface_staging_alloc) = state.allocator.createBuffer(buffer_info, vkutil::vma_mapped_alloc, vita_surface_staging_info);
|
||||
}
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,765 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/vulkan/surface_cache.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
#include <renderer/vulkan/state.h>
|
||||
#include <renderer/vulkan/types.h>
|
||||
#include <vkutil/vkutil.h>
|
||||
|
||||
#include <util/log.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
static constexpr std::uint64_t CASTED_UNUSED_TEXTURE_PURGE_SECS = 40;
|
||||
|
||||
void VKSurfaceCache::destroy_framebuffers(vk::ImageView view) {
|
||||
for (auto it = framebuffer_array.begin(); it != framebuffer_array.end();) {
|
||||
// if the color of depth-stencil match the one of the render_target, this won't be used anymore
|
||||
if (it->first.first == view || it->first.second == view) {
|
||||
reinterpret_cast<VKContext *>(state.context)->frame().destroy_queue.add(it->second);
|
||||
it = framebuffer_array.erase(it);
|
||||
} else {
|
||||
it = std::next(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VKSurfaceCache::destroy_surface(ColorSurfaceCacheInfo &info) {
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
vkutil::DestroyQueue &destroy_queue = context->frame().destroy_queue;
|
||||
|
||||
// don't forget to destroy in the right order
|
||||
for (auto &casted : info.casted_textures) {
|
||||
destroy_queue.add_buffer(casted.transition_buffer);
|
||||
destroy_queue.add_image(casted.texture);
|
||||
}
|
||||
|
||||
destroy_queue.add_image(info.sampled_image);
|
||||
destroy_framebuffers(info.texture.view);
|
||||
destroy_queue.add_image(info.texture);
|
||||
}
|
||||
|
||||
void VKSurfaceCache::destroy_surface(DepthStencilSurfaceCacheInfo &info) {
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
vkutil::DestroyQueue &destroy_queue = context->frame().destroy_queue;
|
||||
|
||||
destroy_queue.add_image(info.read_only);
|
||||
destroy_framebuffers(info.texture.view);
|
||||
destroy_queue.add_image(info.texture);
|
||||
}
|
||||
|
||||
VKSurfaceCache::VKSurfaceCache(VKState &state)
|
||||
: state(state) {
|
||||
for (int i = 0; i < MAX_CACHE_SIZE_PER_CONTAINER; i++) {
|
||||
depth_stencil_textures[i].flags = SurfaceCacheInfo::FLAG_FREE;
|
||||
}
|
||||
}
|
||||
|
||||
vkutil::Image *VKSurfaceCache::retrieve_color_surface_texture_handle(uint16_t width, uint16_t height, const uint16_t pixel_stride,
|
||||
const SceGxmColorBaseFormat base_format, Ptr<void> address, SurfaceTextureRetrievePurpose purpose, vk::ComponentMapping &swizzle,
|
||||
uint16_t *stored_height, uint16_t *stored_width) {
|
||||
// Create the key to access the cache struct
|
||||
const std::uint64_t key = address.address();
|
||||
|
||||
const uint32_t original_width = width;
|
||||
const uint32_t original_height = height;
|
||||
|
||||
width *= state.res_multiplier;
|
||||
height *= state.res_multiplier;
|
||||
|
||||
bool overlap = true;
|
||||
|
||||
// Of course, this works under the assumption that range must be unique :D
|
||||
auto ite = color_surface_textures.upper_bound(key);
|
||||
if (ite == color_surface_textures.begin())
|
||||
// no match
|
||||
overlap = false;
|
||||
else
|
||||
ite--;
|
||||
// ite is now the first item with an adress lower or equal to key
|
||||
bool invalidated = false;
|
||||
|
||||
overlap = (overlap && (ite->first + ite->second.total_bytes) > key);
|
||||
|
||||
if (!overlap && purpose != SurfaceTextureRetrievePurpose::WRITING) {
|
||||
// not part of a surface, let the texture cache handle it
|
||||
return nullptr;
|
||||
}
|
||||
const vk::Format vk_format = color::translate_format(base_format);
|
||||
|
||||
uint32_t bytes_per_stride = pixel_stride * gxm::bits_per_pixel(base_format) / 8;
|
||||
uint32_t total_surface_size = bytes_per_stride * original_height;
|
||||
|
||||
if (overlap) {
|
||||
ColorSurfaceCacheInfo &info = ite->second;
|
||||
auto used_iterator = std::find(last_use_color_surface_index.begin(), last_use_color_surface_index.end(), ite->first);
|
||||
|
||||
if (stored_width) {
|
||||
*stored_width = info.original_width;
|
||||
}
|
||||
|
||||
if (stored_height) {
|
||||
*stored_height = info.original_height;
|
||||
}
|
||||
|
||||
// There are four situations I think of:
|
||||
// 1. Different base address, lookup for write, in this case, if the cached surface range contains the given address, then
|
||||
// probably this cached surface has already been freed GPU-wise. So erase.
|
||||
// 2. Same base address, but width and height change to be larger, or format change if write. Remake a new one for both read and write sitatation.
|
||||
// 3. Out of cache range. In write case, create a new one, in read case, lul
|
||||
// 4. Read situation with smaller width and height, probably need to extract the needed region out.
|
||||
const bool addr_in_range_of_cache = ((key + total_surface_size) <= (ite->first + info.total_bytes));
|
||||
const bool cache_probably_freed = ((ite->first != key) && addr_in_range_of_cache && (purpose == SurfaceTextureRetrievePurpose::WRITING));
|
||||
const bool surface_extent_changed = (info.width < width) || (info.height < height);
|
||||
bool surface_stat_changed = false;
|
||||
|
||||
if (ite->first == key) {
|
||||
if (purpose == SurfaceTextureRetrievePurpose::WRITING) {
|
||||
surface_stat_changed = surface_extent_changed || (base_format != info.format);
|
||||
} else {
|
||||
// If the extent changed but format is not the same, then the probability of it being a cast is high
|
||||
surface_stat_changed = surface_extent_changed && (base_format == info.format);
|
||||
}
|
||||
}
|
||||
|
||||
if (cache_probably_freed || surface_stat_changed) {
|
||||
// Clear out. We will recreate later
|
||||
|
||||
destroy_surface(ite->second);
|
||||
color_surface_textures.erase(ite);
|
||||
invalidated = true;
|
||||
} else if (!addr_in_range_of_cache) {
|
||||
if (purpose == SurfaceTextureRetrievePurpose::WRITING) {
|
||||
destroy_surface(ite->second);
|
||||
color_surface_textures.erase(ite);
|
||||
invalidated = true;
|
||||
}
|
||||
} else if (purpose == SurfaceTextureRetrievePurpose::READING) {
|
||||
// If we read and it's still in range
|
||||
if (used_iterator != last_use_color_surface_index.end()) {
|
||||
last_use_color_surface_index.erase(used_iterator);
|
||||
}
|
||||
|
||||
last_use_color_surface_index.push_back(ite->first);
|
||||
|
||||
if (info.flags & SurfaceCacheInfo::FLAG_DIRTY) {
|
||||
// We can't use this texture sadly :( If it uses for writing of course it will be gud gud
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool castable = (info.pixel_stride == pixel_stride);
|
||||
|
||||
uint32_t bytes_per_pixel_requested = gxm::bits_per_pixel(base_format) / 8;
|
||||
uint32_t bytes_per_pixel_in_store = gxm::bits_per_pixel(info.format) / 8;
|
||||
|
||||
// Check if castable. Technically the income format should be texture format, but this is for easier logic.
|
||||
// When it's required. I may change :p
|
||||
if (base_format != info.format) {
|
||||
if (bytes_per_pixel_requested > bytes_per_pixel_in_store) {
|
||||
castable = (((bytes_per_pixel_requested % bytes_per_pixel_in_store) == 0) && (info.pixel_stride % pixel_stride == 0) && ((info.pixel_stride / pixel_stride) == (bytes_per_pixel_requested / bytes_per_pixel_in_store)));
|
||||
} else {
|
||||
castable = (((bytes_per_pixel_in_store % bytes_per_pixel_requested) == 0) && (pixel_stride % info.pixel_stride == 0) && ((pixel_stride / info.pixel_stride) == (bytes_per_pixel_in_store / bytes_per_pixel_requested)));
|
||||
}
|
||||
|
||||
if (!castable) {
|
||||
static bool has_happened = false;
|
||||
LOG_ERROR_IF(!has_happened, "Two surface formats requested=0x{:X} and inStore=0x{:X} are not castable!", base_format, info.format);
|
||||
has_happened = true;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (castable) {
|
||||
// TODO: this is true only for linear textures (and also kind of for tiled textures) (and in this case start_x = 0),
|
||||
// for swizzled textures this is different
|
||||
const uint32_t data_delta = address.address() - ite->first;
|
||||
uint32_t start_sourced_line = (data_delta / bytes_per_stride) * state.res_multiplier;
|
||||
uint32_t start_x = (data_delta % bytes_per_stride) / bytes_per_pixel_requested * state.res_multiplier;
|
||||
|
||||
if (static_cast<uint16_t>(start_sourced_line + height) > info.height) {
|
||||
LOG_ERROR("Trying to present non-existen segment in cached color surface!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (start_x > 0) {
|
||||
static bool has_happened = false;
|
||||
LOG_ERROR_IF(!has_happened, "Surface copy with nonzero delta x");
|
||||
has_happened = true;
|
||||
start_x = 0;
|
||||
}
|
||||
|
||||
const vk::Image color_handle = reinterpret_cast<VKContext *>(state.context)->current_color_attachment->image;
|
||||
if (info.texture.image == color_handle || (start_sourced_line != 0) || (start_x != 0) || (info.width != width) || (info.height != height) || (info.format != base_format)) {
|
||||
uint64_t current_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
const uint64_t scene_timestamp = reinterpret_cast<VKContext *>(state.context)->scene_timestamp;
|
||||
|
||||
std::vector<CastedTexture> &casted_vec = info.casted_textures;
|
||||
|
||||
vk::Format source_format = color::translate_format(info.format);
|
||||
|
||||
CastedTexture *casted = nullptr;
|
||||
|
||||
// Look in cast cache and grab one. The cache really does not store immediate grab on now, but rather to reduce the synchronization in the pipeline (use different texture)
|
||||
for (size_t i = 0; i < casted_vec.size();) {
|
||||
if ((casted_vec[i].cropped_height == height) && (casted_vec[i].cropped_width == width) && (casted_vec[i].cropped_y == start_sourced_line) && (casted_vec[i].cropped_x == start_x) && (casted_vec[i].format == base_format)) {
|
||||
casted = &casted_vec[i];
|
||||
|
||||
if (casted->scene_timestamp == scene_timestamp) {
|
||||
// already copied for this scene, don't do it again
|
||||
return &casted->texture;
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (current_time - info.casted_textures[i].last_used_time >= CASTED_UNUSED_TEXTURE_PURGE_SECS) {
|
||||
casted_vec.erase(casted_vec.begin() + i);
|
||||
continue;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// use prerender cmd as we can't copy an image or use pipeline barriers in a render pass
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
vk::CommandBuffer cmd_buffer = context->prerender_cmd;
|
||||
bool is_offscene = false;
|
||||
if (!cmd_buffer) {
|
||||
is_offscene = true;
|
||||
cmd_buffer = vkutil::create_single_time_command(state.device, state.general_command_pool);
|
||||
}
|
||||
|
||||
if (casted == nullptr) {
|
||||
// Try to crop + cast
|
||||
casted_vec.resize(casted_vec.size() + 1);
|
||||
casted = &casted_vec[casted_vec.size() - 1];
|
||||
*casted = CastedTexture{
|
||||
.last_used_time = current_time,
|
||||
.cropped_x = start_x,
|
||||
.cropped_y = start_sourced_line,
|
||||
.cropped_width = width,
|
||||
.cropped_height = height,
|
||||
.format = base_format
|
||||
};
|
||||
casted->texture.allocator = state.allocator;
|
||||
casted->texture.width = width;
|
||||
casted->texture.height = height;
|
||||
casted->texture.format = vk_format;
|
||||
const vk::ComponentMapping resulting_swizzle = vkutil::color_to_texture_swizzle(info.swizzle, swizzle);
|
||||
casted->texture.init_image(vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst, resulting_swizzle);
|
||||
casted->texture.transition_to(cmd_buffer, vkutil::ImageLayout::TransferDst);
|
||||
} else {
|
||||
casted->texture.transition_to_discard(cmd_buffer, vkutil::ImageLayout::TransferDst);
|
||||
}
|
||||
|
||||
casted->last_used_time = current_time;
|
||||
casted->scene_timestamp = scene_timestamp;
|
||||
|
||||
if (bytes_per_pixel_requested == bytes_per_pixel_in_store) {
|
||||
vk::ImageCopy image_copy{
|
||||
.srcSubresource = vkutil::color_subresource_layer,
|
||||
.srcOffset = { static_cast<int32_t>(start_x), static_cast<int32_t>(start_sourced_line), 0 },
|
||||
.dstSubresource = vkutil::color_subresource_layer,
|
||||
.dstOffset = { 0,
|
||||
0,
|
||||
0 },
|
||||
.extent = {
|
||||
// Don't try to copy what is in the stride
|
||||
std::min(width, info.width),
|
||||
height,
|
||||
1 }
|
||||
};
|
||||
cmd_buffer.copyImage(info.texture.image, vk::ImageLayout::eGeneral, casted->texture.image, vk::ImageLayout::eTransferDstOptimal, image_copy);
|
||||
} else {
|
||||
if (start_x != 0) {
|
||||
LOG_ERROR("Typeless copy not starting from the beginning of a line");
|
||||
return nullptr;
|
||||
}
|
||||
static bool has_happened = false;
|
||||
LOG_INFO_IF(!has_happened, "Game is doing typeless copies");
|
||||
has_happened = true;
|
||||
// We must use a transition buffer
|
||||
vk::DeviceSize buffer_size = bytes_per_stride * state.res_multiplier * height;
|
||||
if (!casted->transition_buffer.buffer || casted->transition_buffer.size < buffer_size) {
|
||||
// create or re-create the buffer
|
||||
casted->transition_buffer.destroy();
|
||||
casted->transition_buffer = vkutil::Buffer(state.allocator, buffer_size);
|
||||
casted->transition_buffer.init_buffer(vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eTransferSrc);
|
||||
}
|
||||
|
||||
// copy the image to the buffer
|
||||
vk::BufferImageCopy copy_image_buffer{
|
||||
.bufferOffset = 0,
|
||||
.bufferRowLength = static_cast<uint32_t>(info.pixel_stride * state.res_multiplier),
|
||||
.bufferImageHeight = height,
|
||||
.imageSubresource = vkutil::color_subresource_layer,
|
||||
.imageOffset = { 0,
|
||||
static_cast<int32_t>(start_sourced_line),
|
||||
0 },
|
||||
.imageExtent = { info.width, height, 1 }
|
||||
};
|
||||
cmd_buffer.copyImageToBuffer(info.texture.image, vk::ImageLayout::eGeneral, casted->transition_buffer.buffer, copy_image_buffer);
|
||||
|
||||
// then the buffer to the image
|
||||
copy_image_buffer
|
||||
.setBufferRowLength(pixel_stride * state.res_multiplier)
|
||||
.setImageOffset({ 0, 0, 0 })
|
||||
.setImageExtent({ width, height, 1 });
|
||||
cmd_buffer.copyBufferToImage(casted->transition_buffer.buffer, casted->texture.image, vk::ImageLayout::eTransferDstOptimal, copy_image_buffer);
|
||||
}
|
||||
casted->texture.transition_to(cmd_buffer, vkutil::ImageLayout::ColorAttachmentReadWrite);
|
||||
|
||||
if (is_offscene)
|
||||
vkutil::end_single_time_command(state.device, state.general_queue, state.general_command_pool, cmd_buffer);
|
||||
|
||||
return &casted->texture;
|
||||
} else {
|
||||
// we must insert a barrier before reading from the texture
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
vk::CommandBuffer cmd_buffer = context->prerender_cmd;
|
||||
vk::ImageMemoryBarrier barrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eShaderRead,
|
||||
.oldLayout = vk::ImageLayout::eGeneral,
|
||||
.newLayout = vk::ImageLayout::eGeneral,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = info.texture.image,
|
||||
.subresourceRange = vkutil::color_subresource_range
|
||||
};
|
||||
cmd_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, vk::DependencyFlags(), {}, {}, barrier);
|
||||
|
||||
if (swizzle == info.swizzle)
|
||||
// we can use the same texture view
|
||||
return &info.texture;
|
||||
|
||||
if (!info.sampled_image.view) {
|
||||
vk::ComponentMapping resulting_mapping = vkutil::color_to_texture_swizzle(info.swizzle, swizzle);
|
||||
// do not destroy multiple times
|
||||
info.sampled_image.destroy_on_deletion = false;
|
||||
vk::ImageViewCreateInfo view_info{
|
||||
.image = info.texture.image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = vk_format,
|
||||
.components = resulting_mapping,
|
||||
.subresourceRange = vkutil::color_subresource_range
|
||||
};
|
||||
info.sampled_image.view = state.device.createImageView(view_info);
|
||||
}
|
||||
|
||||
return &info.sampled_image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!invalidated) {
|
||||
if (purpose == SurfaceTextureRetrievePurpose::WRITING) {
|
||||
if (used_iterator != last_use_color_surface_index.end()) {
|
||||
last_use_color_surface_index.erase(used_iterator);
|
||||
}
|
||||
|
||||
last_use_color_surface_index.push_back(ite->first);
|
||||
return &info.texture;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
} else if (used_iterator != last_use_color_surface_index.end()) {
|
||||
last_use_color_surface_index.erase(used_iterator);
|
||||
}
|
||||
}
|
||||
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
ColorSurfaceCacheInfo &info_added = color_surface_textures[key];
|
||||
|
||||
if (info_added.texture.image) {
|
||||
// deferred destruction of the existing surface
|
||||
destroy_surface(info_added);
|
||||
}
|
||||
|
||||
info_added.width = width;
|
||||
info_added.height = height;
|
||||
info_added.original_width = original_width;
|
||||
info_added.original_height = original_height;
|
||||
info_added.pixel_stride = pixel_stride;
|
||||
info_added.data = address;
|
||||
info_added.total_bytes = bytes_per_stride * original_height;
|
||||
info_added.format = base_format;
|
||||
// only remember the swizzle here, it will be useful if we get to present or sample from this image with a different swizzle
|
||||
info_added.swizzle = swizzle;
|
||||
info_added.flags = 0;
|
||||
|
||||
vkutil::Image &image = info_added.texture;
|
||||
image.allocator = state.allocator;
|
||||
image.width = width;
|
||||
image.height = height;
|
||||
image.format = vk_format;
|
||||
image.layout = vkutil::ImageLayout::Undefined;
|
||||
image.init_image(vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eInputAttachment);
|
||||
|
||||
// do it in the prerender if we read from this texture in the same scene (although this would be useless)
|
||||
vk::CommandBuffer cmd_buffer = context->prerender_cmd;
|
||||
// must do a first transition to draw the placeholder color
|
||||
image.transition_to(cmd_buffer, vkutil::ImageLayout::TransferDst);
|
||||
|
||||
vk::ClearColorValue clear_color{ std::array<float, 4>({ 0.968627450f, 0.776470588f, 0.0f, 0.0f }) };
|
||||
cmd_buffer.clearColorImage(image.image, vk::ImageLayout::eTransferDstOptimal, clear_color, vkutil::color_subresource_range);
|
||||
image.transition_to(cmd_buffer, vkutil::ImageLayout::ColorAttachmentReadWrite);
|
||||
|
||||
// Now that everything goes well, we can start rearranging
|
||||
if (last_use_color_surface_index.size() >= MAX_CACHE_SIZE_PER_CONTAINER) {
|
||||
// We have to purge a cache along with framebuffer
|
||||
// So choose the one that is last used
|
||||
const std::uint64_t first_key = last_use_color_surface_index.front();
|
||||
if (first_key != key) {
|
||||
destroy_surface(color_surface_textures[first_key]);
|
||||
color_surface_textures.erase(first_key);
|
||||
}
|
||||
|
||||
last_use_color_surface_index.erase(last_use_color_surface_index.begin());
|
||||
}
|
||||
|
||||
last_use_color_surface_index.push_back(key);
|
||||
|
||||
if (stored_height) {
|
||||
*stored_height = height;
|
||||
}
|
||||
|
||||
if (stored_width) {
|
||||
*stored_width = width;
|
||||
}
|
||||
|
||||
return &info_added.texture;
|
||||
}
|
||||
|
||||
vkutil::Image *VKSurfaceCache::retrieve_depth_stencil_texture_handle(const MemState &mem, const SceGxmDepthStencilSurface &surface, int32_t force_width,
|
||||
int32_t force_height, const bool is_reading) {
|
||||
bool packed_ds = (surface.control.content & SceGxmDepthStencilControl::format_bits) == SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24;
|
||||
|
||||
if (force_width < 0) {
|
||||
force_width = target->width;
|
||||
}
|
||||
|
||||
if (force_height < 0) {
|
||||
force_height = target->height;
|
||||
}
|
||||
|
||||
size_t found_index = -1;
|
||||
|
||||
// The whole depth stencil struct is reserved for future use
|
||||
for (size_t i = 0; i < depth_stencil_textures.size(); i++) {
|
||||
if ((depth_stencil_textures[i].surface.depthData == surface.depthData) && (packed_ds || (!packed_ds && (depth_stencil_textures[i].surface.stencilData == surface.stencilData)))) {
|
||||
found_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_index != static_cast<std::size_t>(-1)) {
|
||||
auto ite = std::find(last_use_depth_stencil_surface_index.begin(), last_use_depth_stencil_surface_index.end(), found_index);
|
||||
if (ite != last_use_depth_stencil_surface_index.end()) {
|
||||
last_use_depth_stencil_surface_index.erase(ite);
|
||||
last_use_depth_stencil_surface_index.push_back(found_index);
|
||||
}
|
||||
|
||||
DepthStencilSurfaceCacheInfo &cached_info = depth_stencil_textures[found_index];
|
||||
bool need_remake = false;
|
||||
if (cached_info.width < force_width) {
|
||||
if (is_reading)
|
||||
return nullptr;
|
||||
cached_info.width = force_width;
|
||||
need_remake = true;
|
||||
}
|
||||
|
||||
if (cached_info.height < force_height) {
|
||||
if (is_reading)
|
||||
return nullptr;
|
||||
cached_info.height = force_height;
|
||||
need_remake = true;
|
||||
}
|
||||
|
||||
if (!need_remake) {
|
||||
if (!is_reading)
|
||||
return &cached_info.texture;
|
||||
|
||||
const uint64_t scene_timestamp = reinterpret_cast<VKContext *>(state.context)->scene_timestamp;
|
||||
|
||||
// copy the depth stencil only once per scene
|
||||
if (cached_info.scene_timestamp == scene_timestamp)
|
||||
return &cached_info.read_only;
|
||||
|
||||
cached_info.scene_timestamp = scene_timestamp;
|
||||
|
||||
// use prerender cmd as we can't copy an image or use pipeline barriers in a render pass
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
vk::CommandBuffer cmd_buffer = context->prerender_cmd;
|
||||
bool is_offscene = false;
|
||||
if (!cmd_buffer) {
|
||||
is_offscene = true;
|
||||
cmd_buffer = vkutil::create_single_time_command(state.device, state.general_command_pool);
|
||||
}
|
||||
if (!cached_info.read_only.image) {
|
||||
vk::ImageSubresourceRange range = vkutil::ds_subresource_range;
|
||||
range.aspectMask = vk::ImageAspectFlagBits::eDepth;
|
||||
cached_info.read_only.allocator = state.allocator;
|
||||
cached_info.read_only.width = cached_info.width;
|
||||
cached_info.read_only.height = cached_info.height;
|
||||
cached_info.read_only.format = vk::Format::eD32SfloatS8Uint;
|
||||
cached_info.read_only.init_image(vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst);
|
||||
// we want a texture view with only the depth aspect bit
|
||||
// TODO: not efficient
|
||||
state.device.destroy(cached_info.read_only.view);
|
||||
vk::ImageViewCreateInfo view_info{
|
||||
.image = cached_info.read_only.image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = vk::Format::eD32SfloatS8Uint,
|
||||
.components = {},
|
||||
.subresourceRange = range
|
||||
};
|
||||
cached_info.read_only.view = state.device.createImageView(view_info);
|
||||
|
||||
cached_info.read_only.transition_to(cmd_buffer, vkutil::ImageLayout::TransferDst, vkutil::ds_subresource_range);
|
||||
} else {
|
||||
cached_info.read_only.transition_to_discard(cmd_buffer, vkutil::ImageLayout::TransferDst, vkutil::ds_subresource_range);
|
||||
}
|
||||
|
||||
cached_info.texture.transition_to(cmd_buffer, vkutil::ImageLayout::TransferSrc, vkutil::ds_subresource_range);
|
||||
vk::ImageSubresourceLayers layers = vkutil::color_subresource_layer;
|
||||
layers.aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
||||
vk::ImageCopy image_copy{
|
||||
.srcSubresource = layers,
|
||||
.srcOffset = { 0, 0, 0 },
|
||||
.dstSubresource = layers,
|
||||
.dstOffset = { 0, 0, 0 },
|
||||
.extent = { static_cast<uint32_t>(cached_info.width), static_cast<uint32_t>(cached_info.height), 1U }
|
||||
};
|
||||
cmd_buffer.copyImage(cached_info.texture.image, vk::ImageLayout::eTransferSrcOptimal, cached_info.read_only.image, vk::ImageLayout::eTransferDstOptimal, image_copy);
|
||||
|
||||
// transition back
|
||||
cached_info.texture.transition_to(cmd_buffer, vkutil::ImageLayout::DepthStencilAttachment, vkutil::ds_subresource_range);
|
||||
cached_info.read_only.transition_to(cmd_buffer, vkutil::ImageLayout::DepthReadOnly, vkutil::ds_subresource_range);
|
||||
|
||||
if (is_offscene)
|
||||
vkutil::end_single_time_command(state.device, state.general_queue, state.general_command_pool, cmd_buffer);
|
||||
|
||||
return &cached_info.read_only;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_reading)
|
||||
return nullptr;
|
||||
|
||||
// Now that everything goes well, we can start rearranging
|
||||
// Almost carbon copy but still too specific
|
||||
if (last_use_depth_stencil_surface_index.size() >= MAX_CACHE_SIZE_PER_CONTAINER) {
|
||||
// We have to purge a cache along with framebuffer
|
||||
// So choose the one that is last used
|
||||
const std::size_t index = last_use_depth_stencil_surface_index.front();
|
||||
|
||||
last_use_depth_stencil_surface_index.erase(last_use_depth_stencil_surface_index.begin());
|
||||
depth_stencil_textures[index].flags = SurfaceCacheInfo::FLAG_FREE;
|
||||
|
||||
found_index = index;
|
||||
}
|
||||
|
||||
if (found_index == static_cast<std::size_t>(-1)) {
|
||||
// Still nowhere to found a free slot? We can search maybe
|
||||
for (std::size_t i = 0; i < depth_stencil_textures.size(); i++) {
|
||||
if (depth_stencil_textures[i].flags & SurfaceCacheInfo::FLAG_FREE) {
|
||||
found_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found_index == -1) {
|
||||
LOG_ERROR("No free depth stencil texture cache slot!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (depth_stencil_textures[found_index].texture.image) {
|
||||
// deferred deletion of the previous surface
|
||||
destroy_surface(depth_stencil_textures[found_index]);
|
||||
}
|
||||
|
||||
last_use_depth_stencil_surface_index.push_back(found_index);
|
||||
depth_stencil_textures[found_index].flags = 0;
|
||||
depth_stencil_textures[found_index].surface = surface;
|
||||
depth_stencil_textures[found_index].width = force_width;
|
||||
depth_stencil_textures[found_index].height = force_height;
|
||||
|
||||
vkutil::Image &image = depth_stencil_textures[found_index].texture;
|
||||
|
||||
// use prerender cmd in case we read from the depth buffer (although I really doubt this could happen)
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
vk::CommandBuffer cmd_buffer = context->prerender_cmd;
|
||||
|
||||
image.allocator = state.allocator;
|
||||
image.width = force_width;
|
||||
image.height = force_height;
|
||||
image.format = vk::Format::eD32SfloatS8Uint;
|
||||
image.layout = vkutil::ImageLayout::Undefined;
|
||||
image.init_image(vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc);
|
||||
|
||||
image.transition_to(cmd_buffer, vkutil::ImageLayout::TransferDst, vkutil::ds_subresource_range);
|
||||
vk::ClearDepthStencilValue clear_value{
|
||||
.depth = 1.0,
|
||||
.stencil = 0
|
||||
};
|
||||
cmd_buffer.clearDepthStencilImage(image.image, vk::ImageLayout::eTransferDstOptimal, clear_value, vkutil::ds_subresource_range);
|
||||
image.transition_to(cmd_buffer, vkutil::ImageLayout::DepthStencilAttachment, vkutil::ds_subresource_range);
|
||||
|
||||
return ℑ
|
||||
}
|
||||
|
||||
vk::Framebuffer VKSurfaceCache::retrieve_framebuffer_handle(const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
|
||||
vk::RenderPass render_pass, vkutil::Image **color_texture_handle, vkutil::Image **ds_texture_handle,
|
||||
uint16_t *stored_height) {
|
||||
if (!target) {
|
||||
LOG_ERROR("Unable to retrieve framebuffer with no active render target!");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!color && !depth_stencil) {
|
||||
LOG_ERROR("Depth stencil and color surface are both null!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// First retrieve separately the color surface and ds surface
|
||||
vkutil::Image *color_handle;
|
||||
vkutil::Image *ds_handle;
|
||||
|
||||
if (color && color->data) {
|
||||
const SceGxmColorBaseFormat color_base_format = gxm::get_base_format(color->colorFormat);
|
||||
vk::ComponentMapping swizzle = color::translate_swizzle(color->colorFormat);
|
||||
color_handle = retrieve_color_surface_texture_handle(color->width,
|
||||
color->height, color->strideInPixels, color_base_format, color->data,
|
||||
renderer::SurfaceTextureRetrievePurpose::WRITING, swizzle, stored_height);
|
||||
} else {
|
||||
color_handle = &target->color;
|
||||
}
|
||||
|
||||
if (depth_stencil && (depth_stencil->depthData || depth_stencil->stencilData)) {
|
||||
SceGxmDepthStencilSurface surface_copy = *depth_stencil;
|
||||
ds_handle = retrieve_depth_stencil_texture_handle(mem, *depth_stencil);
|
||||
} else {
|
||||
ds_handle = &target->depthstencil;
|
||||
}
|
||||
|
||||
if (color_texture_handle) {
|
||||
*color_texture_handle = color_handle;
|
||||
}
|
||||
|
||||
if (ds_texture_handle) {
|
||||
*ds_texture_handle = ds_handle;
|
||||
}
|
||||
|
||||
std::pair<vk::ImageView, vk::ImageView> key = { color_handle->view, ds_handle->view };
|
||||
auto it = framebuffer_array.find(key);
|
||||
|
||||
if (it != framebuffer_array.end()) {
|
||||
// we already created a framebuffer for this pair
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
vk::FramebufferCreateInfo fb_info{
|
||||
.renderPass = render_pass,
|
||||
.width = target->width,
|
||||
.height = target->height,
|
||||
.layers = 1
|
||||
};
|
||||
vk::ImageView attachments[] = { color_handle->view, ds_handle->view };
|
||||
fb_info.setAttachments(attachments);
|
||||
vk::Framebuffer fb = state.device.createFramebuffer(fb_info);
|
||||
|
||||
framebuffer_array[key] = fb;
|
||||
return fb;
|
||||
}
|
||||
|
||||
void VKSurfaceCache::destroy_associated_framebuffers(const VKRenderTarget *render_target) {
|
||||
destroy_framebuffers(render_target->color.view);
|
||||
destroy_framebuffers(render_target->depthstencil.view);
|
||||
}
|
||||
|
||||
vk::ImageView VKSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, std::array<float, 4> &uvs, const int res_multiplier, SceFVector2 &texture_size) {
|
||||
// get closest surface with an address below address
|
||||
auto ite = color_surface_textures.upper_bound(address.address());
|
||||
if (ite == color_surface_textures.begin()) {
|
||||
return nullptr;
|
||||
}
|
||||
ite--;
|
||||
|
||||
ColorSurfaceCacheInfo &info = ite->second;
|
||||
if (info.data.address() + info.total_bytes <= address.address())
|
||||
// they do not overlap
|
||||
return nullptr;
|
||||
|
||||
width *= res_multiplier;
|
||||
height *= res_multiplier;
|
||||
|
||||
if (info.pixel_stride == pitch) {
|
||||
// In assumption the format is RGBA8
|
||||
const std::size_t data_delta = address.address() - ite->first;
|
||||
std::uint32_t limited_height = height;
|
||||
if ((data_delta % (pitch * 4)) == 0) {
|
||||
std::uint32_t start_sourced_line = (data_delta / (pitch * 4)) * res_multiplier;
|
||||
if ((start_sourced_line + height) > info.height) {
|
||||
// Sometimes the surface is just missing a little bit of lines
|
||||
if (start_sourced_line < info.height) {
|
||||
// Just limit the height and display it
|
||||
limited_height = info.height - start_sourced_line;
|
||||
} else {
|
||||
LOG_ERROR("Trying to present non-existent segment in cached color surface!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate uvs
|
||||
// First two top left, the two others bottom right
|
||||
uvs[0] = 0.0f;
|
||||
uvs[1] = static_cast<float>(start_sourced_line) / info.height;
|
||||
uvs[2] = static_cast<float>(width) / info.width;
|
||||
uvs[3] = static_cast<float>(start_sourced_line + limited_height) / info.height;
|
||||
|
||||
texture_size.x = info.width;
|
||||
texture_size.y = info.height;
|
||||
|
||||
if (info.swizzle == vkutil::rgba_mapping)
|
||||
return info.texture.view;
|
||||
|
||||
if (!info.sampled_image.view) {
|
||||
// create a view with the right swizzle
|
||||
info.sampled_image.destroy_on_deletion = false;
|
||||
vk::ImageViewCreateInfo view_info{
|
||||
.image = info.texture.image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = vk::Format::eR8G8B8A8Unorm,
|
||||
.components = vkutil::color_to_texture_swizzle(info.swizzle, vkutil::rgba_mapping),
|
||||
.subresourceRange = vkutil::color_subresource_range
|
||||
};
|
||||
info.sampled_image.view = state.device.createImageView(view_info);
|
||||
}
|
||||
|
||||
return info.sampled_image.view;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}; // namespace renderer::vulkan
|
||||
@@ -0,0 +1,228 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/vulkan/functions.h>
|
||||
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
#include <renderer/functions.h>
|
||||
|
||||
#include <util/log.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
void sync_clipping(VKContext &context) {
|
||||
if (!context.render_target)
|
||||
return;
|
||||
|
||||
const int res_multiplier = context.state.res_multiplier;
|
||||
|
||||
const int display_h = context.render_target->height / res_multiplier;
|
||||
const int scissor_x = context.record.region_clip_min.x;
|
||||
const int scissor_y = context.record.region_clip_min.y;
|
||||
|
||||
const unsigned int scissor_w = std::max(context.record.region_clip_max.x - context.record.region_clip_min.x + 1, 0);
|
||||
const unsigned int scissor_h = std::max(context.record.region_clip_max.y - context.record.region_clip_min.y + 1, 0);
|
||||
|
||||
switch (context.record.region_clip_mode) {
|
||||
case SCE_GXM_REGION_CLIP_NONE:
|
||||
// make the scissor the size of the framebuffer
|
||||
context.scissor = vk::Rect2D{ { 0, 0 }, { context.render_target->width, context.render_target->height } };
|
||||
break;
|
||||
case SCE_GXM_REGION_CLIP_ALL:
|
||||
context.scissor = vk::Rect2D{};
|
||||
break;
|
||||
case SCE_GXM_REGION_CLIP_OUTSIDE:
|
||||
context.scissor = vk::Rect2D{
|
||||
{ scissor_x * res_multiplier, scissor_y * res_multiplier },
|
||||
{ scissor_w * res_multiplier, scissor_h * res_multiplier }
|
||||
};
|
||||
break;
|
||||
case SCE_GXM_REGION_CLIP_INSIDE:
|
||||
// TODO: Implement SCE_GXM_REGION_CLIP_INSIDE
|
||||
LOG_WARN("Unimplemented region clip mode used: SCE_GXM_REGION_CLIP_INSIDE");
|
||||
context.scissor = vk::Rect2D{};
|
||||
break;
|
||||
}
|
||||
|
||||
// Vulkan does not allow the offset to be negative
|
||||
if (context.scissor.offset.x < 0) {
|
||||
context.scissor.extent.width = std::max(context.scissor.extent.width - context.scissor.offset.x, 0U);
|
||||
context.scissor.offset.x = 0;
|
||||
}
|
||||
|
||||
if (context.scissor.offset.y < 0) {
|
||||
context.scissor.extent.height = std::max(context.scissor.extent.height - context.scissor.offset.y, 0U);
|
||||
context.scissor.offset.y = 0;
|
||||
}
|
||||
|
||||
if (!context.is_recording)
|
||||
return;
|
||||
|
||||
context.render_cmd.setScissor(0, context.scissor);
|
||||
}
|
||||
|
||||
void sync_stencil_func(VKContext &context, const bool is_back) {
|
||||
if (!context.is_recording)
|
||||
return;
|
||||
|
||||
vk::StencilFaceFlags face;
|
||||
GxmStencilStateValues *state;
|
||||
if (context.record.two_sided == SCE_GXM_TWO_SIDED_DISABLED) {
|
||||
// the back state is not used when two sided is disabled
|
||||
if (is_back)
|
||||
return;
|
||||
|
||||
face = vk::StencilFaceFlagBits::eFrontAndBack;
|
||||
state = &context.record.front_stencil_state_values;
|
||||
} else {
|
||||
face = is_back ? vk::StencilFaceFlagBits::eBack : vk::StencilFaceFlagBits::eFront;
|
||||
state = is_back ? &context.record.back_stencil_state_values : &context.record.front_stencil_state_values;
|
||||
}
|
||||
|
||||
context.render_cmd.setStencilCompareMask(face, state->compare_mask);
|
||||
context.render_cmd.setStencilReference(face, state->ref);
|
||||
context.render_cmd.setStencilWriteMask(face, state->write_mask);
|
||||
}
|
||||
|
||||
void sync_mask(VKContext &context, const MemState &mem) {
|
||||
if (!context.state.features.use_mask_bit)
|
||||
return;
|
||||
|
||||
auto control = context.record.depth_stencil_surface.control.content;
|
||||
// mask is not upscaled
|
||||
auto width = context.render_target->width / context.state.res_multiplier;
|
||||
auto height = context.render_target->height / context.state.res_multiplier;
|
||||
float initial_val = (control & SceGxmDepthStencilControl::mask_bit) ? 1.0f : 0.0f;
|
||||
|
||||
std::array<float, 4> clear_bytes = { initial_val, initial_val, initial_val, initial_val };
|
||||
vk::ClearColorValue clear_color{ clear_bytes };
|
||||
context.render_target->mask.transition_to_discard(context.render_cmd, vkutil::ImageLayout::TransferDst);
|
||||
context.render_cmd.clearColorImage(context.render_target->mask.image, vk::ImageLayout::eTransferDstOptimal, clear_color, vkutil::color_subresource_range);
|
||||
context.render_target->mask.transition_to(context.render_cmd, vkutil::ImageLayout::StorageImage);
|
||||
}
|
||||
|
||||
void sync_depth_bias(VKContext &context) {
|
||||
if (!context.is_recording)
|
||||
return;
|
||||
|
||||
context.render_cmd.setDepthBias(static_cast<float>(context.record.depth_bias_unit), 0.0, static_cast<float>(context.record.depth_bias_slope));
|
||||
}
|
||||
|
||||
void sync_depth_data(VKContext &context) {
|
||||
// If force load is enabled to load saved depth and depth data memory exists (the second condition is just for safe, may sometimes contradict its usefulness, hopefully won't)
|
||||
if ((context.record.depth_stencil_surface.zlsControl & SCE_GXM_DEPTH_STENCIL_FORCE_LOAD_ENABLED) || (!context.record.depth_stencil_surface.depthData))
|
||||
return;
|
||||
|
||||
vk::ClearDepthStencilValue clear_value{
|
||||
.depth = context.record.depth_stencil_surface.backgroundDepth
|
||||
};
|
||||
vk::ClearAttachment clear_attachment{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eDepth,
|
||||
.clearValue = { .depthStencil = clear_value }
|
||||
};
|
||||
vk::ClearRect clear_rect{
|
||||
.rect = vk::Rect2D{
|
||||
.offset = { 0, 0 },
|
||||
.extent = { context.render_target->width, context.render_target->height } },
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
context.render_cmd.clearAttachments(clear_attachment, clear_rect);
|
||||
}
|
||||
|
||||
void sync_stencil_data(VKContext &context, const MemState &mem) {
|
||||
if (context.record.depth_stencil_surface.zlsControl & SCE_GXM_DEPTH_STENCIL_FORCE_LOAD_ENABLED)
|
||||
return;
|
||||
|
||||
vk::ClearDepthStencilValue clear_value{
|
||||
.stencil = context.record.depth_stencil_surface.control.content & SceGxmDepthStencilControl::stencil_bits
|
||||
};
|
||||
vk::ClearAttachment clear_attachment{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eStencil,
|
||||
.clearValue = { .depthStencil = clear_value }
|
||||
};
|
||||
vk::ClearRect clear_rect{
|
||||
.rect = vk::Rect2D{
|
||||
.offset = { 0, 0 },
|
||||
.extent = { context.render_target->width, context.render_target->height } },
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
context.render_cmd.clearAttachments(clear_attachment, clear_rect);
|
||||
}
|
||||
|
||||
void sync_point_line_width(VKContext &context, const bool is_front) {
|
||||
if (!context.is_recording)
|
||||
return;
|
||||
|
||||
if (is_front)
|
||||
context.render_cmd.setLineWidth(static_cast<float>(context.record.line_width));
|
||||
}
|
||||
|
||||
void sync_viewport_flat(VKContext &context) {
|
||||
context.viewport = vk::Viewport{
|
||||
.x = 0.0f,
|
||||
.y = 0.0f,
|
||||
.width = static_cast<float>(context.render_target->width),
|
||||
.height = static_cast<float>(context.render_target->height),
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f
|
||||
};
|
||||
|
||||
if (!context.is_recording)
|
||||
return;
|
||||
context.render_cmd.setViewport(0, context.viewport);
|
||||
}
|
||||
|
||||
void sync_viewport_real(VKContext &context, const float xOffset, const float yOffset, const float zOffset,
|
||||
const float xScale, const float yScale, const float zScale) {
|
||||
if (xScale < 0)
|
||||
LOG_ERROR("Game is using a viewport with negative width!");
|
||||
|
||||
const float w = std::abs(2 * xScale);
|
||||
const float h = 2 * yScale;
|
||||
const float x = xOffset - std::abs(xScale);
|
||||
const float y = yOffset - yScale;
|
||||
|
||||
const int res_multiplier = context.state.res_multiplier;
|
||||
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkViewport.html
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vertexpostproc-viewport
|
||||
// on gxm: x_f = xOffset + xScale * (x / w)
|
||||
// on vulkan: x_f = (x + width/2) + width/2 * (x / w)
|
||||
// the depth viewport is applied directly in the shader
|
||||
context.viewport = vk::Viewport{
|
||||
.x = x * res_multiplier,
|
||||
.y = y * res_multiplier,
|
||||
.width = w * res_multiplier,
|
||||
.height = h * res_multiplier,
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f
|
||||
};
|
||||
|
||||
if (!context.is_recording)
|
||||
return;
|
||||
context.render_cmd.setViewport(0, context.viewport);
|
||||
}
|
||||
|
||||
void refresh_pipeline(VKContext &context) {
|
||||
context.refresh_pipeline = true;
|
||||
}
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
@@ -0,0 +1,352 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <renderer/vulkan/functions.h>
|
||||
|
||||
#include <renderer/vulkan/gxm_to_vulkan.h>
|
||||
|
||||
#include <gxm/functions.h>
|
||||
#include <renderer/functions.h>
|
||||
#include <util/align.h>
|
||||
#include <vkutil/vkutil.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
VKTextureCacheState::VKTextureCacheState(VKState &state)
|
||||
: state(state) {}
|
||||
|
||||
void sync_texture(VKContext &context, MemState &mem, std::size_t index, SceGxmTexture texture, const Config &config,
|
||||
const std::string &base_path, const std::string &title_id) {
|
||||
Address data_addr = texture.data_addr << 2;
|
||||
bool is_vertex = index >= SCE_GXM_MAX_TEXTURE_UNITS;
|
||||
|
||||
const size_t texture_size = renderer::texture::texture_size(texture);
|
||||
|
||||
const SceGxmTextureFormat format = gxm::get_format(&texture);
|
||||
const SceGxmTextureBaseFormat base_format = gxm::get_base_format(format);
|
||||
if (gxm::is_paletted_format(base_format) && texture.palette_addr == 0) {
|
||||
LOG_WARN("Ignoring null palette texture");
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_vertex) {
|
||||
// Vertex textures
|
||||
context.current_vert_render_info.integral_texture_query_format[index - SCE_GXM_MAX_TEXTURE_UNITS] = renderer::texture::get_integral_query_format(base_format);
|
||||
} else {
|
||||
context.current_frag_render_info.integral_texture_query_format[index] = renderer::texture::get_integral_query_format(base_format);
|
||||
}
|
||||
|
||||
vkutil::Image *image = nullptr;
|
||||
vk::ComponentMapping swizzle_surface;
|
||||
bool only_nearest = false;
|
||||
|
||||
SceGxmColorBaseFormat format_target_of_texture;
|
||||
|
||||
uint16_t width = static_cast<std::uint16_t>(gxm::get_width(&texture));
|
||||
uint16_t height = static_cast<std::uint16_t>(gxm::get_height(&texture));
|
||||
|
||||
if (renderer::texture::convert_base_texture_format_to_base_color_format(base_format, format_target_of_texture)) {
|
||||
std::uint16_t stride_in_pixels = width;
|
||||
|
||||
if (texture.texture_type() == SCE_GXM_TEXTURE_LINEAR_STRIDED) {
|
||||
stride_in_pixels = static_cast<std::uint16_t>(gxm::get_stride_in_bytes(&texture)) / ((renderer::texture::bits_per_pixel(base_format) + 7) >> 3);
|
||||
}
|
||||
|
||||
vk::ComponentMapping swizzle = texture::translate_swizzle(format);
|
||||
|
||||
image = context.state.surface_cache.retrieve_color_surface_texture_handle(
|
||||
width, height, stride_in_pixels, format_target_of_texture, Ptr<void>(data_addr),
|
||||
renderer::SurfaceTextureRetrievePurpose::READING, swizzle);
|
||||
}
|
||||
|
||||
vk::ImageLayout layout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
if (image) {
|
||||
layout = vk::ImageLayout::eGeneral;
|
||||
} else {
|
||||
// Try to retrieve S24D8 texture
|
||||
SceGxmDepthStencilSurface lookup_temp;
|
||||
lookup_temp.depthData = data_addr;
|
||||
lookup_temp.stencilData.reset();
|
||||
|
||||
image = context.state.surface_cache.retrieve_depth_stencil_texture_handle(mem, lookup_temp, width, height, true);
|
||||
}
|
||||
|
||||
if (image) {
|
||||
if (!image->sampler)
|
||||
image->sampler = texture::create_sampler(context.state, texture);
|
||||
} else {
|
||||
if (!is_valid_addr_range(mem, data_addr, data_addr + texture_size)) {
|
||||
LOG_WARN("Texture has freed data.");
|
||||
return;
|
||||
}
|
||||
renderer::texture::cache_and_bind_texture(context.state.texture_cache, texture, mem);
|
||||
image = context.state.texture_cache.current_texture;
|
||||
}
|
||||
|
||||
vk::DescriptorImageInfo &image_info = is_vertex
|
||||
? context.vertex_textures[index - SCE_GXM_MAX_TEXTURE_UNITS]
|
||||
: context.fragment_textures[index];
|
||||
if (image_info.sampler != image->sampler || image_info.imageView != image->view) {
|
||||
image_info = vk::DescriptorImageInfo{
|
||||
.sampler = image->sampler,
|
||||
.imageView = image->view,
|
||||
.imageLayout = layout
|
||||
};
|
||||
// invalidate last descriptor set
|
||||
if (is_vertex)
|
||||
context.last_vert_texture_count = ~0;
|
||||
else
|
||||
context.last_frag_texture_count = ~0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace texture {
|
||||
|
||||
bool init(VKTextureCacheState &cache, const bool hashless_texture_cache) {
|
||||
cache.select_callback = [&cache](const std::size_t index, const void *texture) {
|
||||
cache.current_texture = &cache.textures[index];
|
||||
};
|
||||
|
||||
cache.configure_texture_callback = [](const renderer::TextureCacheState &text_cache, const void *texture) {
|
||||
configure_bound_texture(text_cache, *reinterpret_cast<const SceGxmTexture *>(texture));
|
||||
};
|
||||
|
||||
cache.upload_texture_callback = [&cache](SceGxmTextureBaseFormat base_format, uint32_t width, uint32_t height, uint32_t mip_index, const void *pixels, int face, bool is_compressed, size_t pixels_per_stride) {
|
||||
upload_bound_texture(cache, base_format, width, height, mip_index, pixels, face, is_compressed, pixels_per_stride);
|
||||
};
|
||||
|
||||
cache.use_protect = hashless_texture_cache;
|
||||
|
||||
vk::FenceCreateInfo fence_info{};
|
||||
cache.wait_fence = cache.state.device.createFence(fence_info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t max_mip(uint32_t pixels) {
|
||||
return std::bit_width(pixels);
|
||||
}
|
||||
|
||||
void configure_bound_texture(const renderer::TextureCacheState &state, const SceGxmTexture &gxm_texture) {
|
||||
const SceGxmTextureFormat format = gxm::get_format(&gxm_texture);
|
||||
const SceGxmTextureBaseFormat base_format = gxm::get_base_format(format);
|
||||
|
||||
const bool mipmap_enabled = static_cast<bool>(gxm_texture.mip_filter);
|
||||
|
||||
const vk::ComponentMapping swizzle = translate_swizzle(format);
|
||||
|
||||
const bool is_cube = (gxm_texture.texture_type() == SCE_GXM_TEXTURE_CUBE || gxm_texture.texture_type() == SCE_GXM_TEXTURE_CUBE_ARBITRARY);
|
||||
|
||||
uint32_t width = static_cast<uint32_t>(gxm::get_width(&gxm_texture));
|
||||
uint32_t height = static_cast<uint32_t>(gxm::get_height(&gxm_texture));
|
||||
if (gxm::is_block_compressed_format(base_format)) {
|
||||
// align width and height to block size
|
||||
width = align(width, 4);
|
||||
height = align(height, 4);
|
||||
}
|
||||
|
||||
uint16_t mip_count = renderer::texture::get_upload_mip(gxm_texture.true_mip_count(), width, height, base_format);
|
||||
if (gxm_texture.texture_type() == SCE_GXM_TEXTURE_LINEAR_STRIDED) {
|
||||
mip_count = 1;
|
||||
}
|
||||
|
||||
const vk::Format vk_format = translate_format(base_format);
|
||||
const auto texture_type = gxm_texture.texture_type();
|
||||
const bool is_swizzled = (texture_type == SCE_GXM_TEXTURE_SWIZZLED) || (texture_type == SCE_GXM_TEXTURE_CUBE) || (texture_type == SCE_GXM_TEXTURE_SWIZZLED_ARBITRARY) || (texture_type == SCE_GXM_TEXTURE_CUBE_ARBITRARY);
|
||||
const auto base_fmt = gxm::get_base_format(format);
|
||||
|
||||
const VKTextureCacheState &cache = static_cast<const VKTextureCacheState &>(state);
|
||||
vkutil::Image &image = *cache.current_texture;
|
||||
|
||||
// manually initialize the image
|
||||
image.allocator = cache.state.allocator;
|
||||
image.width = width;
|
||||
image.height = height;
|
||||
image.format = vk_format;
|
||||
|
||||
// create image
|
||||
vk::ImageCreateInfo image_info{
|
||||
.flags = is_cube ? vk::ImageCreateFlagBits::eCubeCompatible : vk::ImageCreateFlags(),
|
||||
.imageType = vk::ImageType::e2D,
|
||||
.format = vk_format,
|
||||
.extent = vk::Extent3D{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.depth = 1 },
|
||||
.mipLevels = mip_count,
|
||||
.arrayLayers = is_cube ? 6U : 1U,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = vk::ImageTiling::eOptimal,
|
||||
.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
};
|
||||
|
||||
std::tie(image.image, image.allocation) = image.allocator.createImage(image_info, vkutil::vma_auto_alloc);
|
||||
|
||||
// create image view
|
||||
vk::ImageSubresourceRange range{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = mip_count,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = is_cube ? 6U : 1U
|
||||
};
|
||||
|
||||
vk::ImageViewCreateInfo view_info{
|
||||
.image = image.image,
|
||||
.viewType = is_cube ? vk::ImageViewType::eCube : vk::ImageViewType::e2D,
|
||||
.format = vk_format,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range
|
||||
};
|
||||
image.view = cache.state.device.createImageView(view_info);
|
||||
|
||||
image.sampler = create_sampler(cache.state, gxm_texture, mip_count);
|
||||
}
|
||||
|
||||
vk::Sampler create_sampler(VKState &state, const SceGxmTexture &gxm_texture, const uint16_t mip_count) {
|
||||
const SceGxmTextureAddrMode uaddr = static_cast<SceGxmTextureAddrMode>(gxm_texture.uaddr_mode);
|
||||
const SceGxmTextureAddrMode vaddr = static_cast<SceGxmTextureAddrMode>(gxm_texture.vaddr_mode);
|
||||
const SceGxmTextureFilter min_filter = static_cast<SceGxmTextureFilter>(gxm_texture.min_filter);
|
||||
const SceGxmTextureFilter mag_filter = static_cast<SceGxmTextureFilter>(gxm_texture.mag_filter);
|
||||
const bool mipmap_enabled = static_cast<bool>(gxm_texture.mip_filter);
|
||||
|
||||
// create sampler
|
||||
vk::SamplerCreateInfo sampler_info{
|
||||
.magFilter = translate_filter(mag_filter),
|
||||
.minFilter = translate_filter(min_filter),
|
||||
.mipmapMode = translate_mimpmap_mode(min_filter),
|
||||
.addressModeU = translate_address_mode(uaddr),
|
||||
.addressModeV = translate_address_mode(vaddr),
|
||||
.addressModeW = vk::SamplerAddressMode::eRepeat,
|
||||
.mipLodBias = (static_cast<float>(gxm_texture.lod_bias) - 31.f) / 8.f,
|
||||
.anisotropyEnable = state.texture_cache.anisotropic_filtering > 1,
|
||||
.maxAnisotropy = static_cast<float>(state.texture_cache.anisotropic_filtering),
|
||||
.compareEnable = VK_FALSE,
|
||||
.minLod = mipmap_enabled ? static_cast<float>(std::min<uint16_t>(mip_count, gxm_texture.lod_min0 | (gxm_texture.lod_min1 << 2))) : 0.f,
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSamplerCreateInfo.html
|
||||
// if there is no mipmap, set maxLod to 0.25 so it uses both the magnification or minification filter when needed
|
||||
.maxLod = mipmap_enabled ? static_cast<float>(mip_count) : 0.25f,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
};
|
||||
return state.device.createSampler(sampler_info);
|
||||
}
|
||||
|
||||
// add an alpha channel to u8u8u8 textures
|
||||
static void *add_alpha_channel(const void *pixels, const uint32_t width, const uint32_t height, std::vector<uint8_t> &data) {
|
||||
data.resize(width * height * 4);
|
||||
|
||||
const uint8_t *src = reinterpret_cast<const uint8_t *>(pixels);
|
||||
uint8_t *dst = data.data();
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[2];
|
||||
// set 1.0 as the alpha channel
|
||||
dst[3] = 255;
|
||||
|
||||
src += 3;
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
|
||||
return data.data();
|
||||
}
|
||||
|
||||
void upload_bound_texture(VKTextureCacheState &cache, SceGxmTextureBaseFormat base_format, uint32_t width, uint32_t height,
|
||||
uint32_t mip_index, const void *pixels, int face, bool is_compressed, size_t pixels_per_stride) {
|
||||
vkutil::Image &image = *cache.current_texture;
|
||||
|
||||
const void *text_data = pixels;
|
||||
std::vector<uint8_t> temp_data;
|
||||
if (base_format == SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8) {
|
||||
text_data = add_alpha_channel(pixels, width, height, temp_data);
|
||||
base_format = SCE_GXM_TEXTURE_BASE_FORMAT_U8U8U8U8;
|
||||
}
|
||||
|
||||
if (face > 0)
|
||||
face--;
|
||||
|
||||
if (pixels_per_stride == 0)
|
||||
pixels_per_stride = width;
|
||||
|
||||
vk::DeviceSize upload_size;
|
||||
if (is_compressed) {
|
||||
upload_size = renderer::texture::get_compressed_size(base_format, width, height);
|
||||
} else {
|
||||
size_t bpp = renderer::texture::bits_per_pixel(base_format);
|
||||
size_t bytes_per_pixel = (bpp + 7) >> 3;
|
||||
upload_size = pixels_per_stride * height * bytes_per_pixel;
|
||||
}
|
||||
|
||||
if (cache.alloc_info.size < upload_size) {
|
||||
// we need to create a bigger buffer
|
||||
if (cache.staging_buffer) {
|
||||
// delete the previous one if there is
|
||||
cache.state.allocator.destroyBuffer(cache.staging_buffer, cache.alloc);
|
||||
}
|
||||
|
||||
vk::BufferCreateInfo buffer_info{
|
||||
.size = upload_size,
|
||||
.usage = vk::BufferUsageFlagBits::eTransferSrc,
|
||||
.sharingMode = vk::SharingMode::eExclusive
|
||||
};
|
||||
std::tie(cache.staging_buffer, cache.alloc) = cache.state.allocator.createBuffer(buffer_info, vkutil::vma_mapped_alloc, cache.alloc_info);
|
||||
}
|
||||
|
||||
memcpy(cache.alloc_info.pMappedData, text_data, upload_size);
|
||||
vk::CommandBuffer cmd_buffer = vkutil::create_single_time_command(cache.state.device, cache.state.transfer_command_pool);
|
||||
|
||||
vk::ImageSubresourceRange range{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.baseMipLevel = mip_index,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = static_cast<uint32_t>(face),
|
||||
.layerCount = 1
|
||||
};
|
||||
vkutil::transition_image_layout(cmd_buffer, image.image, vkutil::ImageLayout::Undefined, vkutil::ImageLayout::TransferDst, range);
|
||||
vk::ImageSubresourceLayers layer{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = mip_index,
|
||||
.baseArrayLayer = static_cast<uint32_t>(face),
|
||||
.layerCount = 1
|
||||
};
|
||||
vk::BufferImageCopy region{
|
||||
.bufferOffset = 0,
|
||||
.bufferRowLength = static_cast<uint32_t>(pixels_per_stride),
|
||||
.bufferImageHeight = height,
|
||||
.imageSubresource = layer,
|
||||
.imageOffset = { 0, 0, 0 },
|
||||
.imageExtent = { width, height, 1 }
|
||||
};
|
||||
cmd_buffer.copyBufferToImage(cache.staging_buffer, image.image, vk::ImageLayout::eTransferDstOptimal, region);
|
||||
vkutil::transition_image_layout(cmd_buffer, image.image, vkutil::ImageLayout::TransferDst, vkutil::ImageLayout::SampledImage, range);
|
||||
cmd_buffer.end();
|
||||
|
||||
vk::Device device = cache.state.device;
|
||||
|
||||
vk::SubmitInfo submit_info{};
|
||||
submit_info.setCommandBuffers(cmd_buffer);
|
||||
cache.state.transfer_queue.submit(submit_info, cache.wait_fence);
|
||||
device.waitForFences(cache.wait_fence, VK_TRUE, std::numeric_limits<uint64_t>::max());
|
||||
device.resetFences(cache.wait_fence);
|
||||
device.freeCommandBuffers(cache.state.transfer_command_pool, cmd_buffer);
|
||||
}
|
||||
} // namespace texture
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
@@ -39,7 +39,38 @@ static constexpr float INTEGRAL_TEX_QUERY_TYPE_8BIT_SIGNED = 3.0;
|
||||
static constexpr float INTEGRAL_TEX_QUERY_TYPE_8BIT_UNSIGNED = 2.0;
|
||||
static constexpr float INTEGRAL_TEX_QUERY_TYPE_16BIT = 1.0;
|
||||
static constexpr float INTEGRAL_TEX_QUERY_TYPE_32BIT = 0.0;
|
||||
static constexpr std::uint32_t CURRENT_VERSION = 3;
|
||||
static constexpr std::uint32_t CURRENT_VERSION = 4;
|
||||
|
||||
struct RenderVertUniformBlock {
|
||||
std::array<float, 4> viewport_flip;
|
||||
float viewport_flag;
|
||||
float screen_width;
|
||||
float screen_height;
|
||||
float padding;
|
||||
float integral_texture_query_format[SCE_GXM_MAX_TEXTURE_UNITS];
|
||||
float z_offset;
|
||||
float z_scale;
|
||||
};
|
||||
|
||||
struct RenderFragUniformBlock {
|
||||
float back_disabled = 0;
|
||||
float front_disabled = 0;
|
||||
float writing_mask = 0;
|
||||
float use_raw_image = 0;
|
||||
float integral_texture_query_format[SCE_GXM_MAX_TEXTURE_UNITS];
|
||||
int32_t res_multiplier = 0;
|
||||
};
|
||||
|
||||
enum struct Target {
|
||||
GLSLOpenGL,
|
||||
SpirVOpenGL,
|
||||
SpirVVulkan
|
||||
};
|
||||
|
||||
struct GeneratedShader {
|
||||
std::string glsl;
|
||||
usse::SpirvCode spirv;
|
||||
};
|
||||
|
||||
// Used if the GPU does not support features.support_unknown_format
|
||||
extern SceGxmColorBaseFormat last_color_format;
|
||||
@@ -47,11 +78,10 @@ extern SceGxmColorBaseFormat last_color_format;
|
||||
// Dump generated SPIR-V disassembly up to this point
|
||||
void spirv_disasm_print(const usse::SpirvCode &spirv_binary, std::string *spirv_dump = nullptr);
|
||||
|
||||
usse::SpirvCode convert_gxp_to_spirv(const SceGxmProgram &program, const std::string &shader_hash, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes = nullptr, bool maskupdate = false,
|
||||
// the returned object will only have its glsl or spirv field non-empty depending on the target
|
||||
GeneratedShader convert_gxp(const SceGxmProgram &program, const std::string &shader_hash, const FeatureState &features, const Target target, const std::vector<SceGxmVertexAttribute> *hint_attributes = nullptr, bool maskupdate = false,
|
||||
bool force_shader_debug = false, std::function<bool(const std::string &ext, const std::string &dump)> dumper = nullptr);
|
||||
|
||||
std::string convert_gxp_to_glsl(const SceGxmProgram &program, const std::string &shader_hash, const FeatureState &features,
|
||||
const std::vector<SceGxmVertexAttribute> *hint_attributes = nullptr, bool maskupdate = false, bool force_shader_debug = false, std::function<bool(const std::string &ext, const std::string &dump)> dumper = nullptr);
|
||||
void convert_gxp_to_glsl_from_filepath(const std::string &shader_filepath);
|
||||
|
||||
} // namespace shader
|
||||
|
||||
@@ -198,15 +198,23 @@ public:
|
||||
|
||||
struct AttributeInformation {
|
||||
std::uint32_t info;
|
||||
// this is needed for Vulkan as it doesn't implicitly convert between integers and floats and between signed and unsigned
|
||||
bool is_integer;
|
||||
// only meaningful is is_integer is true
|
||||
bool is_signed;
|
||||
bool regformat;
|
||||
|
||||
explicit AttributeInformation()
|
||||
: info(0)
|
||||
, is_integer(false)
|
||||
, is_signed(false)
|
||||
, regformat(false) {
|
||||
}
|
||||
|
||||
explicit AttributeInformation(const std::uint16_t loc, const std::uint16_t gxm_type, const bool regformat)
|
||||
explicit AttributeInformation(const std::uint16_t loc, const std::uint16_t gxm_type, const bool is_integer, const bool is_signed, const bool regformat)
|
||||
: info(loc | (static_cast<std::uint32_t>(gxm_type) << 16))
|
||||
, is_integer(is_integer)
|
||||
, is_signed(is_signed)
|
||||
, regformat(regformat) {
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
static constexpr bool LOG_SHADER_CODE = true;
|
||||
static constexpr bool LOG_SHADER_CODE = false;
|
||||
static constexpr bool DUMP_SPIRV_BINARIES = false;
|
||||
|
||||
using namespace shader::usse;
|
||||
@@ -112,13 +112,14 @@ struct TranslationState {
|
||||
spv::Id color_attachment_id = spv::NoResult;
|
||||
spv::Id color_attachment_raw_id = spv::NoResult;
|
||||
spv::Id mask_id = spv::NoResult;
|
||||
spv::Id frag_coord_id = spv::NoResult; ///< gl_FragCoord, not built-in in SPIR-V.
|
||||
spv::Id frag_coord_id = spv::NoResult;
|
||||
spv::Id render_info_id = spv::NoResult;
|
||||
std::vector<VarToReg> var_to_regs;
|
||||
std::vector<spv::Id> interfaces;
|
||||
bool is_maskupdate{};
|
||||
bool is_fragment{};
|
||||
bool should_gl_spirv_compatible{};
|
||||
bool is_maskupdate = false;
|
||||
bool is_fragment = false;
|
||||
bool is_target_glsl = false;
|
||||
bool is_vulkan = false;
|
||||
spv::ImageFormat image_storage_format = spv::ImageFormat::ImageFormatUnknown;
|
||||
};
|
||||
|
||||
@@ -512,6 +513,8 @@ static void create_fragment_inputs(spv::Builder &b, SpirvShaderParameters ¶m
|
||||
// log only once
|
||||
LOG_INFO("Sample symbol stripped, using anonymous name");
|
||||
|
||||
if (anonymous && sampler_resource_index == 0)
|
||||
LOG_WARN("Fragment shader has more than one anonymous texture");
|
||||
anonymous = true;
|
||||
tex_name = fmt::format("anonymousTexture{}", anon_tex_count++);
|
||||
}
|
||||
@@ -612,6 +615,8 @@ static void create_fragment_inputs(spv::Builder &b, SpirvShaderParameters ¶m
|
||||
tex_query_info.sampler = create_param_sampler(b, (program.is_vertex() ? "vertTex_" : "fragTex_") + tex_name, dim_type);
|
||||
|
||||
b.addDecoration(tex_query_info.sampler, spv::DecorationBinding, sampler_resource_index);
|
||||
if (translation_state.is_vulkan)
|
||||
b.addDecoration(tex_query_info.sampler, spv::DecorationDescriptorSet, program.is_vertex() ? 2 : 3);
|
||||
samplers[sampler_resource_index] = tex_query_info.sampler;
|
||||
} else {
|
||||
tex_query_info.sampler = samplers[sampler_resource_index];
|
||||
@@ -638,13 +643,13 @@ static void create_fragment_inputs(spv::Builder &b, SpirvShaderParameters ¶m
|
||||
}
|
||||
|
||||
coords[query_info.coord_index].first = b.createVariable(spv::NoPrecision, spv::StorageClassInput,
|
||||
b.makeVectorType(b.makeFloatType(32), /*tex_coord_comp_count*/ 4), coord_name.c_str());
|
||||
b.makeVectorType(b.makeFloatType(32), /*tex_coord_comp_count*/ query_info.coord_index == 10 ? 2 : 4), coord_name.c_str());
|
||||
|
||||
if (query_info.coord_index == 10) {
|
||||
if (query_info.coord_index == 10)
|
||||
b.addDecoration(coords[query_info.coord_index].first, spv::DecorationBuiltIn, spv::BuiltInPointCoord);
|
||||
}
|
||||
else
|
||||
b.addDecoration(coords[query_info.coord_index].first, spv::DecorationLocation, TEXCOORD_BASE_LOCATION + query_info.coord_index);
|
||||
|
||||
b.addDecoration(coords[query_info.coord_index].first, spv::DecorationLocation, TEXCOORD_BASE_LOCATION + query_info.coord_index);
|
||||
translation_state.interfaces.push_back(coords[query_info.coord_index].first);
|
||||
|
||||
coords[query_info.coord_index].second = static_cast<int>(DataType::F32);
|
||||
@@ -653,10 +658,17 @@ static void create_fragment_inputs(spv::Builder &b, SpirvShaderParameters ¶m
|
||||
query_info.coord = coords[query_info.coord_index];
|
||||
}
|
||||
|
||||
auto mask = create_builtin_sampler(b, features, translation_state, "f_mask");
|
||||
translation_state.mask_id = mask;
|
||||
if (features.use_mask_bit) {
|
||||
const spv::Id mask = create_builtin_sampler(b, features, translation_state, "f_mask");
|
||||
translation_state.mask_id = mask;
|
||||
|
||||
b.addDecoration(mask, spv::DecorationBinding, MASK_TEXTURE_SLOT_IMAGE);
|
||||
if (translation_state.is_vulkan) {
|
||||
b.addDecoration(mask, spv::DecorationBinding, 1);
|
||||
b.addDecoration(mask, spv::DecorationDescriptorSet, 1);
|
||||
} else {
|
||||
b.addDecoration(mask, spv::DecorationBinding, MASK_TEXTURE_SLOT_IMAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (program.is_frag_color_used()) {
|
||||
// There might be a chance that this shader also reads from OUTPUT bank. We will load last state frag data
|
||||
@@ -678,22 +690,37 @@ static void create_fragment_inputs(spv::Builder &b, SpirvShaderParameters ¶m
|
||||
}
|
||||
};
|
||||
|
||||
if (features.direct_fragcolor) {
|
||||
// The GPU supports gl_LastFragData. It's only OpenGL though
|
||||
// TODO: Make this only emit with OpenGL
|
||||
spv::Id last_frag_data = b.createVariable(spv::NoPrecision, spv::StorageClassInput, v4, "last_frag_data");
|
||||
if (features.direct_fragcolor && (translation_state.is_vulkan || translation_state.is_target_glsl)) {
|
||||
// The GPU supports gl_LastFragData.
|
||||
// On Vulkan this is a subpass input, it is similar to gl_LastFragData (and should have the same speed on integrated GPUs)
|
||||
// This is not supported on OpenGL with SpirV
|
||||
const spv::Id image_type = b.makeImageType(f32, spv::DimSubpassData, false, false, false, 2, spv::ImageFormatUnknown);
|
||||
const spv::Id last_frag_data = b.createVariable(spv::NoPrecision, spv::StorageClassUniformConstant, image_type, "last_frag_data");
|
||||
b.addDecoration(last_frag_data, spv::DecorationInputAttachmentIndex, 0);
|
||||
translation_state.interfaces.push_back(last_frag_data);
|
||||
if (translation_state.is_vulkan) {
|
||||
b.addDecoration(last_frag_data, spv::DecorationBinding, 0);
|
||||
b.addDecoration(last_frag_data, spv::DecorationDescriptorSet, 1);
|
||||
}
|
||||
|
||||
// we must read from the image at coordinates (0,0) (offset from the current pixel) to get the last pixel value
|
||||
spv::Id coord_0 = b.makeIntConstant(0);
|
||||
const spv::Id ivec2 = b.makeVectorType(b.makeIntType(32), 2);
|
||||
coord_0 = b.makeCompositeConstant(ivec2, { coord_0, coord_0 });
|
||||
source = b.createOp(spv::OpImageRead, v4, { b.createLoad(last_frag_data, spv::NoPrecision), coord_0 });
|
||||
|
||||
// Copy outs into. The output data from last stage should has the same format as our
|
||||
source = b.createLoad(last_frag_data, spv::NoPrecision);
|
||||
translation_state.last_frag_data_id = last_frag_data;
|
||||
} else if (features.support_shader_interlock || features.support_texture_barrier) {
|
||||
// Create a global sampler, which is our color attachment
|
||||
spv::Id color_attachment = create_builtin_sampler(b, features, translation_state, "f_colorAttachment");
|
||||
spv::Id color_attachment_raw = spv::NoResult;
|
||||
|
||||
b.addDecoration(color_attachment, spv::DecorationBinding, COLOR_ATTACHMENT_TEXTURE_SLOT_IMAGE);
|
||||
if (translation_state.is_vulkan) {
|
||||
// this can happen only with shader interlock as a color attachment texture can be sampled only as an attachment in vulkan
|
||||
b.addDecoration(color_attachment, spv::DecorationBinding, 1);
|
||||
b.addDecoration(color_attachment, spv::DecorationDescriptorSet, 1);
|
||||
} else {
|
||||
b.addDecoration(color_attachment, spv::DecorationBinding, COLOR_ATTACHMENT_TEXTURE_SLOT_IMAGE);
|
||||
}
|
||||
translation_state.color_attachment_id = color_attachment;
|
||||
|
||||
spv::Id i32 = b.makeIntegerType(32, true);
|
||||
@@ -704,7 +731,12 @@ static void create_fragment_inputs(spv::Builder &b, SpirvShaderParameters ¶m
|
||||
spv::Id uiv4 = b.makeVectorType(b.makeUintType(32), 4);
|
||||
|
||||
color_attachment_raw = create_builtin_sampler_for_raw(b, features, translation_state, "f_colorAttachment_rawUI");
|
||||
b.addDecoration(color_attachment_raw, spv::DecorationBinding, COLOR_ATTACHMENT_RAW_TEXTURE_SLOT_IMAGE);
|
||||
if (translation_state.is_vulkan) {
|
||||
b.addDecoration(color_attachment_raw, spv::DecorationBinding, 2);
|
||||
b.addDecoration(color_attachment_raw, spv::DecorationDescriptorSet, 1);
|
||||
} else {
|
||||
b.addDecoration(color_attachment_raw, spv::DecorationBinding, COLOR_ATTACHMENT_RAW_TEXTURE_SLOT_IMAGE);
|
||||
}
|
||||
translation_state.color_attachment_raw_id = color_attachment_raw;
|
||||
|
||||
spv::Id load_normal_cond = b.createBinOp(spv::OpFOrdLessThan, b.makeBoolType(), b.createAccessChain(spv::StorageClassPrivate, translation_state.render_info_id, { b.makeIntConstant(3) }), b.makeFloatConstant(0.5f));
|
||||
@@ -762,16 +794,17 @@ static void copy_uniform_block_to_register(spv::Builder &builder, spv::Id sa_ban
|
||||
|
||||
utils::make_for_loop(builder, ite, builder.makeIntConstant(0), builder.makeIntConstant(vec4_count), [&]() {
|
||||
spv::Id to_copy = builder.createAccessChain(spv::StorageClassStorageBuffer, block, { builder.createLoad(ite, spv::NoPrecision) });
|
||||
to_copy = builder.createLoad(to_copy, spv::NoPrecision);
|
||||
spv::Id dest = builder.createAccessChain(spv::StorageClassPrivate, sa_bank, { builder.createBinOp(spv::OpIAdd, builder.getTypeId(builder.createLoad(ite, spv::NoPrecision)), builder.createLoad(ite, spv::NoPrecision), builder.makeIntConstant(start_in_vec4_granularity)) });
|
||||
spv::Id dest_friend = spv::NoResult;
|
||||
|
||||
if (start % 4 == 0) {
|
||||
builder.createStore(builder.createLoad(to_copy, spv::NoPrecision), dest);
|
||||
builder.createStore(to_copy, dest);
|
||||
} else {
|
||||
dest_friend = builder.createAccessChain(spv::StorageClassPrivate, sa_bank, { builder.createBinOp(spv::OpIAdd, builder.getTypeId(builder.createLoad(ite, spv::NoPrecision)), builder.createLoad(ite, spv::NoPrecision), builder.makeIntConstant(start_in_vec4_granularity + 1)) });
|
||||
|
||||
std::vector<spv::Id> ops_copy_1 = { dest, to_copy };
|
||||
std::vector<spv::Id> ops_copy_2 = { dest_friend, to_copy };
|
||||
std::vector<spv::Id> ops_copy_1 = { builder.createLoad(dest, spv::NoPrecision), to_copy };
|
||||
std::vector<spv::Id> ops_copy_2 = { builder.createLoad(dest_friend, spv::NoPrecision), to_copy };
|
||||
|
||||
for (int i = 0; i < start % 4; i++) {
|
||||
ops_copy_1.push_back(i);
|
||||
@@ -786,8 +819,8 @@ static void copy_uniform_block_to_register(spv::Builder &builder, spv::Id sa_ban
|
||||
to_copy = builder.createOp(spv::OpVectorShuffle, builder.getTypeId(to_copy), ops_copy_1);
|
||||
spv::Id to_copy_2 = builder.createOp(spv::OpVectorShuffle, builder.getTypeId(to_copy), ops_copy_2);
|
||||
|
||||
builder.createStore(builder.createLoad(to_copy, spv::NoPrecision), dest);
|
||||
builder.createStore(builder.createLoad(to_copy_2, spv::NoPrecision), dest_friend);
|
||||
builder.createStore(to_copy, dest);
|
||||
builder.createStore(to_copy_2, dest_friend);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -867,7 +900,9 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
is_vert ? "vertexDataType" : "fragmentDataType");
|
||||
|
||||
b.addDecoration(buffer_container_type, spv::DecorationBlock);
|
||||
b.addDecoration(buffer_container_type, spv::DecorationGLSLShared);
|
||||
if (translation_state.is_target_glsl) {
|
||||
b.addDecoration(buffer_container_type, spv::DecorationGLSLShared);
|
||||
}
|
||||
|
||||
spv_params.buffer_container = b.createVariable(spv::NoPrecision, spv::StorageClassStorageBuffer, buffer_container_type,
|
||||
is_vert ? "vertexData" : "fragmentData");
|
||||
@@ -875,6 +910,8 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
b.addDecoration(spv_params.buffer_container, spv::DecorationRestrict);
|
||||
b.addDecoration(spv_params.buffer_container, spv::DecorationNonWritable);
|
||||
b.addDecoration(spv_params.buffer_container, spv::DecorationBinding, is_vert ? 0 : 1);
|
||||
if (translation_state.is_vulkan)
|
||||
b.addDecoration(spv_params.buffer_container, spv::DecorationDescriptorSet, 0);
|
||||
|
||||
for (auto &[index, buffer] : spv_params.buffers) {
|
||||
const std::string member_name = fmt::format("buffer{}", index);
|
||||
@@ -901,24 +938,6 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
spv::Id type = utils::make_vector_or_scalar_type(b, b.makeIntType(32), num_comp);
|
||||
var = b.createVariable(spv::NoPrecision, spv::StorageClassInput, type, name.c_str());
|
||||
|
||||
// should not happen, but just in case
|
||||
switch (semantic) {
|
||||
case SCE_GXM_PARAMETER_SEMANTIC_INDEX:
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInVertexId);
|
||||
break;
|
||||
|
||||
case SCE_GXM_PARAMETER_SEMANTIC_INSTANCE:
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInInstanceId);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (location != -1) {
|
||||
b.addDecoration(var, spv::DecorationLocation, location);
|
||||
}
|
||||
|
||||
VarToReg var_to_reg = {};
|
||||
var_to_reg.var = var;
|
||||
var_to_reg.pa = pa;
|
||||
@@ -929,23 +948,6 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
} else {
|
||||
var = b.createVariable(spv::NoPrecision, spv::StorageClassInput, param_type, name.c_str());
|
||||
|
||||
switch (semantic) {
|
||||
case SCE_GXM_PARAMETER_SEMANTIC_INDEX:
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInVertexId);
|
||||
break;
|
||||
|
||||
case SCE_GXM_PARAMETER_SEMANTIC_INSTANCE:
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInInstanceId);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (location != -1) {
|
||||
b.addDecoration(var, spv::DecorationLocation, location);
|
||||
}
|
||||
|
||||
VarToReg var_to_reg = {};
|
||||
var_to_reg.var = var;
|
||||
var_to_reg.pa = pa;
|
||||
@@ -955,6 +957,28 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
translation_state.var_to_regs.push_back(var_to_reg);
|
||||
}
|
||||
|
||||
switch (semantic) {
|
||||
case SCE_GXM_PARAMETER_SEMANTIC_INDEX:
|
||||
if (translation_state.is_vulkan)
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInVertexIndex);
|
||||
else
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInVertexId);
|
||||
break;
|
||||
|
||||
case SCE_GXM_PARAMETER_SEMANTIC_INSTANCE:
|
||||
if (translation_state.is_vulkan)
|
||||
// InstanceIndex = InstanceId - BaseInstance, but BaseInstance is always 0 for gxm
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInInstanceIndex);
|
||||
else
|
||||
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInInstanceId);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (location != -1) {
|
||||
b.addDecoration(var, spv::DecorationLocation, location);
|
||||
}
|
||||
}
|
||||
|
||||
translation_state.interfaces.push_back(var);
|
||||
};
|
||||
|
||||
@@ -962,8 +986,13 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
const auto sampler_spv_var = create_param_sampler(b, (program.is_vertex() ? "vertTex_" : "fragTex_") + sampler.name, (sampler.is_cube ? spv::DimCube : spv::Dim2D));
|
||||
samplers.emplace(sampler.index, sampler_spv_var);
|
||||
|
||||
// Prefer smaller slot index for fragments since they are gonna be used frequently.
|
||||
b.addDecoration(sampler_spv_var, spv::DecorationBinding, sampler.index + (program.is_vertex() ? SCE_GXM_MAX_TEXTURE_UNITS : 0));
|
||||
if (translation_state.is_vulkan) {
|
||||
b.addDecoration(sampler_spv_var, spv::DecorationBinding, sampler.index);
|
||||
b.addDecoration(sampler_spv_var, spv::DecorationDescriptorSet, program.is_vertex() ? 2 : 3);
|
||||
} else {
|
||||
// Prefer smaller slot index for fragments since they are gonna be used frequently.
|
||||
b.addDecoration(sampler_spv_var, spv::DecorationBinding, sampler.index + (program.is_vertex() ? SCE_GXM_MAX_TEXTURE_UNITS : 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Log parameters
|
||||
@@ -1093,39 +1122,47 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
|
||||
if (program_type == SceGxmProgramType::Vertex) {
|
||||
// Create the default reg uniform buffer
|
||||
spv::Id render_buf_type = b.makeStructType({ v4, f32, f32, f32, texture_format_arr }, "GxmRenderVertBufferBlock");
|
||||
spv::Id render_buf_type = b.makeStructType({ v4, f32, f32, f32, texture_format_arr, f32, f32 }, "GxmRenderVertBufferBlock");
|
||||
b.addDecoration(render_buf_type, spv::DecorationBlock);
|
||||
b.addDecoration(render_buf_type, spv::DecorationGLSLShared);
|
||||
if (translation_state.is_target_glsl)
|
||||
b.addDecoration(render_buf_type, spv::DecorationGLSLShared);
|
||||
|
||||
b.addMemberDecoration(render_buf_type, 0, spv::DecorationOffset, 0);
|
||||
b.addMemberDecoration(render_buf_type, 1, spv::DecorationOffset, 16);
|
||||
b.addMemberDecoration(render_buf_type, 2, spv::DecorationOffset, 20);
|
||||
b.addMemberDecoration(render_buf_type, 3, spv::DecorationOffset, 24);
|
||||
b.addMemberDecoration(render_buf_type, 4, spv::DecorationOffset, 32);
|
||||
b.addMemberDecoration(render_buf_type, 0, spv::DecorationOffset, offsetof(RenderVertUniformBlock, viewport_flip));
|
||||
b.addMemberDecoration(render_buf_type, 1, spv::DecorationOffset, offsetof(RenderVertUniformBlock, viewport_flag));
|
||||
b.addMemberDecoration(render_buf_type, 2, spv::DecorationOffset, offsetof(RenderVertUniformBlock, screen_width));
|
||||
b.addMemberDecoration(render_buf_type, 3, spv::DecorationOffset, offsetof(RenderVertUniformBlock, screen_height));
|
||||
b.addMemberDecoration(render_buf_type, 4, spv::DecorationOffset, offsetof(RenderVertUniformBlock, integral_texture_query_format));
|
||||
b.addMemberDecoration(render_buf_type, 5, spv::DecorationOffset, offsetof(RenderVertUniformBlock, z_offset));
|
||||
b.addMemberDecoration(render_buf_type, 6, spv::DecorationOffset, offsetof(RenderVertUniformBlock, z_scale));
|
||||
|
||||
b.addMemberName(render_buf_type, 0, "viewport_flip");
|
||||
b.addMemberName(render_buf_type, 1, "viewport_flag");
|
||||
b.addMemberName(render_buf_type, 2, "screen_width");
|
||||
b.addMemberName(render_buf_type, 3, "screen_height");
|
||||
b.addMemberName(render_buf_type, 4, "integral_query_formats");
|
||||
b.addMemberName(render_buf_type, 5, "z_offset");
|
||||
b.addMemberName(render_buf_type, 6, "z_scale");
|
||||
|
||||
translation_state.render_info_id = b.createVariable(spv::NoPrecision, spv::StorageClassUniform, render_buf_type, "renderVertInfo");
|
||||
|
||||
b.addDecoration(translation_state.render_info_id, spv::DecorationBinding, 2);
|
||||
if (translation_state.is_vulkan)
|
||||
b.addDecoration(translation_state.render_info_id, spv::DecorationDescriptorSet, 0);
|
||||
}
|
||||
|
||||
if (program_type == SceGxmProgramType::Fragment) {
|
||||
spv::Id render_buf_type = b.makeStructType({ f32, f32, f32, f32, texture_format_arr, i32 }, "GxmRenderFragBufferBlock");
|
||||
|
||||
b.addDecoration(render_buf_type, spv::DecorationBlock);
|
||||
b.addDecoration(render_buf_type, spv::DecorationGLSLShared);
|
||||
if (translation_state.is_target_glsl)
|
||||
b.addDecoration(render_buf_type, spv::DecorationGLSLShared);
|
||||
|
||||
b.addMemberDecoration(render_buf_type, 0, spv::DecorationOffset, 0);
|
||||
b.addMemberDecoration(render_buf_type, 1, spv::DecorationOffset, 4);
|
||||
b.addMemberDecoration(render_buf_type, 2, spv::DecorationOffset, 8);
|
||||
b.addMemberDecoration(render_buf_type, 3, spv::DecorationOffset, 12);
|
||||
b.addMemberDecoration(render_buf_type, 4, spv::DecorationOffset, 16);
|
||||
b.addMemberDecoration(render_buf_type, 5, spv::DecorationOffset, 80);
|
||||
b.addMemberDecoration(render_buf_type, 0, spv::DecorationOffset, offsetof(RenderFragUniformBlock, back_disabled));
|
||||
b.addMemberDecoration(render_buf_type, 1, spv::DecorationOffset, offsetof(RenderFragUniformBlock, front_disabled));
|
||||
b.addMemberDecoration(render_buf_type, 2, spv::DecorationOffset, offsetof(RenderFragUniformBlock, writing_mask));
|
||||
b.addMemberDecoration(render_buf_type, 3, spv::DecorationOffset, offsetof(RenderFragUniformBlock, use_raw_image));
|
||||
b.addMemberDecoration(render_buf_type, 4, spv::DecorationOffset, offsetof(RenderFragUniformBlock, integral_texture_query_format));
|
||||
b.addMemberDecoration(render_buf_type, 5, spv::DecorationOffset, offsetof(RenderFragUniformBlock, res_multiplier));
|
||||
|
||||
// TODO: create enum to refer to these fields
|
||||
b.addMemberName(render_buf_type, 0, "back_disabled");
|
||||
@@ -1138,6 +1175,8 @@ static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProg
|
||||
translation_state.render_info_id = b.createVariable(spv::NoPrecision, spv::StorageClassUniform, render_buf_type, "renderFragInfo");
|
||||
|
||||
b.addDecoration(translation_state.render_info_id, spv::DecorationBinding, 3);
|
||||
if (translation_state.is_vulkan)
|
||||
b.addDecoration(translation_state.render_info_id, spv::DecorationDescriptorSet, 0);
|
||||
|
||||
create_fragment_inputs(b, spv_params, utils, features, translation_state, texture_queries, samplers, program);
|
||||
}
|
||||
@@ -1188,7 +1227,7 @@ static spv::Function *make_frag_finalize_function(spv::Builder &b, const SpirvSh
|
||||
if (!is_float_data_type(color_val_operand.type))
|
||||
color = utils::convert_to_float(b, color, color_val_operand.type, true);
|
||||
|
||||
if (program.is_frag_color_used() && features.should_use_shader_interlock() && !translate_state.should_gl_spirv_compatible) {
|
||||
if (program.is_frag_color_used() && features.should_use_shader_interlock() && translate_state.is_target_glsl) {
|
||||
spv::Id signed_i32 = b.makeIntType(32);
|
||||
spv::Id coord_id = b.createLoad(translate_state.frag_coord_id, spv::NoPrecision);
|
||||
spv::Id depth = b.createOp(spv::OpAccessChain, b.makeFloatType(32), { coord_id, b.makeIntConstant(2) });
|
||||
@@ -1221,29 +1260,31 @@ static spv::Function *make_frag_finalize_function(spv::Builder &b, const SpirvSh
|
||||
}
|
||||
}
|
||||
|
||||
// Discard masked fragments
|
||||
spv::Id current_coord = translate_state.frag_coord_id;
|
||||
spv::Id i32 = b.makeIntegerType(32, true);
|
||||
spv::Id v2i32 = b.makeVectorType(i32, 2);
|
||||
current_coord = b.createUnaryOp(spv::OpConvertFToS, b.makeVectorType(i32, 4), b.createLoad(current_coord, spv::NoPrecision));
|
||||
current_coord = b.createOp(spv::OpVectorShuffle, v2i32, { current_coord, current_coord, 0, 1 });
|
||||
if (features.use_mask_bit) {
|
||||
// Discard masked fragments
|
||||
spv::Id current_coord = translate_state.frag_coord_id;
|
||||
spv::Id i32 = b.makeIntegerType(32, true);
|
||||
spv::Id v2i32 = b.makeVectorType(i32, 2);
|
||||
current_coord = b.createUnaryOp(spv::OpConvertFToS, b.makeVectorType(i32, 4), b.createLoad(current_coord, spv::NoPrecision));
|
||||
current_coord = b.createOp(spv::OpVectorShuffle, v2i32, { current_coord, current_coord, 0, 1 });
|
||||
|
||||
// the mask is not upscaled
|
||||
spv::Id res_multiplier = b.createAccessChain(spv::StorageClassUniform, translate_state.render_info_id, { b.makeIntConstant(5) });
|
||||
res_multiplier = b.createLoad(res_multiplier, spv::NoPrecision);
|
||||
res_multiplier = b.createCompositeConstruct(v2i32, { res_multiplier, res_multiplier });
|
||||
current_coord = b.createBinOp(spv::OpSDiv, v2i32, current_coord, res_multiplier);
|
||||
// the mask is not upscaled
|
||||
spv::Id res_multiplier = b.createAccessChain(spv::StorageClassUniform, translate_state.render_info_id, { b.makeIntConstant(5) });
|
||||
res_multiplier = b.createLoad(res_multiplier, spv::NoPrecision);
|
||||
res_multiplier = b.createCompositeConstruct(v2i32, { res_multiplier, res_multiplier });
|
||||
current_coord = b.createBinOp(spv::OpSDiv, v2i32, current_coord, res_multiplier);
|
||||
|
||||
spv::Id sampled_type = b.makeFloatType(32);
|
||||
spv::Id v4 = b.makeVectorType(sampled_type, 4);
|
||||
spv::Id texel = b.createOp(spv::OpImageRead, v4, { b.createLoad(translate_state.mask_id, spv::NoPrecision), current_coord });
|
||||
spv::Id rezero = b.makeFloatConstant(0.5f);
|
||||
spv::Id zero = b.makeCompositeConstant(v4, { rezero, rezero, rezero, rezero });
|
||||
spv::Id pred = b.createOp(spv::OpFOrdLessThan, b.makeVectorType(b.makeBoolType(), 4), { texel, zero });
|
||||
spv::Id pred2 = b.createUnaryOp(spv::OpAll, b.makeBoolType(), pred);
|
||||
spv::Builder::If cond_builder(pred2, spv::SelectionControlMaskNone, b);
|
||||
b.makeDiscard();
|
||||
cond_builder.makeEndIf();
|
||||
spv::Id sampled_type = b.makeFloatType(32);
|
||||
spv::Id v4 = b.makeVectorType(sampled_type, 4);
|
||||
spv::Id texel = b.createOp(spv::OpImageRead, v4, { b.createLoad(translate_state.mask_id, spv::NoPrecision), current_coord });
|
||||
spv::Id rezero = b.makeFloatConstant(0.5f);
|
||||
spv::Id zero = b.makeCompositeConstant(v4, { rezero, rezero, rezero, rezero });
|
||||
spv::Id pred = b.createOp(spv::OpFOrdLessThan, b.makeVectorType(b.makeBoolType(), 4), { texel, zero });
|
||||
spv::Id pred2 = b.createUnaryOp(spv::OpAll, b.makeBoolType(), pred);
|
||||
spv::Builder::If cond_builder(pred2, spv::SelectionControlMaskNone, b);
|
||||
b.makeDiscard();
|
||||
cond_builder.makeEndIf();
|
||||
}
|
||||
|
||||
b.makeReturn(false);
|
||||
b.setBuildPoint(last_build_point);
|
||||
@@ -1264,7 +1305,7 @@ static spv::Function *make_vert_finalize_function(spv::Builder &b, const SpirvSh
|
||||
gxp::GxmVertexOutputTexCoordInfos coord_infos = {};
|
||||
SceGxmVertexProgramOutputs vertex_outputs = gxp::get_vertex_outputs(program, &coord_infos);
|
||||
|
||||
static const auto calculate_copy_comp_count = [](uint8_t info) {
|
||||
static const auto calculate_copy_comp_count = [](uint8_t info) -> uint8_t {
|
||||
// TexCoord info uses preset values described below for determining lengths.
|
||||
uint8_t length = 0;
|
||||
if (info & 0b001u)
|
||||
@@ -1288,6 +1329,7 @@ static spv::Function *make_vert_finalize_function(spv::Builder &b, const SpirvSh
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_COLOR0, "v_Color0", 4, 1);
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_COLOR1, "v_Color1", 4, 2);
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_FOG, "v_Fog", 2, 3);
|
||||
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD0, "v_TexCoord0", calculate_copy_comp_count(coord_infos[0]), 4);
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD1, "v_TexCoord1", calculate_copy_comp_count(coord_infos[1]), 5);
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD2, "v_TexCoord2", calculate_copy_comp_count(coord_infos[2]), 6);
|
||||
@@ -1298,8 +1340,8 @@ static spv::Function *make_vert_finalize_function(spv::Builder &b, const SpirvSh
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD7, "v_TexCoord7", calculate_copy_comp_count(coord_infos[7]), 11);
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD8, "v_TexCoord8", calculate_copy_comp_count(coord_infos[8]), 12);
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD9, "v_TexCoord9", calculate_copy_comp_count(coord_infos[9]), 13);
|
||||
// TODO: this should be translated to gl_PointSize
|
||||
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_PSIZE, "v_Psize", 1);
|
||||
|
||||
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_PSIZE, "v_Psize", 1, 14);
|
||||
// TODO: these should be translated to gl_ClipDistance
|
||||
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP0, "v_Clip0", 1);
|
||||
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP1, "v_Clip1", 1);
|
||||
@@ -1320,53 +1362,68 @@ static spv::Function *make_vert_finalize_function(spv::Builder &b, const SpirvSh
|
||||
const auto vo_typed = static_cast<SceGxmVertexProgramOutputs>(vo);
|
||||
VertexProgramOutputProperties properties = vertex_properties_map.at(vo_typed);
|
||||
|
||||
// TODO: use the actual size of variable
|
||||
const spv::Id out_type = b.makeVectorType(b.makeFloatType(32), 4);
|
||||
// TODO: use real component_count, for now only force PSIZE to have a component count of 1 and other to 4
|
||||
const uint32_t used_component_count = (vo == SCE_GXM_VERTEX_PROGRAM_OUTPUT_PSIZE) ? 1 : 4;
|
||||
const spv::Id out_type = utils::make_vector_or_scalar_type(b, b.makeFloatType(32), used_component_count);
|
||||
const spv::Id out_var = b.createVariable(spv::NoPrecision, spv::StorageClassOutput, out_type, properties.name.c_str());
|
||||
|
||||
if (vo != SCE_GXM_VERTEX_PROGRAM_OUTPUT_POSITION)
|
||||
if (vo != SCE_GXM_VERTEX_PROGRAM_OUTPUT_POSITION && vo != SCE_GXM_VERTEX_PROGRAM_OUTPUT_PSIZE)
|
||||
// A BuiltIn variable cannot have any Location or Component decorations
|
||||
b.addDecoration(out_var, spv::DecorationLocation, properties.location);
|
||||
|
||||
translation_state.interfaces.push_back(out_var);
|
||||
|
||||
// Do store
|
||||
spv::Id o_val = utils::load(b, parameters, utils, features, o_op, 0b1111, 0);
|
||||
const Imm4 load_mask = (vo == SCE_GXM_VERTEX_PROGRAM_OUTPUT_PSIZE) ? 0b1 : 0b1111;
|
||||
spv::Id o_val = utils::load(b, parameters, utils, features, o_op, load_mask, 0);
|
||||
|
||||
if (vo == SCE_GXM_VERTEX_PROGRAM_OUTPUT_POSITION) {
|
||||
b.addDecoration(out_var, spv::DecorationBuiltIn, spv::BuiltInPosition);
|
||||
|
||||
// Transform screen space coordinate to ndc when viewport is disabled.
|
||||
spv::Id f32 = b.makeFloatType(32);
|
||||
spv::Id v4 = b.makeVectorType(b.makeFloatType(32), 4);
|
||||
spv::Id half = b.makeFloatConstant(0.5f);
|
||||
spv::Id one = b.makeFloatConstant(1.0f);
|
||||
spv::Id neg_one = b.makeFloatConstant(-1.0f);
|
||||
spv::Id two = b.makeFloatConstant(2.0f);
|
||||
spv::Id neg_two = b.makeFloatConstant(-2.0f);
|
||||
spv::Id zero = b.makeFloatConstant(0.0f);
|
||||
const spv::Id f32 = b.makeFloatType(32);
|
||||
const spv::Id v4 = b.makeVectorType(f32, 4);
|
||||
const spv::Id half = b.makeFloatConstant(0.5f);
|
||||
const spv::Id one = b.makeFloatConstant(1.0f);
|
||||
const spv::Id neg_one = b.makeFloatConstant(-1.0f);
|
||||
const spv::Id two = b.makeFloatConstant(2.0f);
|
||||
const spv::Id neg_two = b.makeFloatConstant(-2.0f);
|
||||
const spv::Id zero = b.makeFloatConstant(0.0f);
|
||||
|
||||
spv::Id viewport_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(1) });
|
||||
spv::Id screen_width_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(2) });
|
||||
spv::Id screen_height_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(3) });
|
||||
spv::Id screen_scale;
|
||||
spv::Id screen_offset;
|
||||
// the y axis is inverted in opengl compared to vulkan/gxp
|
||||
// do not touch the z and w components
|
||||
if (translation_state.is_vulkan) {
|
||||
screen_scale = b.makeCompositeConstant(v4, { two, two, one, one });
|
||||
screen_offset = b.makeCompositeConstant(v4, { neg_one, neg_one, zero, zero });
|
||||
} else {
|
||||
screen_scale = b.makeCompositeConstant(v4, { two, neg_two, two, two });
|
||||
screen_offset = b.makeCompositeConstant(v4, { neg_one, one, zero, zero });
|
||||
}
|
||||
|
||||
spv::Id pred = b.createOp(spv::OpFOrdLessThan, b.makeBoolType(), { b.createLoad(viewport_id, spv::NoPrecision), half });
|
||||
const spv::Id viewport_flag = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(1) });
|
||||
const spv::Id pred = b.createOp(spv::OpFOrdLessThan, b.makeBoolType(), { b.createLoad(viewport_flag, spv::NoPrecision), half });
|
||||
spv::Builder::If cond_builder(pred, spv::SelectionControlMaskNone, b);
|
||||
|
||||
spv::Id width_recp = b.createBinOp(spv::OpFDiv, f32, two, b.createLoad(screen_width_id, spv::NoPrecision));
|
||||
spv::Id height_recp = b.createBinOp(spv::OpFDiv, f32, neg_two, b.createLoad(screen_height_id, spv::NoPrecision));
|
||||
spv::Id scale = b.createCompositeConstruct(v4, { width_recp, height_recp, one, one });
|
||||
spv::Id constant = b.createCompositeConstruct(v4, { neg_one, one, zero, zero });
|
||||
spv::Id o_val2 = b.createBinOp(spv::OpFMul, v4, o_val, scale);
|
||||
o_val2 = b.createBinOp(spv::OpFAdd, v4, o_val2, constant);
|
||||
spv::Id screen_width = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(2) });
|
||||
screen_width = b.createLoad(screen_width, spv::NoPrecision);
|
||||
spv::Id screen_height = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(3) });
|
||||
screen_height = b.createLoad(screen_height, spv::NoPrecision);
|
||||
|
||||
if (translation_state.render_info_id != spv::NoResult) {
|
||||
// o_val2 = (x,y,z,w) * (2/width, -2/height, 1, 1) + (-1,1,0,0)
|
||||
const spv::Id screen_coords = b.createCompositeConstruct(v4, { screen_width, screen_height, one, one });
|
||||
const spv::Id scale = b.createBinOp(spv::OpFDiv, v4, screen_scale, screen_coords);
|
||||
spv::Id o_val2 = b.createBinOp(spv::OpFMul, v4, o_val, scale);
|
||||
o_val2 = b.createBinOp(spv::OpFAdd, v4, o_val2, screen_offset);
|
||||
|
||||
// on vulkan this is done using the viewport directly
|
||||
if (!translation_state.is_vulkan && translation_state.render_info_id != spv::NoResult) {
|
||||
spv::Id flip_vec_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(0) });
|
||||
flip_vec_id = b.createLoad(flip_vec_id, spv::NoPrecision);
|
||||
o_val2 = b.createBinOp(spv::OpFMul, v4, o_val2, flip_vec_id);
|
||||
}
|
||||
|
||||
// o_val2 = (x,y) * (2/width, -2/height) + (-1,1)
|
||||
b.createStore(o_val2, out_var);
|
||||
|
||||
// Note: Depth range and user clip planes are ineffective in this mode
|
||||
@@ -1378,14 +1435,47 @@ static spv::Function *make_vert_finalize_function(spv::Builder &b, const SpirvSh
|
||||
|
||||
cond_builder.makeBeginElse();
|
||||
|
||||
if (translation_state.render_info_id != spv::NoResult) {
|
||||
// Apply the viewport flip if opengl
|
||||
if (!translation_state.is_vulkan && translation_state.render_info_id != spv::NoResult) {
|
||||
spv::Id flip_vec_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(0) });
|
||||
flip_vec_id = b.createLoad(flip_vec_id, spv::NoPrecision);
|
||||
o_val = b.createBinOp(spv::OpFMul, out_type, o_val, flip_vec_id);
|
||||
}
|
||||
|
||||
b.createStore(o_val, out_var);
|
||||
|
||||
// scale the depth and w coordinate
|
||||
if (translation_state.render_info_id != spv::NoResult) {
|
||||
spv::Id z_ref = b.createAccessChain(spv::StorageClassOutput, out_var, { b.makeIntConstant(2) });
|
||||
spv::Id w_ref = b.createAccessChain(spv::StorageClassOutput, out_var, { b.makeIntConstant(3) });
|
||||
spv::Id z = b.createLoad(z_ref, spv::NoPrecision);
|
||||
const spv::Id w = b.createLoad(w_ref, spv::NoPrecision);
|
||||
|
||||
spv::Id z_offset = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(5) });
|
||||
spv::Id z_scale = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(6) });
|
||||
z_offset = b.createLoad(z_offset, spv::NoPrecision);
|
||||
z_scale = b.createLoad(z_scale, spv::NoPrecision);
|
||||
|
||||
// screen_z = z_offset + z_scale * (z / w)
|
||||
z = b.createBinOp(spv::OpFDiv, f32, z, w);
|
||||
z = b.createBinOp(spv::OpFMul, f32, z, z_scale);
|
||||
z = b.createBinOp(spv::OpFAdd, f32, z, z_offset);
|
||||
|
||||
if (!translation_state.is_vulkan) {
|
||||
// convert [0,1] depth range (gxp, vulkan) to [-1,1] depth range (opengl)
|
||||
z = b.createBinOp(spv::OpFMul, f32, z, b.makeFloatConstant(2.0f));
|
||||
z = b.createBinOp(spv::OpFAdd, f32, z, b.makeFloatConstant(-1.0f));
|
||||
}
|
||||
|
||||
// multiply by w because it will be re-divided by w during screen normalization
|
||||
z = b.createBinOp(spv::OpFMul, f32, z, w);
|
||||
|
||||
b.createStore(z, z_ref);
|
||||
}
|
||||
|
||||
cond_builder.makeEndIf();
|
||||
} else if (vo == SCE_GXM_VERTEX_PROGRAM_OUTPUT_PSIZE) {
|
||||
b.addDecoration(out_var, spv::DecorationBuiltIn, spv::BuiltInPointSize);
|
||||
b.createStore(o_val, out_var);
|
||||
} else {
|
||||
b.createStore(o_val, out_var);
|
||||
}
|
||||
@@ -1458,8 +1548,11 @@ static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const s
|
||||
|
||||
SceGxmProgramType program_type = program.get_type();
|
||||
|
||||
// SPV 1.3 is only supported by Vulkan 1.1
|
||||
const unsigned int spv_version = translation_state.is_vulkan ? 0x10000 : 0x10300;
|
||||
|
||||
spv::SpvBuildLogger spv_logger;
|
||||
spv::Builder b(/* SPV_VERSION*/ 0x10300, 0x1337 << 12, &spv_logger);
|
||||
spv::Builder b(spv_version, 0x1337 << 12, &spv_logger);
|
||||
b.setSourceFile(shader_hash);
|
||||
b.setEmitOpLines();
|
||||
b.addSourceExtension("gxp");
|
||||
@@ -1469,7 +1562,6 @@ static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const s
|
||||
b.addCapability(spv::Capability::CapabilityShader);
|
||||
if (features.support_unknown_format)
|
||||
b.addCapability(spv::Capability::CapabilityStorageImageReadWithoutFormat);
|
||||
b.addCapability(spv::Capability::CapabilityFloat16);
|
||||
|
||||
NonDependentTextureQueryCallInfos texture_queries;
|
||||
utils::SpirvUtilFunctions utils;
|
||||
@@ -1506,10 +1598,18 @@ static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const s
|
||||
|
||||
std::vector<spv::Id> empty_args;
|
||||
|
||||
if (translation_state.is_vulkan)
|
||||
// core in spv 1.3
|
||||
b.addExtension("SPV_KHR_storage_buffer_storage_class");
|
||||
|
||||
// Lock/unlock and read texel for shader interlock. Texture barrier will have glTextureBarrier() called so we don't
|
||||
// have to worry too much. Texture barrier will not be accurate and may be broken though.
|
||||
if (program_type == SceGxmProgramType::Fragment) {
|
||||
b.addExecutionMode(spv_func_main, spv::ExecutionModeOriginLowerLeft);
|
||||
// in Vulkan, the coordinate (-1,-1) is in the top-left quadrant whereas it is in the bottom left quadrant in opengl
|
||||
if (translation_state.is_vulkan)
|
||||
b.addExecutionMode(spv_func_main, spv::ExecutionModeOriginUpperLeft);
|
||||
else
|
||||
b.addExecutionMode(spv_func_main, spv::ExecutionModeOriginLowerLeft);
|
||||
|
||||
if (program.is_frag_color_used() && features.should_use_shader_interlock()) {
|
||||
b.addExecutionMode(spv_func_main, spv::ExecutionModePixelInterlockOrderedEXT);
|
||||
@@ -1525,7 +1625,9 @@ static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const s
|
||||
|
||||
if (!translation_state.is_maskupdate) {
|
||||
if (program.is_fragment()) {
|
||||
begin_hook_func = make_frag_initialize_function(b, translation_state);
|
||||
if (!translation_state.is_vulkan)
|
||||
begin_hook_func = make_frag_initialize_function(b, translation_state);
|
||||
|
||||
end_hook_func = make_frag_finalize_function(b, parameters, program, utils, features, translation_state);
|
||||
} else {
|
||||
end_hook_func = make_vert_finalize_function(b, parameters, program, utils, features, translation_state);
|
||||
@@ -1590,7 +1692,7 @@ static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const s
|
||||
return spirv;
|
||||
}
|
||||
|
||||
static std::string convert_spirv_to_glsl(const std::string &shader_name, SpirvCode spirv_binary, const FeatureState &features, TranslationState &translation_state, bool is_frag_color_used) {
|
||||
static std::string convert_spirv_to_glsl(const std::string &shader_name, SpirvCode &spirv_binary, const FeatureState &features, TranslationState &translation_state, bool is_frag_color_used) {
|
||||
spirv_cross::CompilerGLSL glsl(std::move(spirv_binary));
|
||||
|
||||
spirv_cross::CompilerGLSL::Options options;
|
||||
@@ -1668,48 +1770,42 @@ static spv::ImageFormat translate_color_format(const SceGxmColorBaseFormat forma
|
||||
// ***************************
|
||||
// * Functions (exposed API) *
|
||||
// ***************************
|
||||
usse::SpirvCode convert_gxp_to_spirv(const SceGxmProgram &program, const std::string &shader_name, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, bool force_shader_debug, std::function<bool(const std::string &ext, const std::string &dump)> dumper) {
|
||||
|
||||
GeneratedShader convert_gxp(const SceGxmProgram &program, const std::string &shader_hash, const FeatureState &features, const Target target, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate,
|
||||
bool force_shader_debug, std::function<bool(const std::string &ext, const std::string &dump)> dumper) {
|
||||
TranslationState translation_state;
|
||||
translation_state.is_fragment = program.is_fragment();
|
||||
translation_state.is_maskupdate = maskupdate;
|
||||
translation_state.should_gl_spirv_compatible = true;
|
||||
translation_state.is_target_glsl = (target == Target::GLSLOpenGL);
|
||||
translation_state.is_vulkan = (target == Target::SpirVVulkan);
|
||||
|
||||
if (!features.support_unknown_format) {
|
||||
// take the color format of the current surface, hoping the shader is not used on two surfaces with different formats (this should be the case)
|
||||
translation_state.image_storage_format = translate_color_format(last_color_format);
|
||||
}
|
||||
|
||||
return convert_gxp_to_spirv_impl(program, shader_name, features, translation_state, hint_attributes, force_shader_debug, dumper);
|
||||
}
|
||||
GeneratedShader shader{};
|
||||
shader.spirv = convert_gxp_to_spirv_impl(program, shader_hash, features, translation_state, hint_attributes, force_shader_debug, dumper);
|
||||
|
||||
std::string convert_gxp_to_glsl(const SceGxmProgram &program, const std::string &shader_name, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, bool force_shader_debug, std::function<bool(const std::string &ext, const std::string &dump)> dumper) {
|
||||
TranslationState translation_state;
|
||||
translation_state.is_fragment = program.is_fragment();
|
||||
translation_state.is_maskupdate = maskupdate;
|
||||
translation_state.should_gl_spirv_compatible = false;
|
||||
if (translation_state.is_target_glsl) {
|
||||
// also generate the glsl file
|
||||
// this destroys shader.spirv
|
||||
shader.glsl = convert_spirv_to_glsl(shader_hash, shader.spirv, features, translation_state, program.is_frag_color_used());
|
||||
|
||||
if (!features.support_unknown_format) {
|
||||
// take the color format of the current surface, hoping the shader is not used on two surfaces with different formats (this should be the case)
|
||||
translation_state.image_storage_format = translate_color_format(last_color_format);
|
||||
}
|
||||
if (LOG_SHADER_CODE || force_shader_debug) {
|
||||
LOG_INFO("Generated GLSL:\n{}", shader.glsl);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> spirv_binary = convert_gxp_to_spirv_impl(program, shader_name, features, translation_state, hint_attributes, force_shader_debug, dumper);
|
||||
|
||||
const auto source = convert_spirv_to_glsl(shader_name, spirv_binary, features, translation_state, program.is_frag_color_used());
|
||||
|
||||
if (LOG_SHADER_CODE || force_shader_debug) {
|
||||
LOG_INFO("Generated GLSL:\n{}", source);
|
||||
}
|
||||
|
||||
if (dumper) {
|
||||
if (program.is_fragment()) {
|
||||
dumper("frag", source);
|
||||
} else {
|
||||
dumper("vert", source);
|
||||
if (dumper) {
|
||||
if (program.is_fragment()) {
|
||||
dumper("frag", shader.glsl);
|
||||
} else {
|
||||
dumper("vert", shader.glsl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
return shader;
|
||||
}
|
||||
|
||||
void convert_gxp_to_glsl_from_filepath(const std::string &shader_filepath) {
|
||||
@@ -1728,7 +1824,7 @@ void convert_gxp_to_glsl_from_filepath(const std::string &shader_filepath) {
|
||||
features.direct_fragcolor = false;
|
||||
features.support_shader_interlock = true;
|
||||
|
||||
convert_gxp_to_glsl(*gxp_program, shader_filepath_str.filename().string(), features, nullptr, false, true);
|
||||
convert_gxp(*gxp_program, shader_filepath_str.filename().string(), features, shader::Target::GLSLOpenGL, nullptr, false, true);
|
||||
|
||||
free(gxp_program);
|
||||
}
|
||||
|
||||
@@ -490,7 +490,8 @@ spv::Id USSETranslatorVisitor::do_alu_op(Instruction &inst, const Imm4 source_ma
|
||||
|
||||
case Opcode::VDP:
|
||||
case Opcode::VF16DP: {
|
||||
result = m_b.createBinOp(spv::OpDot, m_b.makeFloatType(32), vsrc1, vsrc2);
|
||||
const spv::Op op = (m_b.getNumComponents(vsrc1) > 1) ? spv::OpDot : spv::OpFMul;
|
||||
result = m_b.createBinOp(op, m_b.makeFloatType(32), vsrc1, vsrc2);
|
||||
result = postprocess_dot_result_for_store(m_b, result, possible_dest_mask);
|
||||
break;
|
||||
}
|
||||
@@ -1031,8 +1032,8 @@ bool USSETranslatorVisitor::sop2(
|
||||
factored_rgb_lhs = m_b.createBinOp(spv::OpFMul, src_color_type, factored_rgb_lhs, src1_color);
|
||||
factored_rgb_rhs = m_b.createBinOp(spv::OpFMul, src_color_type, factored_rgb_rhs, src2_color);
|
||||
|
||||
factored_a_lhs = m_b.createBinOp(spv::OpFMul, src_color_type, factored_a_lhs, src1_alpha);
|
||||
factored_a_rhs = m_b.createBinOp(spv::OpFMul, src_color_type, factored_a_rhs, src2_alpha);
|
||||
factored_a_lhs = m_b.createBinOp(spv::OpFMul, src_alpha_type, factored_a_lhs, src1_alpha);
|
||||
factored_a_rhs = m_b.createBinOp(spv::OpFMul, src_alpha_type, factored_a_rhs, src2_alpha);
|
||||
|
||||
auto color_res = apply_opcode(color_op, src_color_type, factored_rgb_lhs, factored_rgb_rhs);
|
||||
auto alpha_res = apply_opcode(alpha_op, src_alpha_type, factored_a_lhs, factored_a_rhs);
|
||||
@@ -1630,7 +1631,8 @@ bool USSETranslatorVisitor::vdual(
|
||||
case Opcode::VDP: {
|
||||
const spv::Id first = load(ops[0], write_mask_source);
|
||||
const spv::Id second = load(ops[1], write_mask_source);
|
||||
result = m_b.createBinOp(spv::OpDot, type_f32, first, second);
|
||||
const spv::Op op = (m_b.getNumComponents(first) > 1) ? spv::OpDot : spv::OpFMul;
|
||||
result = m_b.createBinOp(op, type_f32, first, second);
|
||||
break;
|
||||
}
|
||||
case Opcode::FEXP: {
|
||||
@@ -1645,7 +1647,8 @@ bool USSETranslatorVisitor::vdual(
|
||||
}
|
||||
case Opcode::VSSQ: {
|
||||
const spv::Id source = load(ops[0], write_mask_source);
|
||||
result = m_b.createBinOp(spv::OpDot, type_f32, source, source);
|
||||
const spv::Op op = (m_b.getNumComponents(source) > 1) ? spv::OpDot : spv::OpFMul;
|
||||
result = m_b.createBinOp(op, type_f32, source, source);
|
||||
break;
|
||||
}
|
||||
case Opcode::FMAD:
|
||||
|
||||
@@ -242,7 +242,6 @@ spv::Id USSETranslatorVisitor::vtst_impl(Instruction inst, ExtPredicate pred, in
|
||||
spv::Id rhs = spv::NoResult;
|
||||
|
||||
const spv::Id pred_type = utils::make_vector_or_scalar_type(m_b, m_b.makeBoolType(), mask ? 4 : 1);
|
||||
spv::Id pred_result = utils::make_uniform_vector_from_type(m_b, m_b.makeBoolType(), true);
|
||||
|
||||
// Zero test number:
|
||||
// 0 - alway pass
|
||||
@@ -298,7 +297,7 @@ spv::Id USSETranslatorVisitor::vtst_impl(Instruction inst, ExtPredicate pred, in
|
||||
inst.opr.dest.num, disasm::operand_to_str(inst.opr.src1, load_mask), disasm::operand_to_str(inst.opr.src2, load_mask));
|
||||
}
|
||||
|
||||
lhs = do_alu_op(inst, load_mask, mask ? mask : 0b1);
|
||||
lhs = do_alu_op(inst, load_mask, mask ? 0b1111 : 0b1);
|
||||
|
||||
const spv::Id c0_type = utils::make_vector_or_scalar_type(m_b, m_b.makeFloatType(32), mask ? 4 : 1);
|
||||
spv::Id c0 = utils::make_uniform_vector_from_type(m_b, c0_type, 0.0f);
|
||||
@@ -485,23 +484,25 @@ bool USSETranslatorVisitor::vtstmsk(
|
||||
|
||||
spv::Id pred_result = vtst_impl(inst, pred, zero_test, sign_test, 0b1111, true);
|
||||
|
||||
spv::Id float_v4 = utils::make_vector_or_scalar_type(m_b, m_b.makeFloatType(32), 4);
|
||||
spv::Id uint_v4 = utils::make_vector_or_scalar_type(m_b, m_b.makeUintType(32), 4);
|
||||
spv::Id scaler;
|
||||
spv::Id output_type;
|
||||
spv::Id zeros;
|
||||
spv::Id ones;
|
||||
switch (store_data_type) {
|
||||
case DataType::UINT8:
|
||||
// OpSelect doesn't work UConvert doesn't work in glsl transpiler
|
||||
pred_result = m_b.createUnaryOp(spv::OpFConvert, float_v4, pred_result);
|
||||
pred_result = m_b.createUnaryOp(spv::OpConvertFToU, uint_v4, pred_result);
|
||||
scaler = m_b.makeIntConstant(0xFF);
|
||||
pred_result = m_b.createBinOp(spv::OpIMul, uint_v4, pred_result, scaler);
|
||||
output_type = m_b.makeVectorType(m_b.makeUintType(32), 4);
|
||||
zeros = utils::make_uniform_vector_from_type(m_b, output_type, 0);
|
||||
ones = utils::make_uniform_vector_from_type(m_b, output_type, 0xFF);
|
||||
break;
|
||||
case DataType::F16:
|
||||
case DataType::F32:
|
||||
pred_result = m_b.createUnaryOp(spv::OpFConvert, float_v4, pred_result);
|
||||
output_type = m_b.makeVectorType(m_b.makeFloatType(32), 4);
|
||||
zeros = utils::make_uniform_vector_from_type(m_b, output_type, 0.f);
|
||||
ones = utils::make_uniform_vector_from_type(m_b, output_type, 1.f);
|
||||
break;
|
||||
}
|
||||
|
||||
pred_result = m_b.createOp(spv::OpSelect, output_type, { pred_result, zeros, ones });
|
||||
|
||||
store(inst.opr.dest, pred_result);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -198,7 +198,7 @@ bool USSETranslatorVisitor::vmov(
|
||||
if (compare_op != spv::OpAny) {
|
||||
// Merely do what the instruction does
|
||||
// First compare source0 with vector 0
|
||||
spv::Id cond_result = m_b.createOp(compare_op, m_b.makeVectorType(m_b.makeBoolType(), m_b.getNumComponents(source_to_compare_with_0)),
|
||||
spv::Id cond_result = m_b.createOp(compare_op, utils::make_vector_or_scalar_type(m_b, m_b.makeBoolType(), m_b.getNumComponents(source_to_compare_with_0)),
|
||||
{ source_to_compare_with_0, v0 });
|
||||
|
||||
// For each component, if the compare result is true, move the equivalent component from source1 to dest,
|
||||
|
||||
@@ -105,14 +105,16 @@ void shader::usse::USSETranslatorVisitor::do_texture_queries(const NonDependentT
|
||||
}
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
LOG_ERROR("Unknown data type for texture query {}", texture_query.store_type);
|
||||
dest_mask = 0b1111;
|
||||
}
|
||||
|
||||
bool proj = (texture_query.prod_pos >= 0);
|
||||
shader::usse::Coord coord_inst = texture_query.coord;
|
||||
|
||||
if (texture_query.prod_pos >= 0) {
|
||||
coord_inst.first = m_b.createOp(spv::OpVectorShuffle, type_f32_v[3], { texture_query.coord.first, texture_query.coord.first, 0, 1, static_cast<spv::Id>(texture_query.prod_pos) });
|
||||
spv::Id texture_coord = m_b.createLoad(texture_query.coord.first, spv::NoPrecision);
|
||||
coord_inst.first = m_b.createOp(spv::OpVectorShuffle, type_f32_v[3], { texture_coord, texture_coord, 0, 1, static_cast<spv::Id>(texture_query.prod_pos) });
|
||||
proj = true;
|
||||
}
|
||||
|
||||
@@ -121,7 +123,8 @@ void shader::usse::USSETranslatorVisitor::do_texture_queries(const NonDependentT
|
||||
|
||||
if (static_cast<DataType>(texture_query.store_type) == DataType::UNK) {
|
||||
// Manual check
|
||||
spv::Id sampler_integral_query_format = m_b.createAccessChain(spv::StorageClassPrivate, translation_state_id, { m_b.makeIntConstant(4), m_b.makeIntConstant(texture_query.sampler_index / 4), m_b.makeIntConstant(texture_query.sampler_index % 4) });
|
||||
spv::Id sampler_integral_query_format = m_b.createAccessChain(spv::StorageClassUniform, translation_state_id, { m_b.makeIntConstant(4), m_b.makeIntConstant(texture_query.sampler_index / 4), m_b.makeIntConstant(texture_query.sampler_index % 4) });
|
||||
sampler_integral_query_format = m_b.createLoad(sampler_integral_query_format, spv::NoPrecision);
|
||||
spv::Id bool_type = m_b.makeBoolType();
|
||||
|
||||
spv::Builder::If if_builder(m_b.createBinOp(spv::OpFOrdGreaterThanEqual, bool_type, sampler_integral_query_format, m_b.makeFloatConstant(INTEGRAL_TEX_QUERY_TYPE_8BIT_SIGNED)), spv::SelectionControlMaskNone, m_b);
|
||||
@@ -133,7 +136,7 @@ void shader::usse::USSETranslatorVisitor::do_texture_queries(const NonDependentT
|
||||
store(store_op, packed8, dest_mask);
|
||||
if_builder.makeBeginElse();
|
||||
|
||||
spv::Builder::If if_builder_2(m_b.createBinOp(spv::OpFOrdGreaterThanEqual, bool_type, sampler_integral_query_format, m_b.makeIntConstant(INTEGRAL_TEX_QUERY_TYPE_8BIT_UNSIGNED)), spv::SelectionControlMaskNone, m_b);
|
||||
spv::Builder::If if_builder_2(m_b.createBinOp(spv::OpFOrdGreaterThanEqual, bool_type, sampler_integral_query_format, m_b.makeFloatConstant(INTEGRAL_TEX_QUERY_TYPE_8BIT_UNSIGNED)), spv::SelectionControlMaskNone, m_b);
|
||||
|
||||
packed8 = utils::convert_to_int(m_b, fetch_result, DataType::UINT8, true);
|
||||
packed8 = utils::pack_one(m_b, m_util_funcs, m_features, packed8, DataType::UINT8);
|
||||
@@ -142,7 +145,7 @@ void shader::usse::USSETranslatorVisitor::do_texture_queries(const NonDependentT
|
||||
store(store_op, packed8, dest_mask);
|
||||
|
||||
if_builder_2.makeBeginElse();
|
||||
spv::Builder::If if_builder_3(m_b.createBinOp(spv::OpFOrdGreaterThanEqual, bool_type, sampler_integral_query_format, m_b.makeIntConstant(INTEGRAL_TEX_QUERY_TYPE_16BIT)), spv::SelectionControlMaskNone, m_b);
|
||||
spv::Builder::If if_builder_3(m_b.createBinOp(spv::OpFOrdGreaterThanEqual, bool_type, sampler_integral_query_format, m_b.makeFloatConstant(INTEGRAL_TEX_QUERY_TYPE_16BIT)), spv::SelectionControlMaskNone, m_b);
|
||||
|
||||
spv::Id pack1 = m_b.createOp(spv::OpVectorShuffle, type_f32_v[2], { fetch_result, fetch_result, 0, 1 });
|
||||
pack1 = utils::pack_one(m_b, m_util_funcs, m_features, pack1, DataType::F16);
|
||||
@@ -246,7 +249,7 @@ bool USSETranslatorVisitor::smp(
|
||||
|
||||
if (dim == 1) {
|
||||
// It should be a line, so Y should be zero. There are only two dimensions texture, so this is a guess (seems concise)
|
||||
coord = m_b.createCompositeConstruct(m_b.makeVectorType(m_b.makeFloatType(32), 2), { coord, m_b.makeIntConstant(0) });
|
||||
coord = m_b.createCompositeConstruct(m_b.makeVectorType(m_b.makeFloatType(32), 2), { coord, m_b.makeFloatConstant(0.0f) });
|
||||
dim = 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,8 +179,32 @@ void get_attribute_informations(const SceGxmProgram &program, AttributeInformati
|
||||
for (size_t i = 0; i < program.parameter_count; ++i) {
|
||||
const SceGxmProgramParameter ¶meter = gxp_parameters[i];
|
||||
if (parameter.category == SCE_GXM_PARAMETER_CATEGORY_ATTRIBUTE) {
|
||||
const SceGxmParameterType parameter_type = gxp::parameter_type(parameter);
|
||||
bool is_integer;
|
||||
switch (parameter_type) {
|
||||
case SCE_GXM_PARAMETER_TYPE_C10:
|
||||
case SCE_GXM_PARAMETER_TYPE_F16:
|
||||
case SCE_GXM_PARAMETER_TYPE_F32:
|
||||
is_integer = false;
|
||||
break;
|
||||
default:
|
||||
is_integer = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool is_signed;
|
||||
switch (parameter_type) {
|
||||
case SCE_GXM_PARAMETER_TYPE_S8:
|
||||
case SCE_GXM_PARAMETER_TYPE_S16:
|
||||
case SCE_GXM_PARAMETER_TYPE_S32:
|
||||
is_signed = true;
|
||||
default:
|
||||
is_signed = false;
|
||||
}
|
||||
|
||||
bool regformat = (vertex_varyings_ptr->untyped_pa_regs & ((uint64_t)1 << parameter.resource_index)) != 0;
|
||||
locmap.emplace(parameter.resource_index, AttributeInformation(fcount_allocated / 4, parameter.type, regformat));
|
||||
|
||||
locmap.emplace(parameter.resource_index, AttributeInformation(fcount_allocated / 4, parameter.type, is_integer, is_signed, regformat));
|
||||
fcount_allocated += ((parameter.array_size * parameter.component_count + 3) / 4) * 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,15 +331,17 @@ static spv::Function *make_pack_func(spv::Builder &b, const FeatureState &featur
|
||||
spv::Id extracted = pack_func->getParamId(0);
|
||||
const int comp_bits = 32 / comp_count;
|
||||
|
||||
const spv::Id comp_type = b.getContainedTypeId(input_type);
|
||||
|
||||
auto output = b.makeUintConstant(0);
|
||||
for (int i = 0; i < comp_count; ++i) {
|
||||
auto comp = b.createBinOp(spv::OpVectorExtractDynamic, type_ui32, extracted, b.makeIntConstant(i));
|
||||
output = b.createOp(spv::OpBitFieldInsert, type_ui32, { output, comp, b.makeIntConstant(comp_bits * i), b.makeIntConstant(comp_bits - (is_signed ? 1 : 0)) });
|
||||
auto comp = b.createBinOp(spv::OpVectorExtractDynamic, comp_type, extracted, b.makeIntConstant(i));
|
||||
|
||||
if (is_signed) {
|
||||
auto sign_bit = b.createBinOp(spv::OpShiftRightLogical, type_ui32, comp, b.makeIntConstant(31));
|
||||
output = b.createOp(spv::OpBitFieldInsert, type_ui32, { output, sign_bit, b.makeIntConstant(comp_bits * i + comp_bits - 1), b.makeIntConstant(1) });
|
||||
comp = b.createUnaryOp(spv::OpBitcast, type_ui32, comp);
|
||||
}
|
||||
|
||||
output = b.createOp(spv::OpBitFieldInsert, type_ui32, { output, comp, b.makeIntConstant(comp_bits * i), b.makeIntConstant(comp_bits) });
|
||||
}
|
||||
|
||||
output = b.createUnaryOp(spv::OpBitcast, type_f32, output);
|
||||
@@ -998,9 +1000,9 @@ spv::Id shader::usse::utils::load(spv::Builder &b, const SpirvShaderParameters &
|
||||
// Second pass: Do unpack
|
||||
first_pass = unpack(b, utils, features, first_pass, op.type, op.swizzle, dest_mask, 0);
|
||||
} else if (op.type == DataType::INT32) {
|
||||
first_pass = b.createUnaryOp(spv::OpBitcast, b.makeVectorType(b.makeIntType(32), static_cast<int>(dest_comp_count)), first_pass);
|
||||
first_pass = b.createUnaryOp(spv::OpBitcast, make_vector_or_scalar_type(b, b.makeIntType(32), static_cast<int>(dest_comp_count)), first_pass);
|
||||
} else if (op.type == DataType::UINT32) {
|
||||
first_pass = b.createUnaryOp(spv::OpBitcast, b.makeVectorType(b.makeUintType(32), static_cast<int>(dest_comp_count)), first_pass);
|
||||
first_pass = b.createUnaryOp(spv::OpBitcast, make_vector_or_scalar_type(b, b.makeUintType(32), static_cast<int>(dest_comp_count)), first_pass);
|
||||
}
|
||||
|
||||
if (first_pass == spv::NoResult) {
|
||||
@@ -1169,7 +1171,7 @@ void shader::usse::utils::store(spv::Builder &b, const SpirvShaderParameters &pa
|
||||
// Replace it
|
||||
const int actual_offset_start_to_store = insert_offset + (i + nearest_swizz_on) / num_comp_in_float;
|
||||
elem = b.createOp(spv::OpAccessChain, comp_type, { bank_base, b.makeIntConstant(actual_offset_start_to_store >> 2) });
|
||||
elem = b.createOp(spv::OpVectorExtractDynamic, vec_comp_type, { b.createLoad(elem, spv::NoPrecision), b.makeIntConstant(actual_offset_start_to_store % 4) });
|
||||
elem = b.createOp(spv::OpVectorExtractDynamic, b.makeFloatType(32), { b.createLoad(elem, spv::NoPrecision), b.makeIntConstant(actual_offset_start_to_store % 4) });
|
||||
|
||||
// Extract to f16
|
||||
elem = unpack_one(b, utils, features, elem, dest.type);
|
||||
@@ -1339,10 +1341,11 @@ spv::Id shader::usse::utils::convert_to_float(spv::Builder &b, spv::Id opr, Data
|
||||
const auto constant_range = get_int_normalize_range_constants(type);
|
||||
const auto normalizer = b.makeFloatConstant(constant_range.second);
|
||||
const auto normalizer_vec = create_constant_vector_or_scalar(b, normalizer, comp_count);
|
||||
const auto zero_vec = create_constant_vector_or_scalar(b, b.makeFloatConstant(0.0f), comp_count);
|
||||
const auto b_vec_type = b.makeVectorType(b.makeBoolType(), comp_count);
|
||||
|
||||
if (is_sint) {
|
||||
const auto zero_vec = create_constant_vector_or_scalar(b, b.makeFloatConstant(0.0f), comp_count);
|
||||
const auto b_vec_type = make_vector_or_scalar_type(b, b.makeBoolType(), comp_count);
|
||||
|
||||
const auto normalizer_neg = b.makeFloatConstant(constant_range.first);
|
||||
const auto normalize_vec_neg = create_constant_vector_or_scalar(b, normalizer_neg, comp_count);
|
||||
|
||||
@@ -1372,13 +1375,14 @@ spv::Id shader::usse::utils::convert_to_int(spv::Builder &b, spv::Id opr, DataTy
|
||||
const auto normalizer_vec = create_constant_vector_or_scalar(b, normalizer, comp_count);
|
||||
const auto range_begin_vec = create_constant_vector_or_scalar(b, b.makeFloatConstant(is_uint ? 0.f : -1.f), comp_count);
|
||||
const auto range_end_vec = create_constant_vector_or_scalar(b, b.makeFloatConstant(1.f), comp_count);
|
||||
const auto zero_vec = create_constant_vector_or_scalar(b, b.makeFloatConstant(0.f), comp_count);
|
||||
const auto b_vec_type = b.makeVectorType(b.makeBoolType(), comp_count);
|
||||
|
||||
opr = b.createBuiltinCall(opr_type, b.import("GLSL.std.450"), GLSLstd450FClamp, { opr, range_begin_vec, range_end_vec });
|
||||
if (is_uint) {
|
||||
opr = b.createBinOp(spv::OpFMul, opr_type, opr, normalizer_vec);
|
||||
} else {
|
||||
const auto zero_vec = create_constant_vector_or_scalar(b, b.makeFloatConstant(0.f), comp_count);
|
||||
const auto b_vec_type = make_vector_or_scalar_type(b, b.makeBoolType(), comp_count);
|
||||
|
||||
const auto normalizer_neg = b.makeFloatConstant(constant_range.first);
|
||||
const auto normalize_vec_neg = create_constant_vector_or_scalar(b, normalizer_neg, comp_count);
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2022 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 uv_frag;
|
||||
layout(binding = 0) uniform sampler2D fb;
|
||||
|
||||
layout(location = 0) out vec3 color_frag;
|
||||
|
||||
void main() {
|
||||
color_frag = texture(fb, uv_frag).rgb;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 position_vertex;
|
||||
layout(location = 1) in vec2 uv_vertex;
|
||||
|
||||
layout(location = 0) out vec2 uv_frag;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(position_vertex, 1.0);
|
||||
uv_frag = uv_vertex;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
// Vita3K emulator project
|
||||
// Code adapted from http://horde3d.org/wiki/index.php?title=Shading_Technique_-_FXAA
|
||||
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 uv_frag;
|
||||
layout(binding = 0) uniform sampler2D fb;
|
||||
|
||||
layout(location = 0) out vec3 color_frag;
|
||||
|
||||
layout ( push_constant ) uniform constants {
|
||||
vec2 inv_frame_size;
|
||||
} pc;
|
||||
|
||||
void main( void ) {
|
||||
//gl_FragColor.xyz = texture2D(buf0,texCoords).xyz;
|
||||
//return;
|
||||
|
||||
float FXAA_SPAN_MAX = 8.0;
|
||||
float FXAA_REDUCE_MUL = 1.0/8.0;
|
||||
float FXAA_REDUCE_MIN = 1.0/128.0;
|
||||
|
||||
vec3 rgbNW=texture(fb,uv_frag+(vec2(-1.0,-1.0)*pc.inv_frame_size)).xyz;
|
||||
vec3 rgbNE=texture(fb,uv_frag+(vec2(1.0,-1.0)*pc.inv_frame_size)).xyz;
|
||||
vec3 rgbSW=texture(fb,uv_frag+(vec2(-1.0,1.0)*pc.inv_frame_size)).xyz;
|
||||
vec3 rgbSE=texture(fb,uv_frag+(vec2(1.0,1.0)*pc.inv_frame_size)).xyz;
|
||||
vec3 rgbM=texture(fb,uv_frag).xyz;
|
||||
|
||||
vec3 luma=vec3(0.299, 0.587, 0.114);
|
||||
float lumaNW = dot(rgbNW, luma);
|
||||
float lumaNE = dot(rgbNE, luma);
|
||||
float lumaSW = dot(rgbSW, luma);
|
||||
float lumaSE = dot(rgbSE, luma);
|
||||
float lumaM = dot(rgbM, luma);
|
||||
|
||||
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
|
||||
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
|
||||
|
||||
vec2 dir;
|
||||
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
|
||||
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
|
||||
|
||||
float dirReduce = max(
|
||||
(lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL),
|
||||
FXAA_REDUCE_MIN);
|
||||
|
||||
float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);
|
||||
|
||||
dir = min(vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),
|
||||
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
|
||||
dir * rcpDirMin)) * pc.inv_frame_size;
|
||||
|
||||
vec3 rgbA = (1.0/2.0) * (
|
||||
texture(fb, uv_frag.xy + dir * (1.0/3.0 - 0.5)).xyz +
|
||||
texture(fb, uv_frag.xy + dir * (2.0/3.0 - 0.5)).xyz);
|
||||
vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * (
|
||||
texture(fb, uv_frag.xy + dir * (0.0/3.0 - 0.5)).xyz +
|
||||
texture(fb, uv_frag.xy + dir * (3.0/3.0 - 0.5)).xyz);
|
||||
float lumaB = dot(rgbB, luma);
|
||||
|
||||
if((lumaB < lumaMin) || (lumaB > lumaMax)){
|
||||
color_frag.xyz=rgbA;
|
||||
}else{
|
||||
color_frag.xyz=rgbB;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
add_library(
|
||||
vkutil
|
||||
STATIC
|
||||
include/vkutil/objects.h
|
||||
include/vkutil/vkutil.h
|
||||
|
||||
src/objects.cpp
|
||||
src/vkutil.cpp
|
||||
)
|
||||
|
||||
target_include_directories(vkutil PUBLIC include)
|
||||
target_link_libraries(vkutil PUBLIC vulkan vma)
|
||||
target_link_libraries(vkutil PRIVATE util)
|
||||
@@ -0,0 +1,182 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vkutil/vkutil.h>
|
||||
|
||||
namespace vkutil {
|
||||
|
||||
struct Image {
|
||||
vma::Allocator allocator;
|
||||
|
||||
vma::Allocation allocation;
|
||||
vk::Image image{};
|
||||
vk::ImageView view{};
|
||||
vk::Sampler sampler{};
|
||||
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
vk::Format format;
|
||||
ImageLayout layout = ImageLayout::Undefined;
|
||||
|
||||
// should the existing image, view, sampler be destroyed when this image is destroyed?
|
||||
bool destroy_on_deletion = true;
|
||||
|
||||
Image();
|
||||
Image(vma::Allocator allocator, uint32_t width, uint32_t height, vk::Format format);
|
||||
~Image();
|
||||
|
||||
// allow the object to be moved
|
||||
Image(Image &&other) noexcept;
|
||||
Image &operator=(Image &&other) noexcept;
|
||||
|
||||
// make sure an image is never copied
|
||||
Image(const Image &) = delete;
|
||||
Image &operator=(Image const &) = delete;
|
||||
|
||||
void init_image(vk::ImageUsageFlags usage, vk::ComponentMapping mapping = default_comp_mapping);
|
||||
// called by ~Image
|
||||
void destroy();
|
||||
|
||||
void transition_to(vk::CommandBuffer buffer, ImageLayout new_layout, const vk::ImageSubresourceRange &range = color_subresource_range);
|
||||
// use this when you don't care about the former content of the image
|
||||
void transition_to_discard(vk::CommandBuffer buffer, ImageLayout new_layout, const vk::ImageSubresourceRange &range = color_subresource_range);
|
||||
};
|
||||
|
||||
struct Buffer {
|
||||
vma::Allocator allocator;
|
||||
vma::Allocation allocation;
|
||||
vk::Buffer buffer;
|
||||
|
||||
vk::DeviceSize size;
|
||||
// only useful is buffer is host visible
|
||||
void *mapped_data = nullptr;
|
||||
|
||||
bool destroy_on_deletion = true;
|
||||
|
||||
Buffer();
|
||||
Buffer(vma::Allocator allocator, vk::DeviceSize size);
|
||||
~Buffer();
|
||||
|
||||
// allow the object to be moved
|
||||
Buffer(Buffer &&other) noexcept;
|
||||
Buffer &operator=(Buffer &&other) noexcept;
|
||||
|
||||
// make sure an image is never copied
|
||||
Buffer(const Buffer &) = delete;
|
||||
Buffer &operator=(Buffer const &) = delete;
|
||||
|
||||
void init_buffer(vk::BufferUsageFlags usage_flags, const vma::AllocationCreateInfo &alloc_create_info = vma_auto_alloc);
|
||||
// called by ~Image
|
||||
void destroy();
|
||||
};
|
||||
|
||||
// structure that holds contiguous data
|
||||
// if the end is reached, it starts back at the beginning
|
||||
class RingBuffer {
|
||||
protected:
|
||||
vkutil::Buffer buffer;
|
||||
vk::BufferUsageFlags usage;
|
||||
|
||||
uint32_t cursor = ~0;
|
||||
uint32_t capacity;
|
||||
|
||||
virtual void create() = 0;
|
||||
|
||||
public:
|
||||
uint32_t data_offset = 0;
|
||||
|
||||
explicit RingBuffer(vma::Allocator allocator, vk::BufferUsageFlags usage, const size_t capacity);
|
||||
|
||||
// Allocate new data from ring buffer
|
||||
void allocate(const uint32_t data_size);
|
||||
// copy the content to the framebuffer
|
||||
// cmd_buffer may not be used
|
||||
virtual void copy(vk::CommandBuffer cmd_buffer, const uint32_t size, const void *data, const uint32_t offset = 0) = 0;
|
||||
|
||||
// allocate then copy
|
||||
void allocate(vk::CommandBuffer cmd_buffer, const uint32_t data_size, const void *data) {
|
||||
allocate(data_size);
|
||||
copy(cmd_buffer, data_size, data);
|
||||
}
|
||||
|
||||
vk::Buffer handle() const {
|
||||
return buffer.buffer;
|
||||
}
|
||||
};
|
||||
|
||||
// RingBuffer allocated in the GPU memory, may not be accessible from the host
|
||||
// updates are done with updateBuffer, so each data chunk should be small
|
||||
// this is used for our uniform buffers
|
||||
class LocalRingBuffer : public RingBuffer {
|
||||
protected:
|
||||
void create() override;
|
||||
|
||||
public:
|
||||
explicit LocalRingBuffer(vma::Allocator allocator, vk::BufferUsageFlags usage, const size_t capacity)
|
||||
: RingBuffer(allocator, usage, capacity) {
|
||||
create();
|
||||
}
|
||||
|
||||
void copy(vk::CommandBuffer cmd_buffer, const uint32_t size, const void *data, const uint32_t offset = 0) override;
|
||||
};
|
||||
|
||||
// RingBuffer using host visible memory
|
||||
// Should be used for content that is only read a few times and not worth
|
||||
// transferring to the GPU memory
|
||||
class HostRingBuffer : public RingBuffer {
|
||||
protected:
|
||||
void create() override;
|
||||
|
||||
public:
|
||||
explicit HostRingBuffer(vma::Allocator allocator, vk::BufferUsageFlags usage, const size_t capacity)
|
||||
: RingBuffer(allocator, usage, capacity) {
|
||||
create();
|
||||
}
|
||||
|
||||
void copy(vk::CommandBuffer cmd_buffer, const uint32_t size, const void *data, const uint32_t offset = 0) override;
|
||||
};
|
||||
|
||||
// Queue that contains GPU objects that are planned to be destroyed (deferred destruction)
|
||||
class DestroyQueue {
|
||||
private:
|
||||
vk::Device device;
|
||||
vma::Allocator allocator;
|
||||
std::vector<uint64_t> destroy_list;
|
||||
|
||||
public:
|
||||
void init(vk::Device device, vma::Allocator allocator);
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<vk::isVulkanHandleType<T>::value> add(T &vk_object) {
|
||||
if (!vk_object)
|
||||
return;
|
||||
|
||||
destroy_list.push_back(static_cast<uint64_t>(T::objectType));
|
||||
destroy_list.push_back(to_u64(vk_object));
|
||||
vk_object = nullptr;
|
||||
}
|
||||
|
||||
void add_image(Image &image);
|
||||
void add_buffer(Buffer &buffer);
|
||||
void add_cmd_buffer(vk::CommandBuffer cmd_buffer, vk::CommandPool cmd_pool);
|
||||
|
||||
void destroy_objects();
|
||||
};
|
||||
|
||||
}; // namespace vkutil
|
||||
@@ -0,0 +1,134 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#define VULKAN_HPP_NO_CONSTRUCTORS
|
||||
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
|
||||
#include <vk_mem_alloc.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace vkutil {
|
||||
|
||||
static constexpr vk::ImageSubresourceRange color_subresource_range = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
|
||||
static constexpr vk::ImageSubresourceRange ds_subresource_range = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
|
||||
static constexpr vk::ImageSubresourceLayers color_subresource_layer = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
|
||||
static constexpr vk::ComponentMapping default_comp_mapping = {
|
||||
vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity,
|
||||
vk::ComponentSwizzle::eIdentity
|
||||
};
|
||||
|
||||
static constexpr vk::ComponentMapping rgba_mapping = {
|
||||
vk::ComponentSwizzle::eR,
|
||||
vk::ComponentSwizzle::eG,
|
||||
vk::ComponentSwizzle::eB,
|
||||
vk::ComponentSwizzle::eA
|
||||
};
|
||||
|
||||
static constexpr vk::ColorComponentFlags default_color_mask = vk::ColorComponentFlagBits::eR
|
||||
| vk::ColorComponentFlagBits::eG
|
||||
| vk::ColorComponentFlagBits::eB
|
||||
| vk::ColorComponentFlagBits::eA;
|
||||
|
||||
static constexpr vma::AllocationCreateInfo vma_auto_alloc = {
|
||||
.usage = vma::MemoryUsage::eAuto
|
||||
};
|
||||
|
||||
static constexpr vma::AllocationCreateInfo vma_mapped_alloc = {
|
||||
.flags = vma::AllocationCreateFlagBits::eHostAccessSequentialWrite | vma::AllocationCreateFlagBits::eMapped,
|
||||
.usage = vma::MemoryUsage::eAuto,
|
||||
};
|
||||
|
||||
static constexpr vma::AllocationCreateInfo vma_host_visible = {
|
||||
.flags = vma::AllocationCreateFlagBits::eHostAccessSequentialWrite,
|
||||
.usage = vma::MemoryUsage::eAuto,
|
||||
.requiredFlags = vk::MemoryPropertyFlagBits::eHostVisible,
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static std::enable_if_t<vk::isVulkanHandleType<T>::value, uint64_t> &to_u64(T &vk_object) {
|
||||
return reinterpret_cast<uint64_t &>(vk_object);
|
||||
}
|
||||
|
||||
static uint64_t &to_u64(vma::Allocation &vk_object) {
|
||||
return reinterpret_cast<uint64_t &>(vk_object);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::enable_if_t<vk::isVulkanHandleType<T>::value, T> &from_u64(uint64_t &vk_object) {
|
||||
return reinterpret_cast<T &>(vk_object);
|
||||
}
|
||||
|
||||
static vma::Allocation &from_u64(uint64_t &vk_object) {
|
||||
return reinterpret_cast<vma::Allocation &>(vk_object);
|
||||
}
|
||||
|
||||
vk::CommandBuffer create_single_time_command(vk::Device &device, vk::CommandPool &cmd_pool);
|
||||
void end_single_time_command(vk::Device &device, vk::Queue &queue, vk::CommandPool &cmd_pool, vk::CommandBuffer cmd_buffer);
|
||||
|
||||
vk::ShaderModule load_shader(vk::Device &device, const std::string &path);
|
||||
vk::ShaderModule load_shader(vk::Device &device, const void *data, const uint32_t size);
|
||||
|
||||
void copy_buffer(vk::Device &device, vk::CommandPool &cmd_pool, vk::Queue &queue, vk::Buffer &src, vk::Buffer &dst, vk::DeviceSize size);
|
||||
|
||||
enum struct ImageLayout {
|
||||
Undefined,
|
||||
TransferSrc,
|
||||
TransferDst,
|
||||
ColorAttachment,
|
||||
DepthStencilAttachment,
|
||||
ColorAttachmentReadWrite,
|
||||
SampledImage,
|
||||
StorageImage,
|
||||
DepthReadOnly
|
||||
};
|
||||
void transition_image_layout(vk::CommandBuffer &cmd_buffer, vk::Image image, ImageLayout src_layout, ImageLayout dst_layout, const vk::ImageSubresourceRange &range = color_subresource_range);
|
||||
// transition image layout assuming you don't care about the former image content
|
||||
void transition_image_layout_discard(vk::CommandBuffer &cmd_buffer, vk::Image image, ImageLayout src_layout, ImageLayout dst_layout, const vk::ImageSubresourceRange &range = color_subresource_range);
|
||||
|
||||
// given the swizzle of the color surface (which was written as rgba because vulkan doesn't support swizzle on framebuffers)
|
||||
// and the swizzle of the texture that's reading from the color surface
|
||||
// return the swizzle the texture should to make it like the color actually had a swizzle applied to it
|
||||
vk::ComponentMapping color_to_texture_swizzle(const vk::ComponentMapping &swizzle_color, const vk::ComponentMapping &swizzle_texture);
|
||||
} // namespace vkutil
|
||||
@@ -0,0 +1,297 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2021 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "vkutil/objects.h"
|
||||
|
||||
#include "vkutil/vkutil.h"
|
||||
|
||||
#include <util/log.h>
|
||||
|
||||
namespace vkutil {
|
||||
Image::Image() {}
|
||||
|
||||
Image::Image(Image &&other) noexcept {
|
||||
memcpy(this, &other, sizeof(Image));
|
||||
other.sampler = nullptr;
|
||||
other.view = nullptr;
|
||||
other.image = nullptr;
|
||||
other.layout = ImageLayout::Undefined;
|
||||
}
|
||||
Image &Image::operator=(Image &&other) noexcept {
|
||||
memcpy(this, &other, sizeof(Image));
|
||||
other.sampler = nullptr;
|
||||
other.view = nullptr;
|
||||
other.image = nullptr;
|
||||
other.layout = ImageLayout::Undefined;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Image(vma::Allocator allocator, uint32_t width, uint32_t height, vk::Format format)
|
||||
: allocator(allocator)
|
||||
, width(width)
|
||||
, height(height)
|
||||
, format(format) {
|
||||
}
|
||||
|
||||
void Image::destroy() {
|
||||
if (!destroy_on_deletion || !allocator)
|
||||
return;
|
||||
|
||||
vk::Device device = allocator.getAllocatorInfo().device;
|
||||
if (sampler) {
|
||||
device.destroySampler(sampler);
|
||||
sampler = nullptr;
|
||||
}
|
||||
if (view) {
|
||||
device.destroyImageView(view);
|
||||
view = nullptr;
|
||||
}
|
||||
if (image) {
|
||||
allocator.destroyImage(image, allocation);
|
||||
image = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Image::~Image() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
void Image::init_image(vk::ImageUsageFlags usage, vk::ComponentMapping mapping) {
|
||||
vk::ImageCreateInfo image_info{
|
||||
.imageType = vk::ImageType::e2D,
|
||||
.format = format,
|
||||
.extent = vk::Extent3D{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.depth = 1 },
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = vk::ImageTiling::eOptimal,
|
||||
.usage = usage,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
};
|
||||
|
||||
std::tie(image, allocation) = allocator.createImage(image_info, vma_auto_alloc);
|
||||
|
||||
vk::ImageSubresourceRange range = (format == vk::Format::eD32SfloatS8Uint) ? vkutil::ds_subresource_range : vkutil::color_subresource_range;
|
||||
vk::ImageViewCreateInfo view_info{
|
||||
.image = image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = format,
|
||||
.components = mapping,
|
||||
.subresourceRange = range
|
||||
};
|
||||
view = allocator.getAllocatorInfo().device.createImageView(view_info);
|
||||
}
|
||||
|
||||
void Image::transition_to(vk::CommandBuffer buffer, ImageLayout new_layout, const vk::ImageSubresourceRange &range) {
|
||||
transition_image_layout(buffer, image, layout, new_layout, range);
|
||||
layout = new_layout;
|
||||
}
|
||||
|
||||
void Image::transition_to_discard(vk::CommandBuffer buffer, ImageLayout new_layout, const vk::ImageSubresourceRange &range) {
|
||||
transition_image_layout_discard(buffer, image, layout, new_layout, range);
|
||||
layout = new_layout;
|
||||
}
|
||||
|
||||
Buffer::Buffer() {}
|
||||
|
||||
Buffer::Buffer(Buffer &&other) noexcept {
|
||||
memcpy(this, &other, sizeof(Buffer));
|
||||
other.allocation = nullptr;
|
||||
other.buffer = nullptr;
|
||||
other.size = 0;
|
||||
other.mapped_data = nullptr;
|
||||
}
|
||||
Buffer &Buffer::operator=(Buffer &&other) noexcept {
|
||||
memcpy(this, &other, sizeof(Buffer));
|
||||
other.allocation = nullptr;
|
||||
other.buffer = nullptr;
|
||||
other.size = 0;
|
||||
other.mapped_data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Buffer::Buffer(vma::Allocator allocator, vk::DeviceSize size)
|
||||
: allocator(allocator)
|
||||
, size(size) {
|
||||
}
|
||||
|
||||
void Buffer::destroy() {
|
||||
if (!destroy_on_deletion || !allocator)
|
||||
return;
|
||||
|
||||
if (buffer)
|
||||
allocator.destroyBuffer(buffer, allocation);
|
||||
}
|
||||
|
||||
Buffer::~Buffer() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
void Buffer::init_buffer(vk::BufferUsageFlags usage_flags, const vma::AllocationCreateInfo &alloc_create_info) {
|
||||
vk::BufferCreateInfo buffer_info{
|
||||
.size = size,
|
||||
.usage = usage_flags,
|
||||
.sharingMode = vk::SharingMode::eExclusive
|
||||
};
|
||||
vma::AllocationInfo alloc_info;
|
||||
std::tie(buffer, allocation) = allocator.createBuffer(buffer_info, alloc_create_info, alloc_info);
|
||||
mapped_data = alloc_info.pMappedData;
|
||||
}
|
||||
|
||||
RingBuffer::RingBuffer(vma::Allocator allocator, vk::BufferUsageFlags usage, const size_t capacity)
|
||||
: usage(usage)
|
||||
, capacity(capacity) {
|
||||
uint32_t buffer_capacity = capacity;
|
||||
if (usage & vk::BufferUsageFlagBits::eStorageBuffer)
|
||||
// TODO: put max size of a gxm uniform buffer
|
||||
buffer_capacity += 500 * 1024;
|
||||
if (usage & vk::BufferUsageFlagBits::eVertexBuffer)
|
||||
// for AMD GPUs, in case the buffer ends exactly with an rgb16 component (which is read as rgba)
|
||||
buffer_capacity += 2;
|
||||
|
||||
buffer = Buffer(allocator, buffer_capacity);
|
||||
}
|
||||
|
||||
void RingBuffer::allocate(const uint32_t data_size) {
|
||||
if (cursor + data_size > capacity) {
|
||||
// LOG_WARNING("End of buffer reached");
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
data_offset = cursor;
|
||||
|
||||
cursor += data_size;
|
||||
// align to 256 bytes, the granularity is at most this value in any gpu
|
||||
cursor = (cursor + 255) & ~255;
|
||||
}
|
||||
|
||||
void HostRingBuffer::create() {
|
||||
buffer.init_buffer(usage, vma_mapped_alloc);
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
void HostRingBuffer::copy(vk::CommandBuffer cmd_buffer, const uint32_t size, const void *data, const uint32_t offset) {
|
||||
// TODO: call flush if the memory is not coherent
|
||||
memcpy(reinterpret_cast<uint8_t *>(buffer.mapped_data) + data_offset + offset, data, size);
|
||||
}
|
||||
|
||||
void LocalRingBuffer::create() {
|
||||
// the auto_alloc default behavior should give us memory on the gpu
|
||||
// UpdateBuffer needs the buffer to have TransferDst specified
|
||||
buffer.init_buffer(usage | vk::BufferUsageFlagBits::eTransferDst);
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
void LocalRingBuffer::copy(vk::CommandBuffer cmd_buffer, const uint32_t size, const void *data, const uint32_t offset) {
|
||||
cmd_buffer.updateBuffer(buffer.buffer, data_offset + offset, size, data);
|
||||
}
|
||||
|
||||
void DestroyQueue::init(vk::Device device, vma::Allocator allocator) {
|
||||
this->device = device;
|
||||
this->allocator = allocator;
|
||||
}
|
||||
|
||||
void DestroyQueue::add_image(Image &image) {
|
||||
if (image.sampler) {
|
||||
add(image.sampler);
|
||||
image.sampler = nullptr;
|
||||
}
|
||||
if (image.view) {
|
||||
add(image.view);
|
||||
image.view = nullptr;
|
||||
}
|
||||
|
||||
if (image.image) {
|
||||
add(image.image);
|
||||
image.image = nullptr;
|
||||
destroy_list.push_back(to_u64(image.allocation));
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyQueue::add_buffer(Buffer &buffer) {
|
||||
if (buffer.buffer) {
|
||||
add(buffer.buffer);
|
||||
buffer.buffer = nullptr;
|
||||
destroy_list.push_back(to_u64(buffer.allocation));
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyQueue::add_cmd_buffer(vk::CommandBuffer cmd_buffer, vk::CommandPool cmd_pool) {
|
||||
add(cmd_buffer);
|
||||
destroy_list.push_back(to_u64(cmd_pool));
|
||||
}
|
||||
|
||||
#define HANDLE_DESTROY(type) \
|
||||
case vk::ObjectType::e##type: { \
|
||||
auto vk_object = from_u64<vk::type>(el); \
|
||||
device.destroy(vk_object); \
|
||||
break; \
|
||||
}
|
||||
|
||||
void DestroyQueue::destroy_objects() {
|
||||
if (destroy_list.empty())
|
||||
return;
|
||||
|
||||
int idx = 0;
|
||||
while (idx < destroy_list.size()) {
|
||||
const vk ::ObjectType type = static_cast<vk::ObjectType>(destroy_list[idx++]);
|
||||
uint64_t el = destroy_list[idx++];
|
||||
switch (type) {
|
||||
// handle special cases apart
|
||||
|
||||
case vk::ObjectType::eImage: {
|
||||
// special case: this is a vma allocation
|
||||
auto image = from_u64<vk::Image>(el);
|
||||
vma::Allocation allocation = from_u64(destroy_list[idx++]);
|
||||
allocator.destroyImage(image, allocation);
|
||||
break;
|
||||
}
|
||||
|
||||
case vk::ObjectType::eBuffer: {
|
||||
// special case: this is a vma allocation
|
||||
auto buffer = from_u64<vk::Buffer>(el);
|
||||
vma::Allocation allocation = from_u64(destroy_list[idx++]);
|
||||
allocator.destroyBuffer(buffer, allocation);
|
||||
break;
|
||||
}
|
||||
|
||||
case vk::ObjectType::eCommandBuffer: {
|
||||
// special case: we must specify the command pool
|
||||
auto cmd_buffer = from_u64<vk::CommandBuffer>(el);
|
||||
auto cmd_pool = from_u64<vk::CommandPool>(destroy_list[idx++]);
|
||||
device.freeCommandBuffers(cmd_pool, cmd_buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
HANDLE_DESTROY(ImageView)
|
||||
HANDLE_DESTROY(Sampler)
|
||||
HANDLE_DESTROY(Fence)
|
||||
HANDLE_DESTROY(Semaphore)
|
||||
HANDLE_DESTROY(Framebuffer)
|
||||
|
||||
default:
|
||||
LOG_ERROR("Unknown object type {}", vk::to_string(type));
|
||||
}
|
||||
}
|
||||
|
||||
destroy_list.clear();
|
||||
}
|
||||
} // namespace vkutil
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user