renderer: Add Vulkan renderer

This commit is contained in:
Macdu
2022-06-23 15:53:26 +02:00
parent 2af0036b6a
commit b6e1fe24aa
101 changed files with 9315 additions and 3055 deletions
+98 -95
View File
@@ -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
-1
View File
@@ -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 "")
+6 -8
View File
@@ -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)
Vendored Submodule
+1
Submodule external/Vulkan-Headers added at 3be1df310b
+1 -1
+1 -4
View File
@@ -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)
+6 -17
View File
@@ -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();
+2 -1
View File
@@ -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) \
+3 -5
View File
@@ -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);
-8
View File
@@ -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;
}
+2 -1
View File
@@ -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;
+3 -12
View File
@@ -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)
+75 -41
View File
@@ -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.
-1
View File
@@ -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) {
+79 -22
View File
@@ -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;
-54
View File
@@ -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
+26 -13
View File
@@ -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();
+2
View File
@@ -90,4 +90,6 @@ const char *get_container_name(const std::uint16_t idx);
int get_uniform_buffer_base(const SceGxmProgram &program, const SceGxmProgramParameter &parameter);
uint16_t get_texture_count(const SceGxmProgram &program_gxp);
} // namespace gxp
+1 -1
View File
@@ -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,
+39
View File
@@ -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
View File
@@ -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)
+36 -52
View File
@@ -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;
}
+14 -37
View File
@@ -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
+7 -2
View File
@@ -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
+10 -2
View File
@@ -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 &params, 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);
+4 -3
View File
@@ -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
+3 -43
View File
@@ -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 {
+10 -5
View File
@@ -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
+19 -2
View File
@@ -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;
+82 -27
View File
@@ -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 &params, 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
+24 -15
View File
@@ -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
+140 -3
View File
@@ -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
+8 -7
View File
@@ -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);
}
}
+114 -29
View File
@@ -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;
-39
View File
@@ -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
+17 -49
View File
@@ -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;
+13 -11
View File
@@ -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();
-202
View File
@@ -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
+26 -55
View File
@@ -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 = &current_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, &current_color_attachment_handle, nullptr, &current_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, &current_color_attachment_handle, nullptr, &current_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
+1 -1
View File
@@ -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");
+8 -14
View File
@@ -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;
+19 -70
View File
@@ -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));
}
}
+28 -528
View File
@@ -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)))
+6 -17
View File
@@ -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
+40 -19
View File
@@ -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) {
+264
View File
@@ -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
+194 -81
View File
@@ -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) {
+16
View File
@@ -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
+629 -2
View File
@@ -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) {
+55
View File
@@ -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
+3 -3
View File
@@ -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
+237
View File
@@ -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
+329
View File
@@ -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 &params, 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
+356 -453
View File
@@ -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 = &current_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
+341
View File
@@ -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 &image;
}
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
+228
View File
@@ -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
+352
View File
@@ -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) {
}
+258 -162
View File
@@ -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 &param
// 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 &param
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 &param
}
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 &param
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 &param
}
};
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 &param
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);
}
+8 -5
View File
@@ -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:
+12 -11
View File
@@ -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;
+1 -1
View File
@@ -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,
+9 -6
View File
@@ -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;
}
+25 -1
View File
@@ -179,8 +179,32 @@ void get_attribute_informations(const SceGxmProgram &program, AttributeInformati
for (size_t i = 0; i < program.parameter_count; ++i) {
const SceGxmProgramParameter &parameter = 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;
}
}
+15 -11
View File
@@ -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.
+13
View File
@@ -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)
+182
View File
@@ -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
+134
View File
@@ -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
+297
View File
@@ -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