video: Import new shader recompiler + display a triangle (#142)
This commit is contained in:
parent
8cf64a33b2
commit
8730968385
|
@ -56,3 +56,6 @@
|
||||||
[submodule "externals/zydis"]
|
[submodule "externals/zydis"]
|
||||||
path = externals/zydis
|
path = externals/zydis
|
||||||
url = https://github.com/zyantific/zydis.git
|
url = https://github.com/zyantific/zydis.git
|
||||||
|
[submodule "externals/sirit"]
|
||||||
|
path = externals/sirit
|
||||||
|
url = https://github.com/raphaelthegreat/sirit
|
||||||
|
|
|
@ -218,6 +218,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||||
src/common/io_file.h
|
src/common/io_file.h
|
||||||
src/common/error.cpp
|
src/common/error.cpp
|
||||||
src/common/error.h
|
src/common/error.h
|
||||||
|
src/common/scope_exit.h
|
||||||
|
src/common/func_traits.h
|
||||||
src/common/native_clock.cpp
|
src/common/native_clock.cpp
|
||||||
src/common/native_clock.h
|
src/common/native_clock.h
|
||||||
src/common/path_util.cpp
|
src/common/path_util.cpp
|
||||||
|
@ -286,6 +288,75 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||||
src/core/virtual_memory.h
|
src/core/virtual_memory.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(SHADER_RECOMPILER src/shader_recompiler/exception.h
|
||||||
|
src/shader_recompiler/object_pool.h
|
||||||
|
src/shader_recompiler/profile.h
|
||||||
|
src/shader_recompiler/recompiler.cpp
|
||||||
|
src/shader_recompiler/recompiler.h
|
||||||
|
src/shader_recompiler/runtime_info.h
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv.h
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_composite.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_logical.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_select.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_special.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/emit_spirv_undefined.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
|
||||||
|
src/shader_recompiler/backend/spirv/spirv_emit_context.h
|
||||||
|
src/shader_recompiler/frontend/translate/data_share.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/export.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/flat_memory.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/scalar_alu.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/scalar_memory.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/translate.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/translate.h
|
||||||
|
src/shader_recompiler/frontend/translate/vector_alu.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/vector_interpolation.cpp
|
||||||
|
src/shader_recompiler/frontend/translate/vector_memory.cpp
|
||||||
|
src/shader_recompiler/frontend/control_flow_graph.cpp
|
||||||
|
src/shader_recompiler/frontend/control_flow_graph.h
|
||||||
|
src/shader_recompiler/frontend/decode.cpp
|
||||||
|
src/shader_recompiler/frontend/decode.h
|
||||||
|
src/shader_recompiler/frontend/format.cpp
|
||||||
|
src/shader_recompiler/frontend/instruction.cpp
|
||||||
|
src/shader_recompiler/frontend/instruction.h
|
||||||
|
src/shader_recompiler/frontend/opcodes.h
|
||||||
|
src/shader_recompiler/frontend/structured_control_flow.cpp
|
||||||
|
src/shader_recompiler/frontend/structured_control_flow.h
|
||||||
|
src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
|
||||||
|
src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
|
||||||
|
src/shader_recompiler/ir/passes/constant_propogation_pass.cpp
|
||||||
|
src/shader_recompiler/ir/passes/passes.h
|
||||||
|
src/shader_recompiler/ir/abstract_syntax_list.h
|
||||||
|
src/shader_recompiler/ir/attribute.cpp
|
||||||
|
src/shader_recompiler/ir/attribute.h
|
||||||
|
src/shader_recompiler/ir/basic_block.cpp
|
||||||
|
src/shader_recompiler/ir/basic_block.h
|
||||||
|
src/shader_recompiler/ir/condition.h
|
||||||
|
src/shader_recompiler/ir/ir_emitter.cpp
|
||||||
|
src/shader_recompiler/ir/ir_emitter.h
|
||||||
|
src/shader_recompiler/ir/microinstruction.cpp
|
||||||
|
src/shader_recompiler/ir/opcodes.cpp
|
||||||
|
src/shader_recompiler/ir/opcodes.h
|
||||||
|
src/shader_recompiler/ir/opcodes.inc
|
||||||
|
src/shader_recompiler/ir/post_order.cpp
|
||||||
|
src/shader_recompiler/ir/post_order.h
|
||||||
|
src/shader_recompiler/ir/program.cpp
|
||||||
|
src/shader_recompiler/ir/program.h
|
||||||
|
src/shader_recompiler/ir/reg.h
|
||||||
|
src/shader_recompiler/ir/type.cpp
|
||||||
|
src/shader_recompiler/ir/type.h
|
||||||
|
src/shader_recompiler/ir/value.cpp
|
||||||
|
src/shader_recompiler/ir/value.h
|
||||||
|
)
|
||||||
|
|
||||||
set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
||||||
src/video_core/amdgpu/liverpool.h
|
src/video_core/amdgpu/liverpool.h
|
||||||
src/video_core/amdgpu/pixel_format.cpp
|
src/video_core/amdgpu/pixel_format.cpp
|
||||||
|
@ -293,18 +364,26 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
||||||
src/video_core/amdgpu/pm4_cmds.h
|
src/video_core/amdgpu/pm4_cmds.h
|
||||||
src/video_core/amdgpu/pm4_opcodes.h
|
src/video_core/amdgpu/pm4_opcodes.h
|
||||||
src/video_core/amdgpu/resource.h
|
src/video_core/amdgpu/resource.h
|
||||||
|
src/video_core/renderer_vulkan/liverpool_to_vk.cpp
|
||||||
|
src/video_core/renderer_vulkan/liverpool_to_vk.h
|
||||||
src/video_core/renderer_vulkan/renderer_vulkan.cpp
|
src/video_core/renderer_vulkan/renderer_vulkan.cpp
|
||||||
src/video_core/renderer_vulkan/renderer_vulkan.h
|
src/video_core/renderer_vulkan/renderer_vulkan.h
|
||||||
src/video_core/renderer_vulkan/vk_common.cpp
|
src/video_core/renderer_vulkan/vk_common.cpp
|
||||||
src/video_core/renderer_vulkan/vk_common.h
|
src/video_core/renderer_vulkan/vk_common.h
|
||||||
src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp
|
src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp
|
||||||
src/video_core/renderer_vulkan/vk_descriptor_update_queue.h
|
src/video_core/renderer_vulkan/vk_descriptor_update_queue.h
|
||||||
|
src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
|
||||||
|
src/video_core/renderer_vulkan/vk_graphics_pipeline.h
|
||||||
src/video_core/renderer_vulkan/vk_instance.cpp
|
src/video_core/renderer_vulkan/vk_instance.cpp
|
||||||
src/video_core/renderer_vulkan/vk_instance.h
|
src/video_core/renderer_vulkan/vk_instance.h
|
||||||
src/video_core/renderer_vulkan/vk_master_semaphore.cpp
|
src/video_core/renderer_vulkan/vk_master_semaphore.cpp
|
||||||
src/video_core/renderer_vulkan/vk_master_semaphore.h
|
src/video_core/renderer_vulkan/vk_master_semaphore.h
|
||||||
|
src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
|
||||||
|
src/video_core/renderer_vulkan/vk_pipeline_cache.h
|
||||||
src/video_core/renderer_vulkan/vk_platform.cpp
|
src/video_core/renderer_vulkan/vk_platform.cpp
|
||||||
src/video_core/renderer_vulkan/vk_platform.h
|
src/video_core/renderer_vulkan/vk_platform.h
|
||||||
|
src/video_core/renderer_vulkan/vk_rasterizer.cpp
|
||||||
|
src/video_core/renderer_vulkan/vk_rasterizer.h
|
||||||
src/video_core/renderer_vulkan/vk_resource_pool.cpp
|
src/video_core/renderer_vulkan/vk_resource_pool.cpp
|
||||||
src/video_core/renderer_vulkan/vk_resource_pool.h
|
src/video_core/renderer_vulkan/vk_resource_pool.h
|
||||||
src/video_core/renderer_vulkan/vk_scheduler.cpp
|
src/video_core/renderer_vulkan/vk_scheduler.cpp
|
||||||
|
@ -371,6 +450,7 @@ if (ENABLE_QT_GUI)
|
||||||
${QT_GUI}
|
${QT_GUI}
|
||||||
${COMMON}
|
${COMMON}
|
||||||
${CORE}
|
${CORE}
|
||||||
|
${SHADER_RECOMPILER}
|
||||||
${VIDEO_CORE}
|
${VIDEO_CORE}
|
||||||
src/sdl_window.h
|
src/sdl_window.h
|
||||||
src/sdl_window.cpp
|
src/sdl_window.cpp
|
||||||
|
@ -381,18 +461,18 @@ else()
|
||||||
${INPUT}
|
${INPUT}
|
||||||
${COMMON}
|
${COMMON}
|
||||||
${CORE}
|
${CORE}
|
||||||
|
${SHADER_RECOMPILER}
|
||||||
${VIDEO_CORE}
|
${VIDEO_CORE}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/sdl_window.h
|
src/sdl_window.h
|
||||||
src/sdl_window.cpp
|
src/sdl_window.cpp
|
||||||
src/common/scope_exit.h
|
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(shadps4)
|
create_target_directory_groups(shadps4)
|
||||||
|
|
||||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak)
|
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak)
|
||||||
target_link_libraries(shadps4 PRIVATE discord-rpc boost vma vulkan-headers xxhash Zydis SPIRV glslang SDL3-shared)
|
target_link_libraries(shadps4 PRIVATE discord-rpc boost vma sirit vulkan-headers xxhash Zydis SPIRV glslang SDL3-shared)
|
||||||
|
|
||||||
if (NOT ENABLE_QT_GUI)
|
if (NOT ENABLE_QT_GUI)
|
||||||
target_link_libraries(shadps4 PRIVATE SDL3-shared)
|
target_link_libraries(shadps4 PRIVATE SDL3-shared)
|
||||||
|
|
|
@ -87,3 +87,6 @@ if (WIN32)
|
||||||
add_subdirectory(winpthreads EXCLUDE_FROM_ALL)
|
add_subdirectory(winpthreads EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(winpthreads INTERFACE winpthreads/include)
|
target_include_directories(winpthreads INTERFACE winpthreads/include)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# sirit
|
||||||
|
add_subdirectory(sirit EXCLUDE_FROM_ALL)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 2dd57a940b6d1b733cbd1abbc3f842da476d3d48
|
Subproject commit 87b7817119982e8ad6068855fae31b11590514be
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9c12a07e62dfa404727e7fc85dd83bba84cc830d
|
|
@ -14,6 +14,7 @@ void assert_fail_impl() {
|
||||||
|
|
||||||
[[noreturn]] void unreachable_impl() {
|
[[noreturn]] void unreachable_impl() {
|
||||||
Common::Log::Stop();
|
Common::Log::Stop();
|
||||||
|
std::fflush(stdout);
|
||||||
Crash();
|
Crash();
|
||||||
throw std::runtime_error("Unreachable code");
|
throw std::runtime_error("Unreachable code");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
|
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
|
||||||
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
|
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
|
||||||
|
@ -58,3 +59,103 @@
|
||||||
using T = std::underlying_type_t<type>; \
|
using T = std::underlying_type_t<type>; \
|
||||||
return static_cast<T>(key) == 0; \
|
return static_cast<T>(key) == 0; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Flags {
|
||||||
|
public:
|
||||||
|
using IntType = std::underlying_type_t<T>;
|
||||||
|
|
||||||
|
Flags() {}
|
||||||
|
|
||||||
|
Flags(IntType t) : m_bits(t) {}
|
||||||
|
|
||||||
|
template <typename... Tx>
|
||||||
|
Flags(T f, Tx... fx) {
|
||||||
|
this->set(f, fx...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Tx>
|
||||||
|
void set(Tx... fx) {
|
||||||
|
m_bits |= bits(fx...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(Flags flags) {
|
||||||
|
m_bits |= flags.m_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Tx>
|
||||||
|
void clr(Tx... fx) {
|
||||||
|
m_bits &= ~bits(fx...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clr(Flags flags) {
|
||||||
|
m_bits &= ~flags.m_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Tx>
|
||||||
|
bool any(Tx... fx) const {
|
||||||
|
return (m_bits & bits(fx...)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Tx>
|
||||||
|
bool all(Tx... fx) const {
|
||||||
|
const IntType mask = bits(fx...);
|
||||||
|
return (m_bits & mask) == mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test(T f) const {
|
||||||
|
return this->any(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isClear() const {
|
||||||
|
return m_bits == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clrAll() {
|
||||||
|
m_bits = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 raw() const {
|
||||||
|
return m_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flags operator&(const Flags& other) const {
|
||||||
|
return Flags(m_bits & other.m_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
Flags operator|(const Flags& other) const {
|
||||||
|
return Flags(m_bits | other.m_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
Flags operator^(const Flags& other) const {
|
||||||
|
return Flags(m_bits ^ other.m_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Flags& other) const {
|
||||||
|
return m_bits == other.m_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Flags& other) const {
|
||||||
|
return m_bits != other.m_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
IntType m_bits = 0;
|
||||||
|
|
||||||
|
static IntType bit(T f) {
|
||||||
|
return IntType(1) << static_cast<IntType>(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Tx>
|
||||||
|
static IntType bits(T f, Tx... fx) {
|
||||||
|
return bit(f) | bits(fx...);
|
||||||
|
}
|
||||||
|
|
||||||
|
static IntType bits() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <class Func>
|
||||||
|
struct FuncTraits {};
|
||||||
|
|
||||||
|
template <class ReturnType_, class... Args>
|
||||||
|
struct FuncTraits<ReturnType_ (*)(Args...)> {
|
||||||
|
using ReturnType = ReturnType_;
|
||||||
|
|
||||||
|
static constexpr size_t NUM_ARGS = sizeof...(Args);
|
||||||
|
|
||||||
|
template <size_t I>
|
||||||
|
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Func>
|
||||||
|
struct LambdaTraits : LambdaTraits<decltype(&std::remove_reference_t<Func>::operator())> {};
|
||||||
|
|
||||||
|
template <typename ReturnType, typename LambdaType, typename... Args>
|
||||||
|
struct LambdaTraits<ReturnType (LambdaType::*)(Args...) const> {
|
||||||
|
template <size_t I>
|
||||||
|
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
||||||
|
|
||||||
|
static constexpr size_t NUM_ARGS{sizeof...(Args)};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
|
@ -12,6 +12,9 @@
|
||||||
#include "video_core/amdgpu/pm4_cmds.h"
|
#include "video_core/amdgpu/pm4_cmds.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
|
||||||
|
extern Frontend::WindowSDL* g_window;
|
||||||
|
std::unique_ptr<Vulkan::RendererVulkan> renderer;
|
||||||
|
|
||||||
namespace Libraries::GnmDriver {
|
namespace Libraries::GnmDriver {
|
||||||
|
|
||||||
using namespace AmdGpu;
|
using namespace AmdGpu;
|
||||||
|
@ -1912,6 +1915,7 @@ int PS4_SYSV_ABI Func_F916890425496553() {
|
||||||
|
|
||||||
void RegisterlibSceGnmDriver(Core::Loader::SymbolsResolver* sym) {
|
void RegisterlibSceGnmDriver(Core::Loader::SymbolsResolver* sym) {
|
||||||
liverpool = std::make_unique<AmdGpu::Liverpool>();
|
liverpool = std::make_unique<AmdGpu::Liverpool>();
|
||||||
|
renderer = std::make_unique<Vulkan::RendererVulkan>(*g_window, liverpool.get());
|
||||||
|
|
||||||
LIB_FUNCTION("b0xyllnVY-I", "libSceGnmDriver", 1, "libSceGnmDriver", 1, 1, sceGnmAddEqEvent);
|
LIB_FUNCTION("b0xyllnVY-I", "libSceGnmDriver", 1, "libSceGnmDriver", 1, 1, sceGnmAddEqEvent);
|
||||||
LIB_FUNCTION("b08AgtPlHPg", "libSceGnmDriver", 1, "libSceGnmDriver", 1, 1,
|
LIB_FUNCTION("b08AgtPlHPg", "libSceGnmDriver", 1, "libSceGnmDriver", 1, 1,
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
|
||||||
extern Frontend::WindowSDL* g_window;
|
extern std::unique_ptr<Vulkan::RendererVulkan> renderer;
|
||||||
|
|
||||||
namespace Libraries::VideoOut {
|
namespace Libraries::VideoOut {
|
||||||
|
|
||||||
|
@ -41,8 +41,6 @@ VideoOutDriver::VideoOutDriver(u32 width, u32 height) {
|
||||||
main_port.resolution.fullHeight = height;
|
main_port.resolution.fullHeight = height;
|
||||||
main_port.resolution.paneWidth = width;
|
main_port.resolution.paneWidth = width;
|
||||||
main_port.resolution.paneHeight = height;
|
main_port.resolution.paneHeight = height;
|
||||||
|
|
||||||
renderer = std::make_unique<Vulkan::RendererVulkan>(*g_window);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoOutDriver::~VideoOutDriver() = default;
|
VideoOutDriver::~VideoOutDriver() = default;
|
||||||
|
|
|
@ -10,8 +10,7 @@
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
struct Frame;
|
struct Frame;
|
||||||
class RendererVulkan;
|
}
|
||||||
} // namespace Vulkan
|
|
||||||
|
|
||||||
namespace Libraries::VideoOut {
|
namespace Libraries::VideoOut {
|
||||||
|
|
||||||
|
@ -84,7 +83,6 @@ private:
|
||||||
std::condition_variable_any submit_cond;
|
std::condition_variable_any submit_cond;
|
||||||
std::condition_variable done_cond;
|
std::condition_variable done_cond;
|
||||||
std::queue<Request> requests;
|
std::queue<Request> requests;
|
||||||
std::unique_ptr<Vulkan::RendererVulkan> renderer;
|
|
||||||
bool is_neo{};
|
bool is_neo{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,9 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "magic_enum.hpp"
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
|
@ -81,7 +79,7 @@ private:
|
||||||
std::queue<IrqHandler> one_time_subscribers{};
|
std::queue<IrqHandler> one_time_subscribers{};
|
||||||
std::mutex m_lock{};
|
std::mutex m_lock{};
|
||||||
};
|
};
|
||||||
std::array<IrqContext, magic_enum::enum_count<InterruptId>()> irq_contexts{};
|
std::array<IrqContext, 8> irq_contexts{};
|
||||||
};
|
};
|
||||||
|
|
||||||
using IrqC = Common::Singleton<IrqController>;
|
using IrqC = Common::Singleton<IrqController>;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend {
|
||||||
|
|
||||||
|
struct Bindings {
|
||||||
|
u32 unified{};
|
||||||
|
u32 uniform_buffer{};
|
||||||
|
u32 storage_buffer{};
|
||||||
|
u32 texture{};
|
||||||
|
u32 image{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader::Backend
|
|
@ -0,0 +1,285 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/func_traits.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <auto func, typename... Args>
|
||||||
|
void SetDefinition(EmitContext& ctx, IR::Inst* inst, Args... args) {
|
||||||
|
inst->SetDefinition<Id>(func(ctx, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ArgType>
|
||||||
|
ArgType Arg(EmitContext& ctx, const IR::Value& arg) {
|
||||||
|
if constexpr (std::is_same_v<ArgType, Id>) {
|
||||||
|
return ctx.Def(arg);
|
||||||
|
} else if constexpr (std::is_same_v<ArgType, const IR::Value&>) {
|
||||||
|
return arg;
|
||||||
|
} else if constexpr (std::is_same_v<ArgType, u32>) {
|
||||||
|
return arg.U32();
|
||||||
|
} else if constexpr (std::is_same_v<ArgType, IR::Attribute>) {
|
||||||
|
return arg.Attribute();
|
||||||
|
} else if constexpr (std::is_same_v<ArgType, IR::ScalarReg>) {
|
||||||
|
return arg.ScalarReg();
|
||||||
|
} else if constexpr (std::is_same_v<ArgType, IR::VectorReg>) {
|
||||||
|
return arg.VectorReg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto func, bool is_first_arg_inst, size_t... I>
|
||||||
|
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
|
||||||
|
using Traits = Common::FuncTraits<decltype(func)>;
|
||||||
|
if constexpr (std::is_same_v<typename Traits::ReturnType, Id>) {
|
||||||
|
if constexpr (is_first_arg_inst) {
|
||||||
|
SetDefinition<func>(
|
||||||
|
ctx, inst, inst,
|
||||||
|
Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||||
|
} else {
|
||||||
|
SetDefinition<func>(
|
||||||
|
ctx, inst, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if constexpr (is_first_arg_inst) {
|
||||||
|
func(ctx, inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||||
|
} else {
|
||||||
|
func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto func>
|
||||||
|
void Invoke(EmitContext& ctx, IR::Inst* inst) {
|
||||||
|
using Traits = Common::FuncTraits<decltype(func)>;
|
||||||
|
static_assert(Traits::NUM_ARGS >= 1, "Insufficient arguments");
|
||||||
|
if constexpr (Traits::NUM_ARGS == 1) {
|
||||||
|
Invoke<func, false>(ctx, inst, std::make_index_sequence<0>{});
|
||||||
|
} else {
|
||||||
|
using FirstArgType = typename Traits::template ArgType<1>;
|
||||||
|
static constexpr bool is_first_arg_inst = std::is_same_v<FirstArgType, IR::Inst*>;
|
||||||
|
using Indices = std::make_index_sequence<Traits::NUM_ARGS - (is_first_arg_inst ? 2 : 1)>;
|
||||||
|
Invoke<func, is_first_arg_inst>(ctx, inst, Indices{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitInst(EmitContext& ctx, IR::Inst* inst) {
|
||||||
|
switch (inst->GetOpcode()) {
|
||||||
|
#define OPCODE(name, result_type, ...) \
|
||||||
|
case IR::Opcode::name: \
|
||||||
|
return Invoke<&Emit##name>(ctx, inst);
|
||||||
|
#include "shader_recompiler/ir/opcodes.inc"
|
||||||
|
#undef OPCODE
|
||||||
|
}
|
||||||
|
throw LogicError("Invalid opcode {}", inst->GetOpcode());
|
||||||
|
}
|
||||||
|
|
||||||
|
Id TypeId(const EmitContext& ctx, IR::Type type) {
|
||||||
|
switch (type) {
|
||||||
|
case IR::Type::U1:
|
||||||
|
return ctx.U1[1];
|
||||||
|
case IR::Type::U32:
|
||||||
|
return ctx.U32[1];
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Phi node type {}", type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Traverse(EmitContext& ctx, IR::Program& program) {
|
||||||
|
IR::Block* current_block{};
|
||||||
|
for (const IR::AbstractSyntaxNode& node : program.syntax_list) {
|
||||||
|
switch (node.type) {
|
||||||
|
case IR::AbstractSyntaxNode::Type::Block: {
|
||||||
|
const Id label{node.data.block->Definition<Id>()};
|
||||||
|
if (current_block) {
|
||||||
|
ctx.OpBranch(label);
|
||||||
|
}
|
||||||
|
current_block = node.data.block;
|
||||||
|
ctx.AddLabel(label);
|
||||||
|
for (IR::Inst& inst : node.data.block->Instructions()) {
|
||||||
|
EmitInst(ctx, &inst);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::AbstractSyntaxNode::Type::If: {
|
||||||
|
const Id if_label{node.data.if_node.body->Definition<Id>()};
|
||||||
|
const Id endif_label{node.data.if_node.merge->Definition<Id>()};
|
||||||
|
ctx.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone);
|
||||||
|
ctx.OpBranchConditional(ctx.Def(node.data.if_node.cond), if_label, endif_label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::AbstractSyntaxNode::Type::Loop: {
|
||||||
|
const Id body_label{node.data.loop.body->Definition<Id>()};
|
||||||
|
const Id continue_label{node.data.loop.continue_block->Definition<Id>()};
|
||||||
|
const Id endloop_label{node.data.loop.merge->Definition<Id>()};
|
||||||
|
|
||||||
|
ctx.OpLoopMerge(endloop_label, continue_label, spv::LoopControlMask::MaskNone);
|
||||||
|
ctx.OpBranch(body_label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::AbstractSyntaxNode::Type::Break: {
|
||||||
|
const Id break_label{node.data.break_node.merge->Definition<Id>()};
|
||||||
|
const Id skip_label{node.data.break_node.skip->Definition<Id>()};
|
||||||
|
ctx.OpBranchConditional(ctx.Def(node.data.break_node.cond), break_label, skip_label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::AbstractSyntaxNode::Type::EndIf:
|
||||||
|
if (current_block) {
|
||||||
|
ctx.OpBranch(node.data.end_if.merge->Definition<Id>());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IR::AbstractSyntaxNode::Type::Repeat: {
|
||||||
|
Id cond{ctx.Def(node.data.repeat.cond)};
|
||||||
|
const Id loop_header_label{node.data.repeat.loop_header->Definition<Id>()};
|
||||||
|
const Id merge_label{node.data.repeat.merge->Definition<Id>()};
|
||||||
|
ctx.OpBranchConditional(cond, loop_header_label, merge_label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::AbstractSyntaxNode::Type::Return:
|
||||||
|
ctx.OpReturn();
|
||||||
|
break;
|
||||||
|
case IR::AbstractSyntaxNode::Type::Unreachable:
|
||||||
|
ctx.OpUnreachable();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (node.type != IR::AbstractSyntaxNode::Type::Block) {
|
||||||
|
current_block = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id DefineMain(EmitContext& ctx, IR::Program& program) {
|
||||||
|
const Id void_function{ctx.TypeFunction(ctx.void_id)};
|
||||||
|
const Id main{ctx.OpFunction(ctx.void_id, spv::FunctionControlMask::MaskNone, void_function)};
|
||||||
|
for (IR::Block* const block : program.blocks) {
|
||||||
|
block->SetDefinition(ctx.OpLabel());
|
||||||
|
}
|
||||||
|
Traverse(ctx, program);
|
||||||
|
ctx.OpFunctionEnd();
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
|
||||||
|
const std::span interfaces(ctx.interfaces.data(), ctx.interfaces.size());
|
||||||
|
spv::ExecutionModel execution_model{};
|
||||||
|
switch (program.stage) {
|
||||||
|
case Stage::Compute: {
|
||||||
|
// const std::array<u32, 3> workgroup_size{program.workgroup_size};
|
||||||
|
// execution_model = spv::ExecutionModel::GLCompute;
|
||||||
|
// ctx.AddExecutionMode(main, spv::ExecutionMode::LocalSize, workgroup_size[0],
|
||||||
|
// workgroup_size[1], workgroup_size[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Stage::Vertex:
|
||||||
|
execution_model = spv::ExecutionModel::Vertex;
|
||||||
|
break;
|
||||||
|
case Stage::Fragment:
|
||||||
|
execution_model = spv::ExecutionModel::Fragment;
|
||||||
|
if (ctx.profile.lower_left_origin_mode) {
|
||||||
|
ctx.AddExecutionMode(main, spv::ExecutionMode::OriginLowerLeft);
|
||||||
|
} else {
|
||||||
|
ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft);
|
||||||
|
}
|
||||||
|
// if (program.info.stores_frag_depth) {
|
||||||
|
// ctx.AddExecutionMode(main, spv::ExecutionMode::DepthReplacing);
|
||||||
|
// }
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Stage {}", u32(program.stage));
|
||||||
|
}
|
||||||
|
ctx.AddEntryPoint(execution_model, main, "main", interfaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
|
||||||
|
auto inst{program.blocks.front()->begin()};
|
||||||
|
size_t block_index{0};
|
||||||
|
ctx.PatchDeferredPhi([&](size_t phi_arg) {
|
||||||
|
if (phi_arg == 0) {
|
||||||
|
++inst;
|
||||||
|
if (inst == program.blocks[block_index]->end() ||
|
||||||
|
inst->GetOpcode() != IR::Opcode::Phi) {
|
||||||
|
do {
|
||||||
|
++block_index;
|
||||||
|
inst = program.blocks[block_index]->begin();
|
||||||
|
} while (inst->GetOpcode() != IR::Opcode::Phi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.Def(inst->Arg(phi_arg));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
std::vector<u32> EmitSPIRV(const Profile& profile, IR::Program& program, Bindings& bindings) {
|
||||||
|
EmitContext ctx{profile, program, bindings};
|
||||||
|
const Id main{DefineMain(ctx, program)};
|
||||||
|
DefineEntryPoint(program, ctx, main);
|
||||||
|
if (program.stage == Stage::Vertex) {
|
||||||
|
ctx.AddExtension("SPV_KHR_shader_draw_parameters");
|
||||||
|
ctx.AddCapability(spv::Capability::DrawParameters);
|
||||||
|
}
|
||||||
|
PatchPhiNodes(program, ctx);
|
||||||
|
return ctx.Assemble();
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitPhi(EmitContext& ctx, IR::Inst* inst) {
|
||||||
|
const size_t num_args{inst->NumArgs()};
|
||||||
|
boost::container::small_vector<Id, 32> blocks;
|
||||||
|
blocks.reserve(num_args);
|
||||||
|
for (size_t index = 0; index < num_args; ++index) {
|
||||||
|
blocks.push_back(inst->PhiBlock(index)->Definition<Id>());
|
||||||
|
}
|
||||||
|
// The type of a phi instruction is stored in its flags
|
||||||
|
const Id result_type{TypeId(ctx, inst->Flags<IR::Type>())};
|
||||||
|
return ctx.DeferredOpPhi(result_type, std::span(blocks.data(), blocks.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitVoid(EmitContext&) {}
|
||||||
|
|
||||||
|
Id EmitIdentity(EmitContext& ctx, const IR::Value& value) {
|
||||||
|
throw NotImplementedException("Forward identity declaration");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConditionRef(EmitContext& ctx, const IR::Value& value) {
|
||||||
|
throw NotImplementedException("Forward identity declaration");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitReference(EmitContext&) {}
|
||||||
|
|
||||||
|
void EmitPhiMove(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitGetZeroFromOp(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitGetSignFromOp(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitGetCarryFromOp(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitGetOverflowFromOp(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitSetVcc(EmitContext& ctx) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitGetVcc(EmitContext& ctx) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,21 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "shader_recompiler/backend/bindings.h"
|
||||||
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
#include "shader_recompiler/profile.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, IR::Program& program,
|
||||||
|
Bindings& bindings);
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<u32> EmitSPIRV(const Profile& profile, IR::Program& program) {
|
||||||
|
Bindings binding;
|
||||||
|
return EmitSPIRV(profile, program, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,57 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
void EmitBitCastU16F16(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitCastU32F32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitcast(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitBitCastU64F64(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitBitCastF16U16(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitCastF32U32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitcast(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitBitCastF64U64(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitPackUint2x32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitcast(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUnpackUint2x32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitcast(ctx.U32[2], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitPackFloat2x16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitcast(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUnpackFloat2x16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitcast(ctx.F16[2], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitPackHalf2x16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpPackHalf2x16(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUnpackHalf2x16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpUnpackHalf2x16(ctx.F32[2], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,153 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
Id EmitCompositeConstructU32x2(EmitContext& ctx, Id e1, Id e2) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.U32[2], e1, e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructU32x3(EmitContext& ctx, Id e1, Id e2, Id e3) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.U32[3], e1, e2, e3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructU32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.U32[4], e1, e2, e3, e4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractU32x2(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.U32[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractU32x3(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.U32[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractU32x4(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.U32[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertU32x2(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.U32[2], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertU32x3(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.U32[3], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertU32x4(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.U32[4], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructF16x2(EmitContext& ctx, Id e1, Id e2) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.F16[2], e1, e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructF16x3(EmitContext& ctx, Id e1, Id e2, Id e3) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.F16[3], e1, e2, e3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructF16x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.F16[4], e1, e2, e3, e4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractF16x2(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.F16[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractF16x3(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.F16[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractF16x4(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.F16[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF16x2(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F16[2], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF16x3(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F16[3], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF16x4(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F16[4], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructF32x2(EmitContext& ctx, Id e1, Id e2) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.F32[2], e1, e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructF32x3(EmitContext& ctx, Id e1, Id e2, Id e3) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.F32[3], e1, e2, e3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeConstructF32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4) {
|
||||||
|
return ctx.OpCompositeConstruct(ctx.F32[4], e1, e2, e3, e4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractF32x2(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.F32[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractF32x3(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.F32[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeExtractF32x4(EmitContext& ctx, Id composite, u32 index) {
|
||||||
|
return ctx.OpCompositeExtract(ctx.F32[1], composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF32x2(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F32[2], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF32x3(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F32[3], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF32x4(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F32[4], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitCompositeConstructF64x2(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitCompositeConstructF64x3(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitCompositeConstructF64x4(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitCompositeExtractF64x2(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitCompositeExtractF64x3(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitCompositeExtractF64x4(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF64x2(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F64[2], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF64x3(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F64[3], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitCompositeInsertF64x4(EmitContext& ctx, Id composite, Id object, u32 index) {
|
||||||
|
return ctx.OpCompositeInsert(ctx.F64[4], object, composite, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,103 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) {
|
||||||
|
if (IR::IsParam(attr)) {
|
||||||
|
const u32 index{u32(attr) - u32(IR::Attribute::Param0)};
|
||||||
|
const auto& info{ctx.output_params.at(index).at(element)};
|
||||||
|
if (info.num_components == 1) {
|
||||||
|
return info.id;
|
||||||
|
} else {
|
||||||
|
const u32 index_element{element - info.first_element};
|
||||||
|
return ctx.OpAccessChain(ctx.output_f32, info.id, ctx.ConstU32(index_element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (attr) {
|
||||||
|
case IR::Attribute::Position0: {
|
||||||
|
return ctx.OpAccessChain(ctx.output_f32, ctx.output_position, ctx.ConstU32(element));
|
||||||
|
case IR::Attribute::RenderTarget0:
|
||||||
|
return ctx.OpAccessChain(ctx.output_f32, ctx.frag_color[0], ctx.ConstU32(element));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Read attribute {}", attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
void EmitGetScalarRegister(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitSetScalarRegister(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitGetVectorRegister(EmitContext& ctx) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitSetVectorRegister(EmitContext& ctx) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitSetGotoVariable(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitGetGotoVariable(EmitContext&) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitReadConst(EmitContext& ctx) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitReadConstBuffer(EmitContext& ctx, const IR::Value& binding, const IR::Value& addr,
|
||||||
|
const IR::Value& offset) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitReadConstBufferF32(EmitContext& ctx, const IR::Value& binding, const IR::Value& addr,
|
||||||
|
const IR::Value& offset) {
|
||||||
|
throw LogicError("Unreachable instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
|
||||||
|
const u32 element{static_cast<u32>(attr) % 4};
|
||||||
|
if (IR::IsParam(attr)) {
|
||||||
|
const u32 index{u32(attr) - u32(IR::Attribute::Param0)};
|
||||||
|
const auto& param{ctx.input_params.at(index)};
|
||||||
|
if (!ValidId(param.id)) {
|
||||||
|
// Attribute is disabled or varying component is not written
|
||||||
|
return ctx.ConstF32(element == 3 ? 1.0f : 0.0f);
|
||||||
|
}
|
||||||
|
const Id pointer{ctx.OpAccessChain(param.pointer_type, param.id, ctx.ConstU32(element))};
|
||||||
|
return ctx.OpLoad(param.component_type, pointer);
|
||||||
|
}
|
||||||
|
throw NotImplementedException("Read attribute {}", attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, Id) {
|
||||||
|
switch (attr) {
|
||||||
|
case IR::Attribute::VertexId:
|
||||||
|
return ctx.OpLoad(ctx.U32[1], ctx.vertex_index);
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Read U32 attribute {}", attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 element) {
|
||||||
|
if (attr == IR::Attribute::Param0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Id pointer{OutputAttrPointer(ctx, attr, element)};
|
||||||
|
ctx.OpStore(pointer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,262 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
namespace {
|
||||||
|
Id ExtractU16(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpUConvert(ctx.U16, value);
|
||||||
|
} else {
|
||||||
|
return ctx.OpBitFieldUExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(16u));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id ExtractS16(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpSConvert(ctx.S16, value);
|
||||||
|
} else {
|
||||||
|
return ctx.OpBitFieldSExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(16u));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id ExtractU8(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int8) {
|
||||||
|
return ctx.OpUConvert(ctx.U8, value);
|
||||||
|
} else {
|
||||||
|
return ctx.OpBitFieldUExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(8u));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id ExtractS8(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int8) {
|
||||||
|
return ctx.OpSConvert(ctx.S8, value);
|
||||||
|
} else {
|
||||||
|
return ctx.OpBitFieldSExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(8u));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
Id EmitConvertS16F16(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
|
||||||
|
} else {
|
||||||
|
return ExtractS16(ctx, ctx.OpConvertFToS(ctx.U32[1], value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS16F32(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
|
||||||
|
} else {
|
||||||
|
return ExtractS16(ctx, ctx.OpConvertFToS(ctx.U32[1], value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS16F64(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
|
||||||
|
} else {
|
||||||
|
return ExtractS16(ctx, ctx.OpConvertFToS(ctx.U32[1], value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS32F16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToS(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS32F32(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.has_broken_signed_operations) {
|
||||||
|
return ctx.OpBitcast(ctx.U32[1], ctx.OpConvertFToS(ctx.S32[1], value));
|
||||||
|
} else {
|
||||||
|
return ctx.OpConvertFToS(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS32F64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToS(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS64F16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToS(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS64F32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToS(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertS64F64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToS(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU16F16(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
|
||||||
|
} else {
|
||||||
|
return ExtractU16(ctx, ctx.OpConvertFToU(ctx.U32[1], value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU16F32(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
|
||||||
|
} else {
|
||||||
|
return ExtractU16(ctx, ctx.OpConvertFToU(ctx.U32[1], value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU16F64(EmitContext& ctx, Id value) {
|
||||||
|
if (ctx.profile.support_int16) {
|
||||||
|
return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
|
||||||
|
} else {
|
||||||
|
return ExtractU16(ctx, ctx.OpConvertFToU(ctx.U32[1], value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU32F16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToU(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU32F32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToU(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU32F64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToU(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU64F16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToU(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU64F32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToU(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU64F64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertFToU(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU64U32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpUConvert(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertU32U64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpUConvert(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16F32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFConvert(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32F16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFConvert(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32F64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFConvert(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64F32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFConvert(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16S8(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F16[1], ExtractS8(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16S16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F16[1], ExtractS16(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16S32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16S64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16U8(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F16[1], ExtractU8(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16U16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F16[1], ExtractU16(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16U32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF16U64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32S8(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F32[1], ExtractS8(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32S16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F32[1], ExtractS16(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32S32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32S64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32U8(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F32[1], ExtractU8(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32U16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F32[1], ExtractU16(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32U32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF32U64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64S8(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F64[1], ExtractS8(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64S16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F64[1], ExtractS16(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64S32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64S64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertSToF(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64U8(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F64[1], ExtractU8(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64U16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F64[1], ExtractU16(ctx, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64U32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitConvertF64U64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpConvertUToF(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,355 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
Id EmitFPAbs16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFAbs(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPAbs32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFAbs(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPAbs64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFAbs(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPAdd16(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
return ctx.OpFAdd(ctx.F16[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPAdd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
return ctx.OpFAdd(ctx.F32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPAdd64(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
return ctx.OpFAdd(ctx.F64[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPFma16(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) {
|
||||||
|
return ctx.OpFma(ctx.F16[1], a, b, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPFma32(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) {
|
||||||
|
return ctx.OpFma(ctx.F32[1], a, b, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPFma64(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) {
|
||||||
|
return ctx.OpFma(ctx.F64[1], a, b, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPMax32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpFMax(ctx.F32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPMax64(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpFMax(ctx.F64[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPMin32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpFMin(ctx.F32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPMin64(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpFMin(ctx.F64[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPMul16(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
return ctx.OpFMul(ctx.F16[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPMul32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
return ctx.OpFMul(ctx.F32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPMul64(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
return ctx.OpFMul(ctx.F64[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPNeg16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFNegate(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPNeg32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFNegate(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPNeg64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFNegate(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPSin(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpSin(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPCos(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpCos(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPExp2(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpExp2(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPLog2(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpLog2(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPRecip32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFDiv(ctx.F32[1], ctx.ConstF32(1.0f), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPRecip64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFDiv(ctx.F64[1], ctx.Constant(ctx.F64[1], 1.0f), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPRecipSqrt32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpInverseSqrt(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPRecipSqrt64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpInverseSqrt(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPSqrt(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpSqrt(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPSaturate16(EmitContext& ctx, Id value) {
|
||||||
|
const Id zero{ctx.Constant(ctx.F16[1], u16{0})};
|
||||||
|
const Id one{ctx.Constant(ctx.F16[1], u16{0x3c00})};
|
||||||
|
return ctx.OpFClamp(ctx.F16[1], value, zero, one);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPSaturate32(EmitContext& ctx, Id value) {
|
||||||
|
const Id zero{ctx.ConstF32(f32{0.0})};
|
||||||
|
const Id one{ctx.ConstF32(f32{1.0})};
|
||||||
|
return ctx.OpFClamp(ctx.F32[1], value, zero, one);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPSaturate64(EmitContext& ctx, Id value) {
|
||||||
|
const Id zero{ctx.Constant(ctx.F64[1], f64{0.0})};
|
||||||
|
const Id one{ctx.Constant(ctx.F64[1], f64{1.0})};
|
||||||
|
return ctx.OpFClamp(ctx.F64[1], value, zero, one);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPClamp16(EmitContext& ctx, Id value, Id min_value, Id max_value) {
|
||||||
|
return ctx.OpFClamp(ctx.F16[1], value, min_value, max_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPClamp32(EmitContext& ctx, Id value, Id min_value, Id max_value) {
|
||||||
|
return ctx.OpFClamp(ctx.F32[1], value, min_value, max_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPClamp64(EmitContext& ctx, Id value, Id min_value, Id max_value) {
|
||||||
|
return ctx.OpFClamp(ctx.F64[1], value, min_value, max_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPRoundEven16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpRoundEven(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPRoundEven32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpRoundEven(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPRoundEven64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpRoundEven(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPFloor16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFloor(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPFloor32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFloor(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPFloor64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFloor(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPCeil16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpCeil(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPCeil32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpCeil(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPCeil64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpCeil(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPTrunc16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpTrunc(ctx.F16[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPTrunc32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpTrunc(ctx.F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPTrunc64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpTrunc(ctx.F64[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdNotEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdNotEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdNotEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdNotEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdNotEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdNotEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordNotEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordNotEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordNotEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordNotEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordNotEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordNotEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdLessThan16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdLessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdLessThan32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdLessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdLessThan64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdLessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordLessThan16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordLessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordLessThan32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordLessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordLessThan64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordLessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdGreaterThan16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdGreaterThan32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdGreaterThan64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordGreaterThan16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordGreaterThan32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordGreaterThan64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdLessThanEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdLessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdLessThanEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdLessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdLessThanEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdLessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordLessThanEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordLessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordLessThanEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordLessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordLessThanEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordLessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdGreaterThanEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPOrdGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFOrdGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordGreaterThanEqual16(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPUnordGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpFUnordGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPIsNan16(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpIsNan(ctx.U1[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPIsNan32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpIsNan(ctx.U1[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFPIsNan64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpIsNan(ctx.U1[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,66 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
Id bias_lc, const IR::Value& offset) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
Id lod, const IR::Value& offset) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
|
||||||
|
Id coords, Id dref, Id bias_lc, const IR::Value& offset) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
|
||||||
|
Id coords, Id dref, Id lod, const IR::Value& offset) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
const IR::Value& offset, const IR::Value& offset2) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
const IR::Value& offset, const IR::Value& offset2, Id dref) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
|
||||||
|
Id lod, Id ms) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
|
||||||
|
const IR::Value& skip_mips_val) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
Id derivatives, const IR::Value& offset, Id lod_clamp) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id color) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,335 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sirit/sirit.h>
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
enum class Attribute : u64;
|
||||||
|
enum class Patch : u64;
|
||||||
|
class Inst;
|
||||||
|
class Value;
|
||||||
|
} // namespace Shader::IR
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
using Sirit::Id;
|
||||||
|
|
||||||
|
class EmitContext;
|
||||||
|
|
||||||
|
// Microinstruction emitters
|
||||||
|
Id EmitPhi(EmitContext& ctx, IR::Inst* inst);
|
||||||
|
void EmitVoid(EmitContext& ctx);
|
||||||
|
Id EmitIdentity(EmitContext& ctx, const IR::Value& value);
|
||||||
|
Id EmitConditionRef(EmitContext& ctx, const IR::Value& value);
|
||||||
|
void EmitReference(EmitContext&);
|
||||||
|
void EmitPhiMove(EmitContext&);
|
||||||
|
void EmitJoin(EmitContext& ctx);
|
||||||
|
void EmitBarrier(EmitContext& ctx);
|
||||||
|
void EmitWorkgroupMemoryBarrier(EmitContext& ctx);
|
||||||
|
void EmitDeviceMemoryBarrier(EmitContext& ctx);
|
||||||
|
void EmitGetVcc(EmitContext& ctx);
|
||||||
|
void EmitSetVcc(EmitContext& ctx);
|
||||||
|
void EmitPrologue(EmitContext& ctx);
|
||||||
|
void EmitEpilogue(EmitContext& ctx);
|
||||||
|
void EmitGetScalarRegister(EmitContext& ctx);
|
||||||
|
void EmitSetScalarRegister(EmitContext& ctx);
|
||||||
|
void EmitGetVectorRegister(EmitContext& ctx);
|
||||||
|
void EmitSetVectorRegister(EmitContext& ctx);
|
||||||
|
void EmitSetGotoVariable(EmitContext& ctx);
|
||||||
|
void EmitGetGotoVariable(EmitContext& ctx);
|
||||||
|
void EmitSetScc(EmitContext& ctx);
|
||||||
|
Id EmitReadConst(EmitContext& ctx);
|
||||||
|
Id EmitReadConstBuffer(EmitContext& ctx, const IR::Value& handle, const IR::Value& index,
|
||||||
|
const IR::Value& offset);
|
||||||
|
Id EmitReadConstBufferF32(EmitContext& ctx, const IR::Value& handle, const IR::Value& index,
|
||||||
|
const IR::Value& offset);
|
||||||
|
Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex);
|
||||||
|
Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, Id vertex);
|
||||||
|
void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 element);
|
||||||
|
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value);
|
||||||
|
void EmitSetSampleMask(EmitContext& ctx, Id value);
|
||||||
|
void EmitSetFragDepth(EmitContext& ctx, Id value);
|
||||||
|
Id EmitWorkgroupId(EmitContext& ctx);
|
||||||
|
Id EmitLocalInvocationId(EmitContext& ctx);
|
||||||
|
Id EmitInvocationId(EmitContext& ctx);
|
||||||
|
Id EmitInvocationInfo(EmitContext& ctx);
|
||||||
|
Id EmitSampleId(EmitContext& ctx);
|
||||||
|
Id EmitUndefU1(EmitContext& ctx);
|
||||||
|
Id EmitUndefU8(EmitContext& ctx);
|
||||||
|
Id EmitUndefU16(EmitContext& ctx);
|
||||||
|
Id EmitUndefU32(EmitContext& ctx);
|
||||||
|
Id EmitUndefU64(EmitContext& ctx);
|
||||||
|
Id EmitReadSharedU8(EmitContext& ctx, Id offset);
|
||||||
|
Id EmitReadSharedS8(EmitContext& ctx, Id offset);
|
||||||
|
Id EmitReadSharedU16(EmitContext& ctx, Id offset);
|
||||||
|
Id EmitReadSharedS16(EmitContext& ctx, Id offset);
|
||||||
|
Id EmitReadSharedU32(EmitContext& ctx, Id offset);
|
||||||
|
Id EmitReadSharedU64(EmitContext& ctx, Id offset);
|
||||||
|
void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value);
|
||||||
|
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value);
|
||||||
|
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value);
|
||||||
|
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value);
|
||||||
|
void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value);
|
||||||
|
Id EmitCompositeConstructU32x2(EmitContext& ctx, Id e1, Id e2);
|
||||||
|
Id EmitCompositeConstructU32x3(EmitContext& ctx, Id e1, Id e2, Id e3);
|
||||||
|
Id EmitCompositeConstructU32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4);
|
||||||
|
Id EmitCompositeExtractU32x2(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeExtractU32x3(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeExtractU32x4(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeInsertU32x2(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertU32x3(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertU32x4(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeConstructF16x2(EmitContext& ctx, Id e1, Id e2);
|
||||||
|
Id EmitCompositeConstructF16x3(EmitContext& ctx, Id e1, Id e2, Id e3);
|
||||||
|
Id EmitCompositeConstructF16x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4);
|
||||||
|
Id EmitCompositeExtractF16x2(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeExtractF16x3(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeExtractF16x4(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeInsertF16x2(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertF16x3(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertF16x4(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeConstructF32x2(EmitContext& ctx, Id e1, Id e2);
|
||||||
|
Id EmitCompositeConstructF32x3(EmitContext& ctx, Id e1, Id e2, Id e3);
|
||||||
|
Id EmitCompositeConstructF32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4);
|
||||||
|
Id EmitCompositeExtractF32x2(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeExtractF32x3(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeExtractF32x4(EmitContext& ctx, Id composite, u32 index);
|
||||||
|
Id EmitCompositeInsertF32x2(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertF32x3(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertF32x4(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
void EmitCompositeConstructF64x2(EmitContext& ctx);
|
||||||
|
void EmitCompositeConstructF64x3(EmitContext& ctx);
|
||||||
|
void EmitCompositeConstructF64x4(EmitContext& ctx);
|
||||||
|
void EmitCompositeExtractF64x2(EmitContext& ctx);
|
||||||
|
void EmitCompositeExtractF64x3(EmitContext& ctx);
|
||||||
|
void EmitCompositeExtractF64x4(EmitContext& ctx);
|
||||||
|
Id EmitCompositeInsertF64x2(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertF64x3(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitCompositeInsertF64x4(EmitContext& ctx, Id composite, Id object, u32 index);
|
||||||
|
Id EmitSelectU1(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
Id EmitSelectU8(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
Id EmitSelectU16(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
Id EmitSelectU32(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
Id EmitSelectU64(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
Id EmitSelectF16(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
Id EmitSelectF32(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value);
|
||||||
|
void EmitBitCastU16F16(EmitContext& ctx);
|
||||||
|
Id EmitBitCastU32F32(EmitContext& ctx, Id value);
|
||||||
|
void EmitBitCastU64F64(EmitContext& ctx);
|
||||||
|
void EmitBitCastF16U16(EmitContext&);
|
||||||
|
Id EmitBitCastF32U32(EmitContext& ctx, Id value);
|
||||||
|
void EmitBitCastF64U64(EmitContext& ctx);
|
||||||
|
Id EmitPackUint2x32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitUnpackUint2x32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitPackFloat2x16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitUnpackFloat2x16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitPackHalf2x16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitUnpackHalf2x16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPAbs16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPAbs32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPAbs64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPAdd16(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitFPAdd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitFPAdd64(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitFPFma16(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c);
|
||||||
|
Id EmitFPFma32(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c);
|
||||||
|
Id EmitFPFma64(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c);
|
||||||
|
Id EmitFPMax32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitFPMax64(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitFPMin32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitFPMin64(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitFPMul16(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitFPMul32(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitFPMul64(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitFPNeg16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPNeg32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPNeg64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPSin(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPCos(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPExp2(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPLog2(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPRecip32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPRecip64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPRecipSqrt32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPRecipSqrt64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPSqrt(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPSaturate16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPSaturate32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPSaturate64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPClamp16(EmitContext& ctx, Id value, Id min_value, Id max_value);
|
||||||
|
Id EmitFPClamp32(EmitContext& ctx, Id value, Id min_value, Id max_value);
|
||||||
|
Id EmitFPClamp64(EmitContext& ctx, Id value, Id min_value, Id max_value);
|
||||||
|
Id EmitFPRoundEven16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPRoundEven32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPRoundEven64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPFloor16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPFloor32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPFloor64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPCeil16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPCeil32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPCeil64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPTrunc16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPTrunc32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPTrunc64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPOrdEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdNotEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdNotEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdNotEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordNotEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordNotEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordNotEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdLessThan16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdLessThan32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdLessThan64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordLessThan16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordLessThan32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordLessThan64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdGreaterThan16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdGreaterThan32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdGreaterThan64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordGreaterThan16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordGreaterThan32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordGreaterThan64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdLessThanEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdLessThanEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdLessThanEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordLessThanEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordLessThanEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordLessThanEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdGreaterThanEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPOrdGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordGreaterThanEqual16(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPUnordGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitFPIsNan16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPIsNan32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFPIsNan64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitIAdd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitIAdd64(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitISub32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitISub64(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitIMul32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitSDiv32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitUDiv32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitINeg32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitINeg64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitIAbs32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitShiftLeftLogical32(EmitContext& ctx, Id base, Id shift);
|
||||||
|
Id EmitShiftLeftLogical64(EmitContext& ctx, Id base, Id shift);
|
||||||
|
Id EmitShiftRightLogical32(EmitContext& ctx, Id base, Id shift);
|
||||||
|
Id EmitShiftRightLogical64(EmitContext& ctx, Id base, Id shift);
|
||||||
|
Id EmitShiftRightArithmetic32(EmitContext& ctx, Id base, Id shift);
|
||||||
|
Id EmitShiftRightArithmetic64(EmitContext& ctx, Id base, Id shift);
|
||||||
|
Id EmitBitwiseAnd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitBitwiseOr32(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitBitwiseXor32(EmitContext& ctx, IR::Inst* inst, Id a, Id b);
|
||||||
|
Id EmitBitFieldInsert(EmitContext& ctx, Id base, Id insert, Id offset, Id count);
|
||||||
|
Id EmitBitFieldSExtract(EmitContext& ctx, IR::Inst* inst, Id base, Id offset, Id count);
|
||||||
|
Id EmitBitFieldUExtract(EmitContext& ctx, IR::Inst* inst, Id base, Id offset, Id count);
|
||||||
|
Id EmitBitReverse32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitBitCount32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitBitwiseNot32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFindSMsb32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitFindUMsb32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitSMin32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitUMin32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitSMax32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitUMax32(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitSClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max);
|
||||||
|
Id EmitUClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max);
|
||||||
|
Id EmitSLessThan(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitULessThan(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitIEqual(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitSLessThanEqual(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitULessThanEqual(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitSGreaterThan(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitUGreaterThan(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitINotEqual(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitSGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitUGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs);
|
||||||
|
Id EmitLogicalOr(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitLogicalAnd(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitLogicalXor(EmitContext& ctx, Id a, Id b);
|
||||||
|
Id EmitLogicalNot(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS16F16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS16F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS16F64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS32F16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS32F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS32F64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS64F16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS64F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertS64F64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU16F16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU16F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU16F64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU32F16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU32F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU32F64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU64F16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU64F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU64F64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU64U32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertU32U64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32F16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32F64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64F32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16S8(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16S16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16S32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16S64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16U8(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16U16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16U32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF16U64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32S8(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32S16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32S32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32S64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32U8(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32U16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32U32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF32U64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64S8(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64S16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64S32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64S64(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64U8(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64U16(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64U32(EmitContext& ctx, Id value);
|
||||||
|
Id EmitConvertF64U64(EmitContext& ctx, Id value);
|
||||||
|
|
||||||
|
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
Id bias_lc, const IR::Value& offset);
|
||||||
|
Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
Id lod, const IR::Value& offset);
|
||||||
|
Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
|
||||||
|
Id coords, Id dref, Id bias_lc, const IR::Value& offset);
|
||||||
|
Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
|
||||||
|
Id coords, Id dref, Id lod, const IR::Value& offset);
|
||||||
|
Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
const IR::Value& offset, const IR::Value& offset2);
|
||||||
|
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
const IR::Value& offset, const IR::Value& offset2, Id dref);
|
||||||
|
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
|
||||||
|
Id lod, Id ms);
|
||||||
|
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
|
||||||
|
const IR::Value& skip_mips);
|
||||||
|
Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
|
||||||
|
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||||
|
Id derivatives, const IR::Value& offset, Id lod_clamp);
|
||||||
|
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
|
||||||
|
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id color);
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,262 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
namespace {
|
||||||
|
void SetZeroFlag(EmitContext& ctx, IR::Inst* inst, Id result) {
|
||||||
|
// IR::Inst* const zero{inst->GetAssociatedPseudoOperation(IR::Opcode::GetZeroFromOp)};
|
||||||
|
// if (!zero) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// zero->SetDefinition(ctx.OpIEqual(ctx.U1[1], result, ctx.u32_zero_value));
|
||||||
|
// zero->Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetSignFlag(EmitContext& ctx, IR::Inst* inst, Id result) {
|
||||||
|
// IR::Inst* const sign{inst->GetAssociatedPseudoOperation(IR::Opcode::GetSignFromOp)};
|
||||||
|
// if (!sign) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// sign->SetDefinition(ctx.OpSLessThan(ctx.U1[1], result, ctx.u32_zero_value));
|
||||||
|
// sign->Invalidate();
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
Id EmitIAdd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
return ctx.OpIAdd(ctx.U32[1], a, b);
|
||||||
|
// Id result{};
|
||||||
|
// if (IR::Inst* const carry{inst->GetAssociatedPseudoOperation(IR::Opcode::GetCarryFromOp)}) {
|
||||||
|
// const Id carry_type{ctx.TypeStruct(ctx.U32[1], ctx.U32[1])};
|
||||||
|
// const Id carry_result{ctx.OpIAddCarry(carry_type, a, b)};
|
||||||
|
// result = ctx.OpCompositeExtract(ctx.U32[1], carry_result, 0U);
|
||||||
|
|
||||||
|
// const Id carry_value{ctx.OpCompositeExtract(ctx.U32[1], carry_result, 1U)};
|
||||||
|
// carry->SetDefinition(ctx.OpINotEqual(ctx.U1[1][1], carry_value, ctx.u32_zero_value));
|
||||||
|
// carry->Invalidate();
|
||||||
|
//} else {
|
||||||
|
// result = ctx.OpIAdd(ctx.U32[1], a, b);
|
||||||
|
//}
|
||||||
|
// SetZeroFlag(ctx, inst, result);
|
||||||
|
// SetSignFlag(ctx, inst, result);
|
||||||
|
// if (IR::Inst * overflow{inst->GetAssociatedPseudoOperation(IR::Opcode::GetOverflowFromOp)}) {
|
||||||
|
// // https://stackoverflow.com/questions/55468823/how-to-detect-integer-overflow-in-c
|
||||||
|
// constexpr u32 s32_max{static_cast<u32>(std::numeric_limits<s32>::max())};
|
||||||
|
// const Id is_positive{ctx.OpSGreaterThanEqual(ctx.U1[1], a, ctx.u32_zero_value)};
|
||||||
|
// const Id sub_a{ctx.OpISub(ctx.U32[1], ctx.Const(s32_max), a)};
|
||||||
|
|
||||||
|
// const Id positive_test{ctx.OpSGreaterThan(ctx.U1[1], b, sub_a)};
|
||||||
|
// const Id negative_test{ctx.OpSLessThan(ctx.U1[1], b, sub_a)};
|
||||||
|
// const Id carry_flag{ctx.OpSelect(ctx.U1[1], is_positive, positive_test, negative_test)};
|
||||||
|
// overflow->SetDefinition(carry_flag);
|
||||||
|
// overflow->Invalidate();
|
||||||
|
//}
|
||||||
|
// return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitIAdd64(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpIAdd(ctx.U64, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitISub32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpISub(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitISub64(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpISub(ctx.U64, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitIMul32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpIMul(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSDiv32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpSDiv(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUDiv32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpUDiv(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitINeg32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpSNegate(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitINeg64(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpSNegate(ctx.U64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitIAbs32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpSAbs(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitShiftLeftLogical32(EmitContext& ctx, Id base, Id shift) {
|
||||||
|
return ctx.OpShiftLeftLogical(ctx.U32[1], base, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitShiftLeftLogical64(EmitContext& ctx, Id base, Id shift) {
|
||||||
|
return ctx.OpShiftLeftLogical(ctx.U64, base, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitShiftRightLogical32(EmitContext& ctx, Id base, Id shift) {
|
||||||
|
return ctx.OpShiftRightLogical(ctx.U32[1], base, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitShiftRightLogical64(EmitContext& ctx, Id base, Id shift) {
|
||||||
|
return ctx.OpShiftRightLogical(ctx.U64, base, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitShiftRightArithmetic32(EmitContext& ctx, Id base, Id shift) {
|
||||||
|
return ctx.OpShiftRightArithmetic(ctx.U32[1], base, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitShiftRightArithmetic64(EmitContext& ctx, Id base, Id shift) {
|
||||||
|
return ctx.OpShiftRightArithmetic(ctx.U64, base, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitwiseAnd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
const Id result{ctx.OpBitwiseAnd(ctx.U32[1], a, b)};
|
||||||
|
SetZeroFlag(ctx, inst, result);
|
||||||
|
SetSignFlag(ctx, inst, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitwiseOr32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
const Id result{ctx.OpBitwiseOr(ctx.U32[1], a, b)};
|
||||||
|
SetZeroFlag(ctx, inst, result);
|
||||||
|
SetSignFlag(ctx, inst, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitwiseXor32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||||
|
const Id result{ctx.OpBitwiseXor(ctx.U32[1], a, b)};
|
||||||
|
SetZeroFlag(ctx, inst, result);
|
||||||
|
SetSignFlag(ctx, inst, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitFieldInsert(EmitContext& ctx, Id base, Id insert, Id offset, Id count) {
|
||||||
|
return ctx.OpBitFieldInsert(ctx.U32[1], base, insert, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitFieldSExtract(EmitContext& ctx, IR::Inst* inst, Id base, Id offset, Id count) {
|
||||||
|
const Id result{ctx.OpBitFieldSExtract(ctx.U32[1], base, offset, count)};
|
||||||
|
SetZeroFlag(ctx, inst, result);
|
||||||
|
SetSignFlag(ctx, inst, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitFieldUExtract(EmitContext& ctx, IR::Inst* inst, Id base, Id offset, Id count) {
|
||||||
|
const Id result{ctx.OpBitFieldUExtract(ctx.U32[1], base, offset, count)};
|
||||||
|
SetZeroFlag(ctx, inst, result);
|
||||||
|
SetSignFlag(ctx, inst, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitReverse32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitReverse(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitCount32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpBitCount(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitBitwiseNot32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpNot(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFindSMsb32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFindSMsb(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitFindUMsb32(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpFindUMsb(ctx.U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSMin32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpSMin(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUMin32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpUMin(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSMax32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpSMax(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUMax32(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpUMax(ctx.U32[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max) {
|
||||||
|
Id result{};
|
||||||
|
if (ctx.profile.has_broken_spirv_clamp) {
|
||||||
|
value = ctx.OpBitcast(ctx.S32[1], value);
|
||||||
|
min = ctx.OpBitcast(ctx.S32[1], min);
|
||||||
|
max = ctx.OpBitcast(ctx.S32[1], max);
|
||||||
|
result = ctx.OpSMax(ctx.S32[1], ctx.OpSMin(ctx.S32[1], value, max), min);
|
||||||
|
result = ctx.OpBitcast(ctx.U32[1], result);
|
||||||
|
} else {
|
||||||
|
result = ctx.OpSClamp(ctx.U32[1], value, min, max);
|
||||||
|
}
|
||||||
|
SetZeroFlag(ctx, inst, result);
|
||||||
|
SetSignFlag(ctx, inst, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUClamp32(EmitContext& ctx, IR::Inst* inst, Id value, Id min, Id max) {
|
||||||
|
Id result{};
|
||||||
|
if (ctx.profile.has_broken_spirv_clamp) {
|
||||||
|
result = ctx.OpUMax(ctx.U32[1], ctx.OpUMin(ctx.U32[1], value, max), min);
|
||||||
|
} else {
|
||||||
|
result = ctx.OpUClamp(ctx.U32[1], value, min, max);
|
||||||
|
}
|
||||||
|
SetZeroFlag(ctx, inst, result);
|
||||||
|
SetSignFlag(ctx, inst, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSLessThan(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpSLessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitULessThan(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpULessThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitIEqual(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpIEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSLessThanEqual(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpSLessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitULessThanEqual(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpULessThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSGreaterThan(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpSGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUGreaterThan(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpUGreaterThan(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitINotEqual(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpINotEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpSGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs) {
|
||||||
|
return ctx.OpUGreaterThanEqual(ctx.U1[1], lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,25 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
Id EmitLogicalOr(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpLogicalOr(ctx.U1[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitLogicalAnd(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpLogicalAnd(ctx.U1[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitLogicalXor(EmitContext& ctx, Id a, Id b) {
|
||||||
|
return ctx.OpLogicalNotEqual(ctx.U1[1], a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitLogicalNot(EmitContext& ctx, Id value) {
|
||||||
|
return ctx.OpLogicalNot(ctx.U1[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,41 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
Id EmitSelectU1(EmitContext& ctx, Id cond, Id true_value, Id false_value) {
|
||||||
|
return ctx.OpSelect(ctx.U1[1], cond, true_value, false_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSelectU8(EmitContext&, Id, Id, Id) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSelectU16(EmitContext& ctx, Id cond, Id true_value, Id false_value) {
|
||||||
|
return ctx.OpSelect(ctx.U16, cond, true_value, false_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSelectU32(EmitContext& ctx, Id cond, Id true_value, Id false_value) {
|
||||||
|
return ctx.OpSelect(ctx.U32[1], cond, true_value, false_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSelectU64(EmitContext& ctx, Id cond, Id true_value, Id false_value) {
|
||||||
|
return ctx.OpSelect(ctx.U64, cond, true_value, false_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSelectF16(EmitContext& ctx, Id cond, Id true_value, Id false_value) {
|
||||||
|
return ctx.OpSelect(ctx.F16[1], cond, true_value, false_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSelectF32(EmitContext& ctx, Id cond, Id true_value, Id false_value) {
|
||||||
|
return ctx.OpSelect(ctx.F32[1], cond, true_value, false_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value) {
|
||||||
|
return ctx.OpSelect(ctx.F64[1], cond, true_value, false_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,21 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
void EmitPrologue(EmitContext& ctx) {}
|
||||||
|
|
||||||
|
void EmitEpilogue(EmitContext& ctx) {}
|
||||||
|
|
||||||
|
void EmitEmitVertex(EmitContext& ctx, const IR::Value& stream) {
|
||||||
|
throw NotImplementedException("Geometry streams");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitEndPrimitive(EmitContext& ctx, const IR::Value& stream) {
|
||||||
|
throw NotImplementedException("Geometry streams");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,29 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
Id EmitUndefU1(EmitContext& ctx) {
|
||||||
|
return ctx.OpUndef(ctx.U1[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUndefU8(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUndefU16(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUndefU32(EmitContext& ctx) {
|
||||||
|
return ctx.OpUndef(ctx.U32[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id EmitUndefU64(EmitContext&) {
|
||||||
|
throw NotImplementedException("SPIR-V Instruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,136 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <boost/container/static_vector.hpp>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string_view StageName(Stage stage) {
|
||||||
|
switch (stage) {
|
||||||
|
case Stage::Vertex:
|
||||||
|
return "vs";
|
||||||
|
case Stage::TessellationControl:
|
||||||
|
return "tcs";
|
||||||
|
case Stage::TessellationEval:
|
||||||
|
return "tes";
|
||||||
|
case Stage::Geometry:
|
||||||
|
return "gs";
|
||||||
|
case Stage::Fragment:
|
||||||
|
return "fs";
|
||||||
|
case Stage::Compute:
|
||||||
|
return "cs";
|
||||||
|
}
|
||||||
|
throw InvalidArgument("Invalid stage {}", u32(stage));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void Name(EmitContext& ctx, Id object, std::string_view format_str, Args&&... args) {
|
||||||
|
ctx.Name(object, fmt::format(fmt::runtime(format_str), StageName(ctx.stage),
|
||||||
|
std::forward<Args>(args)...)
|
||||||
|
.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
EmitContext::EmitContext(const Profile& profile_, IR::Program& program, Bindings& bindings)
|
||||||
|
: Sirit::Module(profile_.supported_spirv), profile{profile_}, stage{program.stage} {
|
||||||
|
u32& uniform_binding{bindings.unified};
|
||||||
|
u32& storage_binding{bindings.unified};
|
||||||
|
u32& texture_binding{bindings.unified};
|
||||||
|
u32& image_binding{bindings.unified};
|
||||||
|
AddCapability(spv::Capability::Shader);
|
||||||
|
DefineArithmeticTypes();
|
||||||
|
DefineInterfaces(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmitContext::~EmitContext() = default;
|
||||||
|
|
||||||
|
Id EmitContext::Def(const IR::Value& value) {
|
||||||
|
if (!value.IsImmediate()) {
|
||||||
|
return value.InstRecursive()->Definition<Id>();
|
||||||
|
}
|
||||||
|
switch (value.Type()) {
|
||||||
|
case IR::Type::Void:
|
||||||
|
return Id{};
|
||||||
|
case IR::Type::U1:
|
||||||
|
return value.U1() ? true_value : false_value;
|
||||||
|
case IR::Type::U32:
|
||||||
|
return ConstU32(value.U32());
|
||||||
|
case IR::Type::U64:
|
||||||
|
return Constant(U64, value.U64());
|
||||||
|
case IR::Type::F32:
|
||||||
|
return ConstF32(value.F32());
|
||||||
|
case IR::Type::F64:
|
||||||
|
return Constant(F64[1], value.F64());
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Immediate type {}", value.Type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitContext::DefineArithmeticTypes() {
|
||||||
|
void_id = Name(TypeVoid(), "void_id");
|
||||||
|
U1[1] = Name(TypeBool(), "bool_id");
|
||||||
|
// F16[1] = Name(TypeFloat(16), "f16_id");
|
||||||
|
F32[1] = Name(TypeFloat(32), "f32_id");
|
||||||
|
// F64[1] = Name(TypeFloat(64), "f64_id");
|
||||||
|
S32[1] = Name(TypeSInt(32), "i32_id");
|
||||||
|
U32[1] = Name(TypeUInt(32), "u32_id");
|
||||||
|
// U8 = Name(TypeSInt(8), "u8");
|
||||||
|
// S8 = Name(TypeUInt(8), "s8");
|
||||||
|
// U16 = Name(TypeUInt(16), "u16_id");
|
||||||
|
// S16 = Name(TypeSInt(16), "s16_id");
|
||||||
|
// U64 = Name(TypeUInt(64), "u64_id");
|
||||||
|
|
||||||
|
for (u32 i = 2; i <= 4; i++) {
|
||||||
|
// F16[i] = Name(TypeVector(F16[1], i), fmt::format("f16vec{}_id", i));
|
||||||
|
F32[i] = Name(TypeVector(F32[1], i), fmt::format("f32vec{}_id", i));
|
||||||
|
// F64[i] = Name(TypeVector(F64[1], i), fmt::format("f64vec{}_id", i));
|
||||||
|
S32[i] = Name(TypeVector(S32[1], i), fmt::format("i32vec{}_id", i));
|
||||||
|
U32[i] = Name(TypeVector(U32[1], i), fmt::format("u32vec{}_id", i));
|
||||||
|
U1[i] = Name(TypeVector(U1[1], i), fmt::format("bvec{}_id", i));
|
||||||
|
}
|
||||||
|
|
||||||
|
true_value = ConstantTrue(U1[1]);
|
||||||
|
false_value = ConstantFalse(U1[1]);
|
||||||
|
u32_zero_value = ConstU32(0U);
|
||||||
|
f32_zero_value = ConstF32(0.0f);
|
||||||
|
|
||||||
|
output_f32 = Name(TypePointer(spv::StorageClass::Output, F32[1]), "output_f32");
|
||||||
|
output_u32 = Name(TypePointer(spv::StorageClass::Output, U32[1]), "output_u32");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitContext::DefineInterfaces(const IR::Program& program) {
|
||||||
|
DefineInputs(program);
|
||||||
|
DefineOutputs(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitContext::DefineInputs(const IR::Program& program) {
|
||||||
|
switch (stage) {
|
||||||
|
case Stage::Vertex:
|
||||||
|
vertex_index = DefineVariable(U32[1], spv::BuiltIn::VertexIndex, spv::StorageClass::Input);
|
||||||
|
base_vertex = DefineVariable(U32[1], spv::BuiltIn::BaseVertex, spv::StorageClass::Input);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitContext::DefineOutputs(const IR::Program& program) {
|
||||||
|
switch (stage) {
|
||||||
|
case Stage::Vertex:
|
||||||
|
output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output);
|
||||||
|
break;
|
||||||
|
case Stage::Fragment:
|
||||||
|
frag_color[0] = DefineOutput(F32[4], 0);
|
||||||
|
Name(frag_color[0], fmt::format("frag_color{}", 0));
|
||||||
|
interfaces.push_back(frag_color[0]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,169 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <sirit/sirit.h>
|
||||||
|
|
||||||
|
#include "shader_recompiler/backend/bindings.h"
|
||||||
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
#include "shader_recompiler/profile.h"
|
||||||
|
#include "shader_recompiler/runtime_info.h"
|
||||||
|
|
||||||
|
namespace Shader::Backend::SPIRV {
|
||||||
|
|
||||||
|
using Sirit::Id;
|
||||||
|
|
||||||
|
struct VectorIds {
|
||||||
|
[[nodiscard]] Id& operator[](u32 index) {
|
||||||
|
return ids[index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const Id& operator[](u32 index) const {
|
||||||
|
return ids[index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<Id, 4> ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EmitContext final : public Sirit::Module {
|
||||||
|
public:
|
||||||
|
explicit EmitContext(const Profile& profile, IR::Program& program, Bindings& binding);
|
||||||
|
~EmitContext();
|
||||||
|
|
||||||
|
Id Def(const IR::Value& value);
|
||||||
|
|
||||||
|
[[nodiscard]] Id DefineInput(Id type, u32 location) {
|
||||||
|
const Id input_id{DefineVar(type, spv::StorageClass::Input)};
|
||||||
|
Decorate(input_id, spv::Decoration::Location, location);
|
||||||
|
return input_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Id DefineOutput(Id type, std::optional<u32> location = std::nullopt) {
|
||||||
|
const Id output_id{DefineVar(type, spv::StorageClass::Output)};
|
||||||
|
if (location) {
|
||||||
|
Decorate(output_id, spv::Decoration::Location, *location);
|
||||||
|
}
|
||||||
|
return output_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Id DefineUniformConst(Id type, u32 set, u32 binding, bool readonly = false) {
|
||||||
|
const Id uniform_id{DefineVar(type, spv::StorageClass::UniformConstant)};
|
||||||
|
Decorate(uniform_id, spv::Decoration::DescriptorSet, set);
|
||||||
|
Decorate(uniform_id, spv::Decoration::Binding, binding);
|
||||||
|
if (readonly) {
|
||||||
|
Decorate(uniform_id, spv::Decoration::NonWritable);
|
||||||
|
}
|
||||||
|
return uniform_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool global = true>
|
||||||
|
[[nodiscard]] Id DefineVar(Id type, spv::StorageClass storage_class) {
|
||||||
|
const Id pointer_type_id{TypePointer(storage_class, type)};
|
||||||
|
return global ? AddGlobalVariable(pointer_type_id, storage_class)
|
||||||
|
: AddLocalVariable(pointer_type_id, storage_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Id DefineVariable(Id type, std::optional<spv::BuiltIn> builtin,
|
||||||
|
spv::StorageClass storage_class) {
|
||||||
|
const Id id{DefineVar(type, storage_class)};
|
||||||
|
if (builtin) {
|
||||||
|
Decorate(id, spv::Decoration::BuiltIn, *builtin);
|
||||||
|
}
|
||||||
|
interfaces.push_back(id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Id ConstU32(u32 value) {
|
||||||
|
return Constant(U32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] Id ConstU32(Args&&... values) {
|
||||||
|
constexpr u32 size = static_cast<u32>(sizeof...(values));
|
||||||
|
static_assert(size >= 2);
|
||||||
|
const std::array constituents{Constant(U32[1], values)...};
|
||||||
|
const Id type = size <= 4 ? U32[size] : TypeArray(U32[1], ConstU32(size));
|
||||||
|
return ConstantComposite(type, constituents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Id ConstS32(s32 value) {
|
||||||
|
return Constant(S32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] Id ConstS32(Args&&... values) {
|
||||||
|
constexpr u32 size = static_cast<u32>(sizeof...(values));
|
||||||
|
static_assert(size >= 2);
|
||||||
|
const std::array constituents{Constant(S32[1], values)...};
|
||||||
|
const Id type = size <= 4 ? S32[size] : TypeArray(S32[1], ConstU32(size));
|
||||||
|
return ConstantComposite(type, constituents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Id ConstF32(f32 value) {
|
||||||
|
return Constant(F32[1], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] Id ConstF32(Args... values) {
|
||||||
|
constexpr u32 size = static_cast<u32>(sizeof...(values));
|
||||||
|
static_assert(size >= 2);
|
||||||
|
const std::array constituents{Constant(F32[1], values)...};
|
||||||
|
const Id type = size <= 4 ? F32[size] : TypeArray(F32[1], ConstU32(size));
|
||||||
|
return ConstantComposite(type, constituents);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Profile& profile;
|
||||||
|
Stage stage{};
|
||||||
|
|
||||||
|
Id void_id{};
|
||||||
|
Id U8{};
|
||||||
|
Id S8{};
|
||||||
|
Id U16{};
|
||||||
|
Id S16{};
|
||||||
|
Id U64{};
|
||||||
|
VectorIds F16{};
|
||||||
|
VectorIds F32{};
|
||||||
|
VectorIds F64{};
|
||||||
|
VectorIds S32{};
|
||||||
|
VectorIds U32{};
|
||||||
|
VectorIds U1{};
|
||||||
|
|
||||||
|
Id true_value{};
|
||||||
|
Id false_value{};
|
||||||
|
Id u32_zero_value{};
|
||||||
|
Id f32_zero_value{};
|
||||||
|
|
||||||
|
Id output_u32{};
|
||||||
|
Id output_f32{};
|
||||||
|
|
||||||
|
boost::container::small_vector<Id, 16> interfaces;
|
||||||
|
|
||||||
|
Id output_position{};
|
||||||
|
Id vertex_index{};
|
||||||
|
Id base_vertex{};
|
||||||
|
std::array<Id, 8> frag_color{};
|
||||||
|
|
||||||
|
struct InputParamInfo {
|
||||||
|
Id id;
|
||||||
|
Id pointer_type;
|
||||||
|
Id component_type;
|
||||||
|
};
|
||||||
|
std::array<InputParamInfo, 32> input_params{};
|
||||||
|
|
||||||
|
struct ParamElementInfo {
|
||||||
|
Id id{};
|
||||||
|
u32 first_element{};
|
||||||
|
u32 num_components{};
|
||||||
|
};
|
||||||
|
std::array<std::array<ParamElementInfo, 4>, 32> output_params{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DefineArithmeticTypes();
|
||||||
|
void DefineInterfaces(const IR::Program& program);
|
||||||
|
void DefineInputs(const IR::Program& program);
|
||||||
|
void DefineOutputs(const IR::Program& program);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader::Backend::SPIRV
|
|
@ -0,0 +1,64 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
|
||||||
|
class Exception : public std::exception {
|
||||||
|
public:
|
||||||
|
explicit Exception(std::string message) noexcept : err_message{std::move(message)} {}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* what() const noexcept override {
|
||||||
|
return err_message.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Prepend(std::string_view prepend) {
|
||||||
|
err_message.insert(0, prepend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Append(std::string_view append) {
|
||||||
|
err_message += append;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string err_message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LogicError : public Exception {
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
explicit LogicError(const char* message, Args&&... args)
|
||||||
|
: Exception{fmt::format(fmt::runtime(message), std::forward<Args>(args)...)} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RuntimeError : public Exception {
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
explicit RuntimeError(const char* message, Args&&... args)
|
||||||
|
: Exception{fmt::format(fmt::runtime(message), std::forward<Args>(args)...)} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class NotImplementedException : public Exception {
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
explicit NotImplementedException(const char* message, Args&&... args)
|
||||||
|
: Exception{fmt::format(fmt::runtime(message), std::forward<Args>(args)...)} {
|
||||||
|
Append(" is not implemented");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InvalidArgument : public Exception {
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
explicit InvalidArgument(const char* message, Args&&... args)
|
||||||
|
: Exception{fmt::format(fmt::runtime(message), std::forward<Args>(args)...)} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader
|
|
@ -0,0 +1,209 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "shader_recompiler/frontend/control_flow_graph.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
struct Compare {
|
||||||
|
bool operator()(const Block& lhs, u32 rhs) const noexcept {
|
||||||
|
return lhs.begin < rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(u32 lhs, const Block& rhs) const noexcept {
|
||||||
|
return lhs < rhs.begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const Block& lhs, const Block& rhs) const noexcept {
|
||||||
|
return lhs.begin < rhs.begin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static IR::Condition MakeCondition(Opcode opcode) {
|
||||||
|
switch (opcode) {
|
||||||
|
case Opcode::S_CBRANCH_SCC0:
|
||||||
|
return IR::Condition::Scc0;
|
||||||
|
case Opcode::S_CBRANCH_SCC1:
|
||||||
|
return IR::Condition::Scc1;
|
||||||
|
case Opcode::S_CBRANCH_VCCZ:
|
||||||
|
return IR::Condition::Vccz;
|
||||||
|
case Opcode::S_CBRANCH_VCCNZ:
|
||||||
|
return IR::Condition::Vccnz;
|
||||||
|
case Opcode::S_CBRANCH_EXECZ:
|
||||||
|
return IR::Condition::Execz;
|
||||||
|
case Opcode::S_CBRANCH_EXECNZ:
|
||||||
|
return IR::Condition::Execnz;
|
||||||
|
default:
|
||||||
|
return IR::Condition::True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CFG::CFG(ObjectPool<Block>& block_pool_, std::span<const GcnInst> inst_list_)
|
||||||
|
: block_pool{block_pool_}, inst_list{inst_list_} {
|
||||||
|
index_to_pc.resize(inst_list.size());
|
||||||
|
EmitLabels();
|
||||||
|
EmitBlocks();
|
||||||
|
LinkBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFG::EmitLabels() {
|
||||||
|
// Always set a label at entry point.
|
||||||
|
u32 pc = 0;
|
||||||
|
labels.push_back(pc);
|
||||||
|
|
||||||
|
const auto add_label = [this](u32 address) {
|
||||||
|
const auto it = std::ranges::find(labels, address);
|
||||||
|
if (it == labels.end()) {
|
||||||
|
labels.push_back(address);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate instruction list and add labels to branch targets.
|
||||||
|
for (u32 i = 0; i < inst_list.size(); i++) {
|
||||||
|
index_to_pc[i] = pc;
|
||||||
|
const GcnInst inst = inst_list[i];
|
||||||
|
if (inst.IsUnconditionalBranch()) {
|
||||||
|
const u32 target = inst.BranchTarget(pc);
|
||||||
|
add_label(target);
|
||||||
|
} else if (inst.IsConditionalBranch()) {
|
||||||
|
const u32 true_label = inst.BranchTarget(pc);
|
||||||
|
const u32 false_label = pc + inst.length;
|
||||||
|
add_label(true_label);
|
||||||
|
add_label(false_label);
|
||||||
|
} else if (inst.opcode == Opcode::S_ENDPGM) {
|
||||||
|
const u32 next_label = pc + inst.length;
|
||||||
|
add_label(next_label);
|
||||||
|
}
|
||||||
|
pc += inst.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort labels to make sure block insertion is correct.
|
||||||
|
std::ranges::sort(labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFG::EmitBlocks() {
|
||||||
|
const auto get_index = [this](Label label) -> size_t {
|
||||||
|
if (label == 0) {
|
||||||
|
return 0ULL;
|
||||||
|
}
|
||||||
|
const auto it_index = std::ranges::lower_bound(index_to_pc, label);
|
||||||
|
ASSERT(it_index != index_to_pc.end() || label > index_to_pc.back());
|
||||||
|
return std::distance(index_to_pc.begin(), std::prev(it_index));
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto it = labels.begin(); it != labels.end(); it++) {
|
||||||
|
const Label start = *it;
|
||||||
|
const auto next_it = std::next(it);
|
||||||
|
const bool is_last = next_it == labels.end();
|
||||||
|
if (is_last) {
|
||||||
|
// Last label is special.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Label end = *next_it;
|
||||||
|
const size_t end_index = get_index(end);
|
||||||
|
const auto& end_inst = inst_list[end_index];
|
||||||
|
|
||||||
|
// Insert block between the labels using the last instruction
|
||||||
|
// as an indicator for branching type.
|
||||||
|
Block* block = block_pool.Create();
|
||||||
|
block->begin = start;
|
||||||
|
block->end = end;
|
||||||
|
block->begin_index = get_index(start);
|
||||||
|
block->end_index = end_index;
|
||||||
|
block->end_inst = end_inst;
|
||||||
|
block->cond = MakeCondition(end_inst.opcode);
|
||||||
|
blocks.insert(*block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFG::LinkBlocks() {
|
||||||
|
const auto get_block = [this](u32 address) {
|
||||||
|
const auto it = blocks.find(address, Compare{});
|
||||||
|
ASSERT_MSG(it != blocks.end() && it->begin == address);
|
||||||
|
return &*it;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& block : blocks) {
|
||||||
|
const auto end_inst{block.end_inst};
|
||||||
|
// If the block doesn't end with a branch we simply
|
||||||
|
// need to link with the next block.
|
||||||
|
if (!end_inst.IsTerminateInstruction()) {
|
||||||
|
block.branch_true = get_block(block.end);
|
||||||
|
block.end_class = EndClass::Branch;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the branch targets from the instruction and link the blocks.
|
||||||
|
// Note: Block end address is one instruction after end_inst.
|
||||||
|
const u32 branch_pc = block.end - end_inst.length;
|
||||||
|
const u32 target_pc = end_inst.BranchTarget(branch_pc);
|
||||||
|
if (end_inst.IsUnconditionalBranch()) {
|
||||||
|
block.branch_true = get_block(target_pc);
|
||||||
|
block.end_class = EndClass::Branch;
|
||||||
|
} else if (end_inst.IsConditionalBranch()) {
|
||||||
|
block.branch_true = get_block(target_pc);
|
||||||
|
block.branch_false = get_block(block.end);
|
||||||
|
block.end_class = EndClass::Branch;
|
||||||
|
} else {
|
||||||
|
// Exit blocks don't link to anything.
|
||||||
|
block.end_class = EndClass::Exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CFG::Dot() const {
|
||||||
|
int node_uid{0};
|
||||||
|
|
||||||
|
const auto name_of = [](const Block& block) { return fmt::format("\"{:#x}\"", block.begin); };
|
||||||
|
|
||||||
|
std::string dot{"digraph shader {\n"};
|
||||||
|
dot += fmt::format("\tsubgraph cluster_{} {{\n", 0);
|
||||||
|
dot += fmt::format("\t\tnode [style=filled];\n");
|
||||||
|
for (const Block& block : blocks) {
|
||||||
|
const std::string name{name_of(block)};
|
||||||
|
const auto add_branch = [&](Block* branch, bool add_label) {
|
||||||
|
dot += fmt::format("\t\t{}->{}", name, name_of(*branch));
|
||||||
|
if (add_label && block.cond != IR::Condition::True &&
|
||||||
|
block.cond != IR::Condition::False) {
|
||||||
|
dot += fmt::format(" [label=\"{}\"]", block.cond);
|
||||||
|
}
|
||||||
|
dot += '\n';
|
||||||
|
};
|
||||||
|
dot += fmt::format("\t\t{};\n", name);
|
||||||
|
switch (block.end_class) {
|
||||||
|
case EndClass::Branch:
|
||||||
|
if (block.cond != IR::Condition::False) {
|
||||||
|
add_branch(block.branch_true, true);
|
||||||
|
}
|
||||||
|
if (block.cond != IR::Condition::True) {
|
||||||
|
add_branch(block.branch_false, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EndClass::Exit:
|
||||||
|
dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
|
||||||
|
dot +=
|
||||||
|
fmt::format("\t\tN{} [label=\"Exit\"][shape=square][style=stripped];\n", node_uid);
|
||||||
|
++node_uid;
|
||||||
|
break;
|
||||||
|
// case EndClass::Kill:
|
||||||
|
// dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
|
||||||
|
// dot += fmt::format("\t\tN{} [label=\"Kill\"][shape=square][style=stripped];\n",
|
||||||
|
// node_uid);
|
||||||
|
// ++node_uid;
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dot += "\t\tlabel = \"main\";\n\t}\n";
|
||||||
|
if (blocks.empty()) {
|
||||||
|
dot += "Start;\n";
|
||||||
|
} else {
|
||||||
|
dot += fmt::format("\tStart -> {};\n", name_of(*blocks.begin()));
|
||||||
|
}
|
||||||
|
dot += fmt::format("\tStart [shape=diamond];\n");
|
||||||
|
dot += "}\n";
|
||||||
|
return dot;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,66 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
#include <boost/intrusive/set.hpp>
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "shader_recompiler/frontend/instruction.h"
|
||||||
|
#include "shader_recompiler/ir/condition.h"
|
||||||
|
#include "shader_recompiler/object_pool.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
using Hook =
|
||||||
|
boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>;
|
||||||
|
|
||||||
|
enum class EndClass {
|
||||||
|
Branch, ///< Block ends with a (un)conditional branch.
|
||||||
|
Exit, ///< Block ends with an exit instruction.
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A block represents a linear range of instructions.
|
||||||
|
struct Block : Hook {
|
||||||
|
[[nodiscard]] bool Contains(u32 pc) const noexcept;
|
||||||
|
|
||||||
|
bool operator<(const Block& rhs) const noexcept {
|
||||||
|
return begin < rhs.begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 begin;
|
||||||
|
u32 end;
|
||||||
|
u32 begin_index;
|
||||||
|
u32 end_index;
|
||||||
|
IR::Condition cond{};
|
||||||
|
GcnInst end_inst{};
|
||||||
|
EndClass end_class{};
|
||||||
|
Block* branch_true{};
|
||||||
|
Block* branch_false{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CFG {
|
||||||
|
using Label = u32;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CFG(ObjectPool<Block>& block_pool, std::span<const GcnInst> inst_list);
|
||||||
|
|
||||||
|
[[nodiscard]] std::string Dot() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void EmitLabels();
|
||||||
|
void EmitBlocks();
|
||||||
|
void LinkBlocks();
|
||||||
|
|
||||||
|
public:
|
||||||
|
ObjectPool<Block>& block_pool;
|
||||||
|
std::span<const GcnInst> inst_list;
|
||||||
|
std::vector<u32> index_to_pc;
|
||||||
|
boost::container::small_vector<Label, 16> labels;
|
||||||
|
boost::intrusive::set<Block> blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,97 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/instruction.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
struct InstFormat {
|
||||||
|
InstClass inst_class = InstClass::Undefined;
|
||||||
|
InstCategory inst_category = InstCategory::Undefined;
|
||||||
|
u32 src_count = 0;
|
||||||
|
u32 dst_count = 0;
|
||||||
|
ScalarType src_type = ScalarType::Undefined;
|
||||||
|
ScalarType dst_type = ScalarType::Undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
InstEncoding GetInstructionEncoding(u32 token);
|
||||||
|
|
||||||
|
u32 GetEncodingLength(InstEncoding encoding);
|
||||||
|
|
||||||
|
InstFormat InstructionFormat(InstEncoding encoding, u32 opcode);
|
||||||
|
|
||||||
|
Opcode DecodeOpcode(u32 token);
|
||||||
|
|
||||||
|
class GcnCodeSlice {
|
||||||
|
public:
|
||||||
|
GcnCodeSlice(const u32* ptr, const u32* end) : m_ptr(ptr), m_end(end) {}
|
||||||
|
GcnCodeSlice(const GcnCodeSlice& other) = default;
|
||||||
|
~GcnCodeSlice() = default;
|
||||||
|
|
||||||
|
u32 at(u32 id) const {
|
||||||
|
return m_ptr[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 readu32() {
|
||||||
|
return *(m_ptr++);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 readu64() {
|
||||||
|
const u64 value = *(u64*)m_ptr;
|
||||||
|
m_ptr += 2;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool atEnd() const {
|
||||||
|
return m_ptr == m_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const u32* m_ptr{};
|
||||||
|
const u32* m_end{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class GcnDecodeContext {
|
||||||
|
public:
|
||||||
|
GcnInst decodeInstruction(GcnCodeSlice& code);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t getEncodingLength(InstEncoding encoding);
|
||||||
|
uint32_t getOpMapOffset(InstEncoding encoding);
|
||||||
|
uint32_t mapEncodingOp(InstEncoding encoding, Opcode opcode);
|
||||||
|
void updateInstructionMeta(InstEncoding encoding);
|
||||||
|
uint32_t getMimgModifier(Opcode opcode);
|
||||||
|
void repairOperandType();
|
||||||
|
|
||||||
|
OperandField getOperandField(uint32_t code);
|
||||||
|
|
||||||
|
void decodeInstruction32(InstEncoding encoding, GcnCodeSlice& code);
|
||||||
|
void decodeInstruction64(InstEncoding encoding, GcnCodeSlice& code);
|
||||||
|
void decodeLiteralConstant(InstEncoding encoding, GcnCodeSlice& code);
|
||||||
|
|
||||||
|
// 32 bits encodings
|
||||||
|
void decodeInstructionSOP1(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionSOPP(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionSOPC(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionSOPK(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionSOP2(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionVOP1(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionVOPC(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionVOP2(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionSMRD(uint32_t hexInstruction);
|
||||||
|
void decodeInstructionVINTRP(uint32_t hexInstruction);
|
||||||
|
// 64 bits encodings
|
||||||
|
void decodeInstructionVOP3(uint64_t hexInstruction);
|
||||||
|
void decodeInstructionMUBUF(uint64_t hexInstruction);
|
||||||
|
void decodeInstructionMTBUF(uint64_t hexInstruction);
|
||||||
|
void decodeInstructionMIMG(uint64_t hexInstruction);
|
||||||
|
void decodeInstructionDS(uint64_t hexInstruction);
|
||||||
|
void decodeInstructionEXP(uint64_t hexInstruction);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GcnInst m_instruction;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,50 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "shader_recompiler/frontend/instruction.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
u32 GcnInst::BranchTarget(u32 pc) const {
|
||||||
|
const s16 simm = static_cast<s16>(control.sopp.simm * 4);
|
||||||
|
const u32 target = pc + simm + 4;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GcnInst::IsTerminateInstruction() const {
|
||||||
|
return IsUnconditionalBranch() || IsConditionalBranch() || IsFork() ||
|
||||||
|
opcode == Opcode::S_ENDPGM;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GcnInst::IsUnconditionalBranch() const {
|
||||||
|
return opcode == Opcode::S_BRANCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GcnInst::IsFork() const {
|
||||||
|
return opcode == Opcode::S_CBRANCH_I_FORK || opcode == Opcode::S_CBRANCH_G_FORK ||
|
||||||
|
opcode == Opcode::S_CBRANCH_JOIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GcnInst::IsConditionalBranch() const {
|
||||||
|
switch (opcode) {
|
||||||
|
case Opcode::S_CBRANCH_SCC0:
|
||||||
|
case Opcode::S_CBRANCH_SCC1:
|
||||||
|
case Opcode::S_CBRANCH_VCCZ:
|
||||||
|
case Opcode::S_CBRANCH_VCCNZ:
|
||||||
|
case Opcode::S_CBRANCH_EXECZ:
|
||||||
|
case Opcode::S_CBRANCH_EXECNZ:
|
||||||
|
return true;
|
||||||
|
case Opcode::S_CBRANCH_CDBGSYS:
|
||||||
|
case Opcode::S_CBRANCH_CDBGUSER:
|
||||||
|
case Opcode::S_CBRANCH_CDBGSYS_OR_USER:
|
||||||
|
case Opcode::S_CBRANCH_CDBGSYS_AND_USER:
|
||||||
|
UNIMPLEMENTED();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,208 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "shader_recompiler/frontend/opcodes.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
constexpr u32 GcnMaxSrcCount = 4;
|
||||||
|
constexpr u32 GcnMaxDstCount = 2;
|
||||||
|
|
||||||
|
enum OperandFieldRange {
|
||||||
|
ScalarGPRMin = 0,
|
||||||
|
ScalarGPRMax = 103,
|
||||||
|
SignedConstIntPosMin = 129,
|
||||||
|
SignedConstIntPosMax = 192,
|
||||||
|
SignedConstIntNegMin = 193,
|
||||||
|
SignedConstIntNegMax = 208,
|
||||||
|
ConstFloatMin = 240,
|
||||||
|
VectorGPRMin = 256,
|
||||||
|
VectorGPRMax = 511
|
||||||
|
};
|
||||||
|
|
||||||
|
/// These are applied after loading an operand register.
|
||||||
|
struct InputModifiers {
|
||||||
|
bool neg = false;
|
||||||
|
bool abs = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// These are applied before storing an operand register.
|
||||||
|
struct OutputModifiers {
|
||||||
|
bool clamp = false;
|
||||||
|
float multiplier = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstOperand {
|
||||||
|
OperandField field = OperandField::Undefined;
|
||||||
|
ScalarType type = ScalarType::Undefined;
|
||||||
|
InputModifiers input_modifier = {};
|
||||||
|
OutputModifiers output_modifier = {};
|
||||||
|
u32 code = 0xFFFFFFFF;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Operand {
|
||||||
|
OperandField field = OperandField::Undefined;
|
||||||
|
ScalarType type = ScalarType::Undefined;
|
||||||
|
union {
|
||||||
|
InputModifiers input_modifier = {};
|
||||||
|
OutputModifiers output_modifier;
|
||||||
|
};
|
||||||
|
u32 code = 0xFFFFFFFF;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstSOPK {
|
||||||
|
u16 simm;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstSOPP {
|
||||||
|
u16 simm;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstVOP3 {
|
||||||
|
Operand vdst;
|
||||||
|
Operand src0;
|
||||||
|
Operand src1;
|
||||||
|
Operand src2;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SMRD {
|
||||||
|
u8 offset;
|
||||||
|
bool imm;
|
||||||
|
u8 sbase;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlSOPK {
|
||||||
|
BitField<0, 16, u32> simm;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlSOPP {
|
||||||
|
BitField<0, 16, u32> simm;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlVOP3 {
|
||||||
|
u64 : 8;
|
||||||
|
u64 abs : 3;
|
||||||
|
u64 clmp : 1;
|
||||||
|
u64 : 47;
|
||||||
|
u64 omod : 2;
|
||||||
|
u64 neg : 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlSMRD {
|
||||||
|
u32 offset : 8;
|
||||||
|
u32 imm : 1;
|
||||||
|
u32 count : 5;
|
||||||
|
u32 : 18;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlMUBUF {
|
||||||
|
u64 offset : 12;
|
||||||
|
u64 offen : 1;
|
||||||
|
u64 idxen : 1;
|
||||||
|
u64 glc : 1;
|
||||||
|
u64 : 1;
|
||||||
|
u64 lds : 1;
|
||||||
|
u64 : 37;
|
||||||
|
u64 slc : 1;
|
||||||
|
u64 tfe : 1;
|
||||||
|
u64 count : 3;
|
||||||
|
u64 size : 5;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlMTBUF {
|
||||||
|
u64 offset : 12;
|
||||||
|
u64 offen : 1;
|
||||||
|
u64 idxen : 1;
|
||||||
|
u64 glc : 1;
|
||||||
|
u64 : 4;
|
||||||
|
u64 dfmt : 4;
|
||||||
|
u64 nfmt : 3;
|
||||||
|
u64 : 28;
|
||||||
|
u64 slc : 1;
|
||||||
|
u64 tfe : 1;
|
||||||
|
u64 count : 3;
|
||||||
|
u64 size : 5;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlMIMG {
|
||||||
|
u64 : 8;
|
||||||
|
u64 dmask : 4;
|
||||||
|
u64 unrm : 1;
|
||||||
|
u64 glc : 1;
|
||||||
|
u64 da : 1;
|
||||||
|
u64 r128 : 1;
|
||||||
|
u64 tfe : 1;
|
||||||
|
u64 lwe : 1;
|
||||||
|
u64 : 7;
|
||||||
|
u64 slc : 1;
|
||||||
|
u64 mod : 32;
|
||||||
|
u64 : 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlDS {
|
||||||
|
u64 offset0 : 8;
|
||||||
|
u64 offset1 : 8;
|
||||||
|
u64 : 1;
|
||||||
|
u64 gds : 1;
|
||||||
|
u64 dual : 1;
|
||||||
|
u64 sign : 1;
|
||||||
|
u64 relative : 1;
|
||||||
|
u64 stride : 1;
|
||||||
|
u64 size : 4;
|
||||||
|
u64 : 38;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlVINTRP {
|
||||||
|
u32 : 8;
|
||||||
|
u32 chan : 2;
|
||||||
|
u32 attr : 6;
|
||||||
|
u32 : 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstControlEXP {
|
||||||
|
u64 en : 4;
|
||||||
|
u64 target : 6;
|
||||||
|
u64 compr : 1;
|
||||||
|
u64 done : 1;
|
||||||
|
u64 vm : 1;
|
||||||
|
u64 reserved : 51;
|
||||||
|
};
|
||||||
|
|
||||||
|
union InstControl {
|
||||||
|
InstControlSOPK sopk;
|
||||||
|
InstControlSOPP sopp;
|
||||||
|
InstControlVOP3 vop3;
|
||||||
|
InstControlSMRD smrd;
|
||||||
|
InstControlMUBUF mubuf;
|
||||||
|
InstControlMTBUF mtbuf;
|
||||||
|
InstControlMIMG mimg;
|
||||||
|
InstControlDS ds;
|
||||||
|
InstControlVINTRP vintrp;
|
||||||
|
InstControlEXP exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GcnInst {
|
||||||
|
Opcode opcode;
|
||||||
|
InstEncoding encoding;
|
||||||
|
InstClass inst_class;
|
||||||
|
InstCategory category;
|
||||||
|
InstControl control;
|
||||||
|
u32 length;
|
||||||
|
u32 src_count;
|
||||||
|
u32 dst_count;
|
||||||
|
std::array<InstOperand, GcnMaxSrcCount> src;
|
||||||
|
std::array<InstOperand, GcnMaxDstCount> dst;
|
||||||
|
|
||||||
|
u32 BranchTarget(u32 pc) const;
|
||||||
|
|
||||||
|
bool IsTerminateInstruction() const;
|
||||||
|
bool IsUnconditionalBranch() const;
|
||||||
|
bool IsConditionalBranch() const;
|
||||||
|
bool IsFork() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,10 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Translate();
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,829 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <boost/intrusive/list.hpp>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "shader_recompiler/frontend/structured_control_flow.h"
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
#include "shader_recompiler/ir/ir_emitter.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Statement;
|
||||||
|
|
||||||
|
// Use normal_link because we are not guaranteed to destroy the tree in order
|
||||||
|
using ListBaseHook =
|
||||||
|
boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>;
|
||||||
|
|
||||||
|
using Tree = boost::intrusive::list<Statement,
|
||||||
|
// Allow using Statement without a definition
|
||||||
|
boost::intrusive::base_hook<ListBaseHook>,
|
||||||
|
// Avoid linear complexity on splice, size is never called
|
||||||
|
boost::intrusive::constant_time_size<false>>;
|
||||||
|
using Node = Tree::iterator;
|
||||||
|
|
||||||
|
enum class StatementType {
|
||||||
|
Code,
|
||||||
|
Goto,
|
||||||
|
Label,
|
||||||
|
If,
|
||||||
|
Loop,
|
||||||
|
Break,
|
||||||
|
Return,
|
||||||
|
Kill,
|
||||||
|
Unreachable,
|
||||||
|
Function,
|
||||||
|
Identity,
|
||||||
|
Not,
|
||||||
|
Or,
|
||||||
|
SetVariable,
|
||||||
|
Variable,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool HasChildren(StatementType type) {
|
||||||
|
switch (type) {
|
||||||
|
case StatementType::If:
|
||||||
|
case StatementType::Loop:
|
||||||
|
case StatementType::Function:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Goto {};
|
||||||
|
struct Label {};
|
||||||
|
struct If {};
|
||||||
|
struct Loop {};
|
||||||
|
struct Break {};
|
||||||
|
struct Return {};
|
||||||
|
struct Kill {};
|
||||||
|
struct Unreachable {};
|
||||||
|
struct FunctionTag {};
|
||||||
|
struct Identity {};
|
||||||
|
struct Not {};
|
||||||
|
struct Or {};
|
||||||
|
struct SetVariable {};
|
||||||
|
struct Variable {};
|
||||||
|
|
||||||
|
struct Statement : ListBaseHook {
|
||||||
|
Statement(const Block* block_, Statement* up_)
|
||||||
|
: block{block_}, up{up_}, type{StatementType::Code} {}
|
||||||
|
Statement(Goto, Statement* cond_, Node label_, Statement* up_)
|
||||||
|
: label{label_}, cond{cond_}, up{up_}, type{StatementType::Goto} {}
|
||||||
|
Statement(Label, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Label} {}
|
||||||
|
Statement(If, Statement* cond_, Tree&& children_, Statement* up_)
|
||||||
|
: children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::If} {}
|
||||||
|
Statement(Loop, Statement* cond_, Tree&& children_, Statement* up_)
|
||||||
|
: children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::Loop} {}
|
||||||
|
Statement(Break, Statement* cond_, Statement* up_)
|
||||||
|
: cond{cond_}, up{up_}, type{StatementType::Break} {}
|
||||||
|
Statement(Return, Statement* up_) : up{up_}, type{StatementType::Return} {}
|
||||||
|
Statement(Kill, Statement* up_) : up{up_}, type{StatementType::Kill} {}
|
||||||
|
Statement(Unreachable, Statement* up_) : up{up_}, type{StatementType::Unreachable} {}
|
||||||
|
Statement(FunctionTag) : children{}, type{StatementType::Function} {}
|
||||||
|
Statement(Identity, IR::Condition cond_, Statement* up_)
|
||||||
|
: guest_cond{cond_}, up{up_}, type{StatementType::Identity} {}
|
||||||
|
Statement(Not, Statement* op_, Statement* up_) : op{op_}, up{up_}, type{StatementType::Not} {}
|
||||||
|
Statement(Or, Statement* op_a_, Statement* op_b_, Statement* up_)
|
||||||
|
: op_a{op_a_}, op_b{op_b_}, up{up_}, type{StatementType::Or} {}
|
||||||
|
Statement(SetVariable, u32 id_, Statement* op_, Statement* up_)
|
||||||
|
: op{op_}, id{id_}, up{up_}, type{StatementType::SetVariable} {}
|
||||||
|
Statement(Variable, u32 id_, Statement* up_)
|
||||||
|
: id{id_}, up{up_}, type{StatementType::Variable} {}
|
||||||
|
|
||||||
|
~Statement() {
|
||||||
|
if (HasChildren(type)) {
|
||||||
|
std::destroy_at(&children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
union {
|
||||||
|
const Block* block;
|
||||||
|
Node label;
|
||||||
|
Tree children;
|
||||||
|
IR::Condition guest_cond;
|
||||||
|
Statement* op;
|
||||||
|
Statement* op_a;
|
||||||
|
u32 location;
|
||||||
|
s32 branch_offset;
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
Statement* cond;
|
||||||
|
Statement* op_b;
|
||||||
|
u32 id;
|
||||||
|
};
|
||||||
|
Statement* up{};
|
||||||
|
StatementType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string DumpExpr(const Statement* stmt) {
|
||||||
|
switch (stmt->type) {
|
||||||
|
case StatementType::Identity:
|
||||||
|
return fmt::format("{}", stmt->guest_cond);
|
||||||
|
case StatementType::Not:
|
||||||
|
return fmt::format("!{}", DumpExpr(stmt->op));
|
||||||
|
case StatementType::Or:
|
||||||
|
return fmt::format("{} || {}", DumpExpr(stmt->op_a), DumpExpr(stmt->op_b));
|
||||||
|
case StatementType::Variable:
|
||||||
|
return fmt::format("goto_L{}", stmt->id);
|
||||||
|
default:
|
||||||
|
return "<invalid type>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] std::string DumpTree(const Tree& tree, u32 indentation = 0) {
|
||||||
|
std::string ret;
|
||||||
|
std::string indent(indentation, ' ');
|
||||||
|
for (auto stmt = tree.begin(); stmt != tree.end(); ++stmt) {
|
||||||
|
switch (stmt->type) {
|
||||||
|
case StatementType::Code:
|
||||||
|
ret += fmt::format("{} Block {:04x} -> {:04x} (0x{:016x});\n", indent,
|
||||||
|
stmt->block->begin, stmt->block->end,
|
||||||
|
reinterpret_cast<uintptr_t>(stmt->block));
|
||||||
|
break;
|
||||||
|
case StatementType::Goto:
|
||||||
|
ret += fmt::format("{} if ({}) goto L{};\n", indent, DumpExpr(stmt->cond),
|
||||||
|
stmt->label->id);
|
||||||
|
break;
|
||||||
|
case StatementType::Label:
|
||||||
|
ret += fmt::format("{}L{}:\n", indent, stmt->id);
|
||||||
|
break;
|
||||||
|
case StatementType::If:
|
||||||
|
ret += fmt::format("{} if ({}) {{\n", indent, DumpExpr(stmt->cond));
|
||||||
|
ret += DumpTree(stmt->children, indentation + 4);
|
||||||
|
ret += fmt::format("{} }}\n", indent);
|
||||||
|
break;
|
||||||
|
case StatementType::Loop:
|
||||||
|
ret += fmt::format("{} do {{\n", indent);
|
||||||
|
ret += DumpTree(stmt->children, indentation + 4);
|
||||||
|
ret += fmt::format("{} }} while ({});\n", indent, DumpExpr(stmt->cond));
|
||||||
|
break;
|
||||||
|
case StatementType::Break:
|
||||||
|
ret += fmt::format("{} if ({}) break;\n", indent, DumpExpr(stmt->cond));
|
||||||
|
break;
|
||||||
|
case StatementType::Return:
|
||||||
|
ret += fmt::format("{} return;\n", indent);
|
||||||
|
break;
|
||||||
|
case StatementType::Kill:
|
||||||
|
ret += fmt::format("{} kill;\n", indent);
|
||||||
|
break;
|
||||||
|
case StatementType::Unreachable:
|
||||||
|
ret += fmt::format("{} unreachable;\n", indent);
|
||||||
|
break;
|
||||||
|
case StatementType::SetVariable:
|
||||||
|
ret += fmt::format("{} goto_L{} = {};\n", indent, stmt->id, DumpExpr(stmt->op));
|
||||||
|
break;
|
||||||
|
case StatementType::Function:
|
||||||
|
case StatementType::Identity:
|
||||||
|
case StatementType::Not:
|
||||||
|
case StatementType::Or:
|
||||||
|
case StatementType::Variable:
|
||||||
|
throw LogicError("Statement can't be printed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SanitizeNoBreaks(const Tree& tree) {
|
||||||
|
if (std::ranges::find(tree, StatementType::Break, &Statement::type) != tree.end()) {
|
||||||
|
throw NotImplementedException("Capturing statement with break nodes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Level(Node stmt) {
|
||||||
|
size_t level{0};
|
||||||
|
Statement* node{stmt->up};
|
||||||
|
while (node) {
|
||||||
|
++level;
|
||||||
|
node = node->up;
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDirectlyRelated(Node goto_stmt, Node label_stmt) {
|
||||||
|
const size_t goto_level{Level(goto_stmt)};
|
||||||
|
const size_t label_level{Level(label_stmt)};
|
||||||
|
size_t min_level;
|
||||||
|
size_t max_level;
|
||||||
|
Node min;
|
||||||
|
Node max;
|
||||||
|
if (label_level < goto_level) {
|
||||||
|
min_level = label_level;
|
||||||
|
max_level = goto_level;
|
||||||
|
min = label_stmt;
|
||||||
|
max = goto_stmt;
|
||||||
|
} else { // goto_level < label_level
|
||||||
|
min_level = goto_level;
|
||||||
|
max_level = label_level;
|
||||||
|
min = goto_stmt;
|
||||||
|
max = label_stmt;
|
||||||
|
}
|
||||||
|
while (max_level > min_level) {
|
||||||
|
--max_level;
|
||||||
|
max = max->up;
|
||||||
|
}
|
||||||
|
return min->up == max->up;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsIndirectlyRelated(Node goto_stmt, Node label_stmt) {
|
||||||
|
return goto_stmt->up != label_stmt->up && !IsDirectlyRelated(goto_stmt, label_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] bool AreSiblings(Node goto_stmt, Node label_stmt) noexcept {
|
||||||
|
Node it{goto_stmt};
|
||||||
|
do {
|
||||||
|
if (it == label_stmt) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
--it;
|
||||||
|
} while (it != goto_stmt->up->children.begin());
|
||||||
|
while (it != goto_stmt->up->children.end()) {
|
||||||
|
if (it == label_stmt) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node SiblingFromNephew(Node uncle, Node nephew) noexcept {
|
||||||
|
Statement* const parent{uncle->up};
|
||||||
|
Statement* it{&*nephew};
|
||||||
|
while (it->up != parent) {
|
||||||
|
it = it->up;
|
||||||
|
}
|
||||||
|
return Tree::s_iterator_to(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreOrdered(Node left_sibling, Node right_sibling) noexcept {
|
||||||
|
const Node end{right_sibling->up->children.end()};
|
||||||
|
for (auto it = right_sibling; it != end; ++it) {
|
||||||
|
if (it == left_sibling) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeedsLift(Node goto_stmt, Node label_stmt) noexcept {
|
||||||
|
const Node sibling{SiblingFromNephew(goto_stmt, label_stmt)};
|
||||||
|
return AreOrdered(sibling, goto_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The algorithm used here is from:
|
||||||
|
* Taming Control Flow: A Structured Approach to Eliminating Goto Statements.
|
||||||
|
* Ana M. Erosa and Laurie J. Hendren
|
||||||
|
* http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.1485&rep=rep1&type=pdf
|
||||||
|
*/
|
||||||
|
class GotoPass {
|
||||||
|
public:
|
||||||
|
explicit GotoPass(CFG& cfg, ObjectPool<Statement>& stmt_pool) : pool{stmt_pool} {
|
||||||
|
std::vector gotos{BuildTree(cfg)};
|
||||||
|
const auto end{gotos.rend()};
|
||||||
|
for (auto goto_stmt = gotos.rbegin(); goto_stmt != end; ++goto_stmt) {
|
||||||
|
RemoveGoto(*goto_stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Statement& RootStatement() noexcept {
|
||||||
|
return root_stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RemoveGoto(Node goto_stmt) {
|
||||||
|
// Force goto_stmt and label_stmt to be directly related
|
||||||
|
const Node label_stmt{goto_stmt->label};
|
||||||
|
if (IsIndirectlyRelated(goto_stmt, label_stmt)) {
|
||||||
|
// Move goto_stmt out using outward-movement transformation until it becomes
|
||||||
|
// directly related to label_stmt
|
||||||
|
while (!IsDirectlyRelated(goto_stmt, label_stmt)) {
|
||||||
|
goto_stmt = MoveOutward(goto_stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Force goto_stmt and label_stmt to be siblings
|
||||||
|
if (IsDirectlyRelated(goto_stmt, label_stmt)) {
|
||||||
|
const size_t label_level{Level(label_stmt)};
|
||||||
|
size_t goto_level{Level(goto_stmt)};
|
||||||
|
if (goto_level > label_level) {
|
||||||
|
// Move goto_stmt out of its level using outward-movement transformations
|
||||||
|
while (goto_level > label_level) {
|
||||||
|
goto_stmt = MoveOutward(goto_stmt);
|
||||||
|
--goto_level;
|
||||||
|
}
|
||||||
|
} else { // Level(goto_stmt) < Level(label_stmt)
|
||||||
|
if (NeedsLift(goto_stmt, label_stmt)) {
|
||||||
|
// Lift goto_stmt to above stmt containing label_stmt using goto-lifting
|
||||||
|
// transformations
|
||||||
|
goto_stmt = Lift(goto_stmt);
|
||||||
|
}
|
||||||
|
// Move goto_stmt into label_stmt's level using inward-movement transformation
|
||||||
|
while (goto_level < label_level) {
|
||||||
|
goto_stmt = MoveInward(goto_stmt);
|
||||||
|
++goto_level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Expensive operation:
|
||||||
|
if (!AreSiblings(goto_stmt, label_stmt)) {
|
||||||
|
throw LogicError("Goto is not a sibling with the label");
|
||||||
|
}
|
||||||
|
// goto_stmt and label_stmt are guaranteed to be siblings, eliminate
|
||||||
|
if (std::next(goto_stmt) == label_stmt) {
|
||||||
|
// Simply eliminate the goto if the label is next to it
|
||||||
|
goto_stmt->up->children.erase(goto_stmt);
|
||||||
|
} else if (AreOrdered(goto_stmt, label_stmt)) {
|
||||||
|
// Eliminate goto_stmt with a conditional
|
||||||
|
EliminateAsConditional(goto_stmt, label_stmt);
|
||||||
|
} else {
|
||||||
|
// Eliminate goto_stmt with a loop
|
||||||
|
EliminateAsLoop(goto_stmt, label_stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Node> BuildTree(CFG& cfg) {
|
||||||
|
u32 label_id{0};
|
||||||
|
std::vector<Node> gotos;
|
||||||
|
BuildTree(cfg, label_id, gotos, root_stmt.children.end(), std::nullopt);
|
||||||
|
return gotos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildTree(CFG& cfg, u32& label_id, std::vector<Node>& gotos, Node function_insert_point,
|
||||||
|
std::optional<Node> return_label) {
|
||||||
|
Statement* const false_stmt{pool.Create(Identity{}, IR::Condition::False, &root_stmt)};
|
||||||
|
Tree& root{root_stmt.children};
|
||||||
|
std::unordered_map<Block*, Node> local_labels;
|
||||||
|
local_labels.reserve(cfg.blocks.size());
|
||||||
|
|
||||||
|
for (Block& block : cfg.blocks) {
|
||||||
|
Statement* const label{pool.Create(Label{}, label_id, &root_stmt)};
|
||||||
|
const Node label_it{root.insert(function_insert_point, *label)};
|
||||||
|
local_labels.emplace(&block, label_it);
|
||||||
|
++label_id;
|
||||||
|
}
|
||||||
|
for (Block& block : cfg.blocks) {
|
||||||
|
const Node label{local_labels.at(&block)};
|
||||||
|
// Insertion point
|
||||||
|
const Node ip{std::next(label)};
|
||||||
|
|
||||||
|
// Reset goto variables before the first block and after its respective label
|
||||||
|
const auto make_reset_variable{[&]() -> Statement& {
|
||||||
|
return *pool.Create(SetVariable{}, label->id, false_stmt, &root_stmt);
|
||||||
|
}};
|
||||||
|
root.push_front(make_reset_variable());
|
||||||
|
root.insert(ip, make_reset_variable());
|
||||||
|
root.insert(ip, *pool.Create(&block, &root_stmt));
|
||||||
|
|
||||||
|
switch (block.end_class) {
|
||||||
|
case EndClass::Branch: {
|
||||||
|
Statement* const always_cond{
|
||||||
|
pool.Create(Identity{}, IR::Condition::True, &root_stmt)};
|
||||||
|
if (block.cond == IR::Condition::True) {
|
||||||
|
const Node true_label{local_labels.at(block.branch_true)};
|
||||||
|
gotos.push_back(
|
||||||
|
root.insert(ip, *pool.Create(Goto{}, always_cond, true_label, &root_stmt)));
|
||||||
|
} else if (block.cond == IR::Condition::False) {
|
||||||
|
const Node false_label{local_labels.at(block.branch_false)};
|
||||||
|
gotos.push_back(root.insert(
|
||||||
|
ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
|
||||||
|
} else {
|
||||||
|
const Node true_label{local_labels.at(block.branch_true)};
|
||||||
|
const Node false_label{local_labels.at(block.branch_false)};
|
||||||
|
Statement* const true_cond{pool.Create(Identity{}, block.cond, &root_stmt)};
|
||||||
|
gotos.push_back(
|
||||||
|
root.insert(ip, *pool.Create(Goto{}, true_cond, true_label, &root_stmt)));
|
||||||
|
gotos.push_back(root.insert(
|
||||||
|
ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EndClass::Exit:
|
||||||
|
root.insert(ip, *pool.Create(Return{}, &root_stmt));
|
||||||
|
break;
|
||||||
|
// case EndClass::Kill:
|
||||||
|
// root.insert(ip, *pool.Create(Kill{}, &root_stmt));
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateTreeUp(Statement* tree) {
|
||||||
|
for (Statement& stmt : tree->children) {
|
||||||
|
stmt.up = tree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EliminateAsConditional(Node goto_stmt, Node label_stmt) {
|
||||||
|
Tree& body{goto_stmt->up->children};
|
||||||
|
Tree if_body;
|
||||||
|
if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_stmt);
|
||||||
|
Statement* const cond{pool.Create(Not{}, goto_stmt->cond, &root_stmt)};
|
||||||
|
Statement* const if_stmt{pool.Create(If{}, cond, std::move(if_body), goto_stmt->up)};
|
||||||
|
UpdateTreeUp(if_stmt);
|
||||||
|
body.insert(goto_stmt, *if_stmt);
|
||||||
|
body.erase(goto_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EliminateAsLoop(Node goto_stmt, Node label_stmt) {
|
||||||
|
Tree& body{goto_stmt->up->children};
|
||||||
|
Tree loop_body;
|
||||||
|
loop_body.splice(loop_body.begin(), body, label_stmt, goto_stmt);
|
||||||
|
Statement* const cond{goto_stmt->cond};
|
||||||
|
Statement* const loop{pool.Create(Loop{}, cond, std::move(loop_body), goto_stmt->up)};
|
||||||
|
UpdateTreeUp(loop);
|
||||||
|
body.insert(goto_stmt, *loop);
|
||||||
|
body.erase(goto_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Node MoveOutward(Node goto_stmt) {
|
||||||
|
switch (goto_stmt->up->type) {
|
||||||
|
case StatementType::If:
|
||||||
|
return MoveOutwardIf(goto_stmt);
|
||||||
|
case StatementType::Loop:
|
||||||
|
return MoveOutwardLoop(goto_stmt);
|
||||||
|
default:
|
||||||
|
throw LogicError("Invalid outward movement");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Node MoveInward(Node goto_stmt) {
|
||||||
|
Statement* const parent{goto_stmt->up};
|
||||||
|
Tree& body{parent->children};
|
||||||
|
const Node label{goto_stmt->label};
|
||||||
|
const Node label_nested_stmt{SiblingFromNephew(goto_stmt, label)};
|
||||||
|
const u32 label_id{label->id};
|
||||||
|
|
||||||
|
Statement* const goto_cond{goto_stmt->cond};
|
||||||
|
Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
|
||||||
|
body.insert(goto_stmt, *set_var);
|
||||||
|
|
||||||
|
Tree if_body;
|
||||||
|
if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_nested_stmt);
|
||||||
|
Statement* const variable{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||||
|
Statement* const neg_var{pool.Create(Not{}, variable, &root_stmt)};
|
||||||
|
if (!if_body.empty()) {
|
||||||
|
Statement* const if_stmt{pool.Create(If{}, neg_var, std::move(if_body), parent)};
|
||||||
|
UpdateTreeUp(if_stmt);
|
||||||
|
body.insert(goto_stmt, *if_stmt);
|
||||||
|
}
|
||||||
|
body.erase(goto_stmt);
|
||||||
|
|
||||||
|
switch (label_nested_stmt->type) {
|
||||||
|
case StatementType::If:
|
||||||
|
// Update nested if condition
|
||||||
|
label_nested_stmt->cond =
|
||||||
|
pool.Create(Or{}, variable, label_nested_stmt->cond, &root_stmt);
|
||||||
|
break;
|
||||||
|
case StatementType::Loop:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw LogicError("Invalid inward movement");
|
||||||
|
}
|
||||||
|
Tree& nested_tree{label_nested_stmt->children};
|
||||||
|
Statement* const new_goto{pool.Create(Goto{}, variable, label, &*label_nested_stmt)};
|
||||||
|
return nested_tree.insert(nested_tree.begin(), *new_goto);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Node Lift(Node goto_stmt) {
|
||||||
|
Statement* const parent{goto_stmt->up};
|
||||||
|
Tree& body{parent->children};
|
||||||
|
const Node label{goto_stmt->label};
|
||||||
|
const u32 label_id{label->id};
|
||||||
|
const Node label_nested_stmt{SiblingFromNephew(goto_stmt, label)};
|
||||||
|
|
||||||
|
Tree loop_body;
|
||||||
|
loop_body.splice(loop_body.begin(), body, label_nested_stmt, goto_stmt);
|
||||||
|
SanitizeNoBreaks(loop_body);
|
||||||
|
Statement* const variable{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||||
|
Statement* const loop_stmt{pool.Create(Loop{}, variable, std::move(loop_body), parent)};
|
||||||
|
UpdateTreeUp(loop_stmt);
|
||||||
|
body.insert(goto_stmt, *loop_stmt);
|
||||||
|
|
||||||
|
Statement* const new_goto{pool.Create(Goto{}, variable, label, loop_stmt)};
|
||||||
|
loop_stmt->children.push_front(*new_goto);
|
||||||
|
const Node new_goto_node{loop_stmt->children.begin()};
|
||||||
|
|
||||||
|
Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_stmt->cond, loop_stmt)};
|
||||||
|
loop_stmt->children.push_back(*set_var);
|
||||||
|
|
||||||
|
body.erase(goto_stmt);
|
||||||
|
return new_goto_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node MoveOutwardIf(Node goto_stmt) {
|
||||||
|
const Node parent{Tree::s_iterator_to(*goto_stmt->up)};
|
||||||
|
Tree& body{parent->children};
|
||||||
|
const u32 label_id{goto_stmt->label->id};
|
||||||
|
Statement* const goto_cond{goto_stmt->cond};
|
||||||
|
Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, &*parent)};
|
||||||
|
body.insert(goto_stmt, *set_goto_var);
|
||||||
|
|
||||||
|
Tree if_body;
|
||||||
|
if_body.splice(if_body.begin(), body, std::next(goto_stmt), body.end());
|
||||||
|
if_body.pop_front();
|
||||||
|
Statement* const cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||||
|
Statement* const neg_cond{pool.Create(Not{}, cond, &root_stmt)};
|
||||||
|
Statement* const if_stmt{pool.Create(If{}, neg_cond, std::move(if_body), &*parent)};
|
||||||
|
UpdateTreeUp(if_stmt);
|
||||||
|
body.insert(goto_stmt, *if_stmt);
|
||||||
|
|
||||||
|
body.erase(goto_stmt);
|
||||||
|
|
||||||
|
Statement* const new_cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||||
|
Statement* const new_goto{pool.Create(Goto{}, new_cond, goto_stmt->label, parent->up)};
|
||||||
|
Tree& parent_tree{parent->up->children};
|
||||||
|
return parent_tree.insert(std::next(parent), *new_goto);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node MoveOutwardLoop(Node goto_stmt) {
|
||||||
|
Statement* const parent{goto_stmt->up};
|
||||||
|
Tree& body{parent->children};
|
||||||
|
const u32 label_id{goto_stmt->label->id};
|
||||||
|
Statement* const goto_cond{goto_stmt->cond};
|
||||||
|
Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
|
||||||
|
Statement* const cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||||
|
Statement* const break_stmt{pool.Create(Break{}, cond, parent)};
|
||||||
|
body.insert(goto_stmt, *set_goto_var);
|
||||||
|
body.insert(goto_stmt, *break_stmt);
|
||||||
|
body.erase(goto_stmt);
|
||||||
|
|
||||||
|
const Node loop{Tree::s_iterator_to(*goto_stmt->up)};
|
||||||
|
Statement* const new_goto_cond{pool.Create(Variable{}, label_id, &root_stmt)};
|
||||||
|
Statement* const new_goto{pool.Create(Goto{}, new_goto_cond, goto_stmt->label, loop->up)};
|
||||||
|
Tree& parent_tree{loop->up->children};
|
||||||
|
return parent_tree.insert(std::next(loop), *new_goto);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectPool<Statement>& pool;
|
||||||
|
Statement root_stmt{FunctionTag{}};
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Statement* TryFindForwardBlock(Statement& stmt) {
|
||||||
|
Tree& tree{stmt.up->children};
|
||||||
|
const Node end{tree.end()};
|
||||||
|
Node forward_node{std::next(Tree::s_iterator_to(stmt))};
|
||||||
|
while (forward_node != end && !HasChildren(forward_node->type)) {
|
||||||
|
if (forward_node->type == StatementType::Code) {
|
||||||
|
return &*forward_node;
|
||||||
|
}
|
||||||
|
++forward_node;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] IR::U1 VisitExpr(IR::IREmitter& ir, const Statement& stmt) {
|
||||||
|
switch (stmt.type) {
|
||||||
|
case StatementType::Identity:
|
||||||
|
return ir.Condition(stmt.guest_cond);
|
||||||
|
case StatementType::Not:
|
||||||
|
return ir.LogicalNot(IR::U1{VisitExpr(ir, *stmt.op)});
|
||||||
|
case StatementType::Or:
|
||||||
|
return ir.LogicalOr(VisitExpr(ir, *stmt.op_a), VisitExpr(ir, *stmt.op_b));
|
||||||
|
case StatementType::Variable:
|
||||||
|
return ir.GetGotoVariable(stmt.id);
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Statement type {}", u32(stmt.type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TranslatePass {
|
||||||
|
public:
|
||||||
|
TranslatePass(ObjectPool<IR::Inst>& inst_pool_, ObjectPool<IR::Block>& block_pool_,
|
||||||
|
ObjectPool<Statement>& stmt_pool_, Statement& root_stmt,
|
||||||
|
IR::AbstractSyntaxList& syntax_list_, std::span<const GcnInst> inst_list_,
|
||||||
|
Stage stage_)
|
||||||
|
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_},
|
||||||
|
syntax_list{syntax_list_}, inst_list{inst_list_}, stage{stage_} {
|
||||||
|
Visit(root_stmt, nullptr, nullptr);
|
||||||
|
|
||||||
|
IR::Block& first_block{*syntax_list.front().data.block};
|
||||||
|
IR::IREmitter ir(first_block, first_block.begin());
|
||||||
|
ir.Prologue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Visit(Statement& parent, IR::Block* break_block, IR::Block* fallthrough_block) {
|
||||||
|
IR::Block* current_block{};
|
||||||
|
const auto ensure_block{[&] {
|
||||||
|
if (current_block) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current_block = block_pool.Create(inst_pool);
|
||||||
|
auto& node{syntax_list.emplace_back()};
|
||||||
|
node.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
node.data.block = current_block;
|
||||||
|
}};
|
||||||
|
Tree& tree{parent.children};
|
||||||
|
for (auto it = tree.begin(); it != tree.end(); ++it) {
|
||||||
|
Statement& stmt{*it};
|
||||||
|
switch (stmt.type) {
|
||||||
|
case StatementType::Label:
|
||||||
|
// Labels can be ignored
|
||||||
|
break;
|
||||||
|
case StatementType::Code: {
|
||||||
|
ensure_block();
|
||||||
|
const u32 start = stmt.block->begin_index;
|
||||||
|
const u32 size = stmt.block->end_index - start + 1;
|
||||||
|
Translate(current_block, stage, inst_list.subspan(start, size));
|
||||||
|
fmt::print("{}\n", IR::DumpBlock(*current_block));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case StatementType::SetVariable: {
|
||||||
|
ensure_block();
|
||||||
|
IR::IREmitter ir{*current_block};
|
||||||
|
ir.SetGotoVariable(stmt.id, VisitExpr(ir, *stmt.op));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case StatementType::If: {
|
||||||
|
ensure_block();
|
||||||
|
IR::Block* const merge_block{MergeBlock(parent, stmt)};
|
||||||
|
|
||||||
|
// Implement if header block
|
||||||
|
IR::IREmitter ir{*current_block};
|
||||||
|
const IR::U1 cond{ir.ConditionRef(VisitExpr(ir, *stmt.cond))};
|
||||||
|
|
||||||
|
const size_t if_node_index{syntax_list.size()};
|
||||||
|
syntax_list.emplace_back();
|
||||||
|
|
||||||
|
// Visit children
|
||||||
|
const size_t then_block_index{syntax_list.size()};
|
||||||
|
Visit(stmt, break_block, merge_block);
|
||||||
|
|
||||||
|
IR::Block* const then_block{syntax_list.at(then_block_index).data.block};
|
||||||
|
current_block->AddBranch(then_block);
|
||||||
|
current_block->AddBranch(merge_block);
|
||||||
|
current_block = merge_block;
|
||||||
|
|
||||||
|
auto& if_node{syntax_list[if_node_index]};
|
||||||
|
if_node.type = IR::AbstractSyntaxNode::Type::If;
|
||||||
|
if_node.data.if_node.cond = cond;
|
||||||
|
if_node.data.if_node.body = then_block;
|
||||||
|
if_node.data.if_node.merge = merge_block;
|
||||||
|
|
||||||
|
auto& endif_node{syntax_list.emplace_back()};
|
||||||
|
endif_node.type = IR::AbstractSyntaxNode::Type::EndIf;
|
||||||
|
endif_node.data.end_if.merge = merge_block;
|
||||||
|
|
||||||
|
auto& merge{syntax_list.emplace_back()};
|
||||||
|
merge.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
merge.data.block = merge_block;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case StatementType::Loop: {
|
||||||
|
IR::Block* const loop_header_block{block_pool.Create(inst_pool)};
|
||||||
|
if (current_block) {
|
||||||
|
current_block->AddBranch(loop_header_block);
|
||||||
|
}
|
||||||
|
auto& header_node{syntax_list.emplace_back()};
|
||||||
|
header_node.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
header_node.data.block = loop_header_block;
|
||||||
|
|
||||||
|
IR::Block* const continue_block{block_pool.Create(inst_pool)};
|
||||||
|
IR::Block* const merge_block{MergeBlock(parent, stmt)};
|
||||||
|
|
||||||
|
const size_t loop_node_index{syntax_list.size()};
|
||||||
|
syntax_list.emplace_back();
|
||||||
|
|
||||||
|
// Visit children
|
||||||
|
const size_t body_block_index{syntax_list.size()};
|
||||||
|
Visit(stmt, merge_block, continue_block);
|
||||||
|
|
||||||
|
// The continue block is located at the end of the loop
|
||||||
|
IR::IREmitter ir{*continue_block};
|
||||||
|
const IR::U1 cond{ir.ConditionRef(VisitExpr(ir, *stmt.cond))};
|
||||||
|
|
||||||
|
IR::Block* const body_block{syntax_list.at(body_block_index).data.block};
|
||||||
|
loop_header_block->AddBranch(body_block);
|
||||||
|
|
||||||
|
continue_block->AddBranch(loop_header_block);
|
||||||
|
continue_block->AddBranch(merge_block);
|
||||||
|
|
||||||
|
current_block = merge_block;
|
||||||
|
|
||||||
|
auto& loop{syntax_list[loop_node_index]};
|
||||||
|
loop.type = IR::AbstractSyntaxNode::Type::Loop;
|
||||||
|
loop.data.loop.body = body_block;
|
||||||
|
loop.data.loop.continue_block = continue_block;
|
||||||
|
loop.data.loop.merge = merge_block;
|
||||||
|
|
||||||
|
auto& continue_block_node{syntax_list.emplace_back()};
|
||||||
|
continue_block_node.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
continue_block_node.data.block = continue_block;
|
||||||
|
|
||||||
|
auto& repeat{syntax_list.emplace_back()};
|
||||||
|
repeat.type = IR::AbstractSyntaxNode::Type::Repeat;
|
||||||
|
repeat.data.repeat.cond = cond;
|
||||||
|
repeat.data.repeat.loop_header = loop_header_block;
|
||||||
|
repeat.data.repeat.merge = merge_block;
|
||||||
|
|
||||||
|
auto& merge{syntax_list.emplace_back()};
|
||||||
|
merge.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
merge.data.block = merge_block;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case StatementType::Break: {
|
||||||
|
ensure_block();
|
||||||
|
IR::Block* const skip_block{MergeBlock(parent, stmt)};
|
||||||
|
|
||||||
|
IR::IREmitter ir{*current_block};
|
||||||
|
const IR::U1 cond{ir.ConditionRef(VisitExpr(ir, *stmt.cond))};
|
||||||
|
current_block->AddBranch(break_block);
|
||||||
|
current_block->AddBranch(skip_block);
|
||||||
|
current_block = skip_block;
|
||||||
|
|
||||||
|
auto& break_node{syntax_list.emplace_back()};
|
||||||
|
break_node.type = IR::AbstractSyntaxNode::Type::Break;
|
||||||
|
break_node.data.break_node.cond = cond;
|
||||||
|
break_node.data.break_node.merge = break_block;
|
||||||
|
break_node.data.break_node.skip = skip_block;
|
||||||
|
|
||||||
|
auto& merge{syntax_list.emplace_back()};
|
||||||
|
merge.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
merge.data.block = skip_block;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case StatementType::Return: {
|
||||||
|
ensure_block();
|
||||||
|
IR::Block* return_block{block_pool.Create(inst_pool)};
|
||||||
|
IR::IREmitter{*return_block}.Epilogue();
|
||||||
|
current_block->AddBranch(return_block);
|
||||||
|
|
||||||
|
auto& merge{syntax_list.emplace_back()};
|
||||||
|
merge.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
merge.data.block = return_block;
|
||||||
|
|
||||||
|
current_block = nullptr;
|
||||||
|
syntax_list.emplace_back().type = IR::AbstractSyntaxNode::Type::Return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case StatementType::Kill: {
|
||||||
|
ensure_block();
|
||||||
|
IR::Block* demote_block{MergeBlock(parent, stmt)};
|
||||||
|
// IR::IREmitter{*current_block}.DemoteToHelperInvocation();
|
||||||
|
current_block->AddBranch(demote_block);
|
||||||
|
current_block = demote_block;
|
||||||
|
|
||||||
|
auto& merge{syntax_list.emplace_back()};
|
||||||
|
merge.type = IR::AbstractSyntaxNode::Type::Block;
|
||||||
|
merge.data.block = demote_block;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case StatementType::Unreachable: {
|
||||||
|
ensure_block();
|
||||||
|
current_block = nullptr;
|
||||||
|
syntax_list.emplace_back().type = IR::AbstractSyntaxNode::Type::Unreachable;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Statement type {}", u32(stmt.type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current_block) {
|
||||||
|
if (fallthrough_block) {
|
||||||
|
current_block->AddBranch(fallthrough_block);
|
||||||
|
} else {
|
||||||
|
syntax_list.emplace_back().type = IR::AbstractSyntaxNode::Type::Unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::Block* MergeBlock(Statement& parent, Statement& stmt) {
|
||||||
|
Statement* merge_stmt{TryFindForwardBlock(stmt)};
|
||||||
|
if (!merge_stmt) {
|
||||||
|
// Create a merge block we can visit later
|
||||||
|
merge_stmt = stmt_pool.Create(&dummy_flow_block, &parent);
|
||||||
|
parent.children.insert(std::next(Tree::s_iterator_to(stmt)), *merge_stmt);
|
||||||
|
}
|
||||||
|
return block_pool.Create(inst_pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectPool<Statement>& stmt_pool;
|
||||||
|
ObjectPool<IR::Inst>& inst_pool;
|
||||||
|
ObjectPool<IR::Block>& block_pool;
|
||||||
|
IR::AbstractSyntaxList& syntax_list;
|
||||||
|
const Block dummy_flow_block{};
|
||||||
|
std::span<const GcnInst> inst_list;
|
||||||
|
Stage stage;
|
||||||
|
};
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
IR::AbstractSyntaxList BuildASL(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
|
||||||
|
CFG& cfg, Stage stage) {
|
||||||
|
ObjectPool<Statement> stmt_pool{64};
|
||||||
|
GotoPass goto_pass{cfg, stmt_pool};
|
||||||
|
Statement& root{goto_pass.RootStatement()};
|
||||||
|
IR::AbstractSyntaxList syntax_list;
|
||||||
|
TranslatePass{inst_pool, block_pool, stmt_pool, root, syntax_list, cfg.inst_list, stage};
|
||||||
|
return syntax_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,22 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/control_flow_graph.h"
|
||||||
|
#include "shader_recompiler/ir/abstract_syntax_list.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
#include "shader_recompiler/object_pool.h"
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
enum class Stage : u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
[[nodiscard]] IR::AbstractSyntaxList BuildASL(ObjectPool<IR::Inst>& inst_pool,
|
||||||
|
ObjectPool<IR::Block>& block_pool, CFG& cfg,
|
||||||
|
Stage stage);
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,44 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst) {
|
||||||
|
const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))};
|
||||||
|
const IR::VectorReg dst_reg{inst.dst[0].code};
|
||||||
|
if (is_pair) {
|
||||||
|
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0)));
|
||||||
|
ir.SetVectorReg(dst_reg, ir.ReadShared(32, is_signed, addr0));
|
||||||
|
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1)));
|
||||||
|
ir.SetVectorReg(dst_reg + 1, ir.ReadShared(32, is_signed, addr1));
|
||||||
|
} else if (bit_size == 64) {
|
||||||
|
const IR::Value data = ir.UnpackUint2x32(ir.ReadShared(bit_size, is_signed, addr));
|
||||||
|
ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)});
|
||||||
|
ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)});
|
||||||
|
} else {
|
||||||
|
const IR::U32 data = ir.ReadShared(bit_size, is_signed, addr);
|
||||||
|
ir.SetVectorReg(dst_reg, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst) {
|
||||||
|
const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))};
|
||||||
|
const IR::VectorReg data0{inst.src[1].code};
|
||||||
|
const IR::VectorReg data1{inst.src[2].code};
|
||||||
|
if (is_pair) {
|
||||||
|
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0)));
|
||||||
|
ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
|
||||||
|
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1)));
|
||||||
|
ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
|
||||||
|
} else if (bit_size == 64) {
|
||||||
|
const IR::U64 data = ir.PackUint2x32(
|
||||||
|
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)));
|
||||||
|
ir.WriteShared(bit_size, data, addr);
|
||||||
|
} else {
|
||||||
|
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,49 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Translator::EXP(const GcnInst& inst) {
|
||||||
|
const auto& exp = inst.control.exp;
|
||||||
|
const IR::Attribute attrib{exp.target};
|
||||||
|
const std::array vsrc = {
|
||||||
|
IR::VectorReg(inst.src[0].code),
|
||||||
|
IR::VectorReg(inst.src[1].code),
|
||||||
|
IR::VectorReg(inst.src[2].code),
|
||||||
|
IR::VectorReg(inst.src[3].code),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto unpack = [&](u32 idx) {
|
||||||
|
const IR::Value value = ir.UnpackHalf2x16(ir.GetVectorReg(vsrc[idx]));
|
||||||
|
const IR::F32 r = IR::F32{ir.CompositeExtract(value, 0)};
|
||||||
|
const IR::F32 g = IR::F32{ir.CompositeExtract(value, 1)};
|
||||||
|
ir.SetAttribute(attrib, r, idx * 2);
|
||||||
|
ir.SetAttribute(attrib, g, idx * 2 + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Components are float16 packed into a VGPR
|
||||||
|
if (exp.compr) {
|
||||||
|
// Export R, G
|
||||||
|
if (exp.en & 1) {
|
||||||
|
unpack(0);
|
||||||
|
}
|
||||||
|
// Export B, A
|
||||||
|
if ((exp.en >> 2) & 1) {
|
||||||
|
unpack(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Components are float32 into separate VGPRS
|
||||||
|
u32 mask = exp.en;
|
||||||
|
for (u32 i = 0; i < 4; i++, mask >>= 1) {
|
||||||
|
if ((mask & 1) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const IR::F32 comp = ir.GetVectorReg<IR::F32>(vsrc[i]);
|
||||||
|
ir.SetAttribute(attrib, comp, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,38 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Translator::S_MOV(const GcnInst& inst) {
|
||||||
|
SetDst(inst.dst[0], GetSrc(inst.src[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::S_MUL_I32(const GcnInst& inst) {
|
||||||
|
SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) {
|
||||||
|
const IR::U32 lhs = GetSrc(inst.src[0]);
|
||||||
|
const IR::U32 rhs = GetSrc(inst.src[1]);
|
||||||
|
const IR::U1 result = [&] {
|
||||||
|
switch (cond) {
|
||||||
|
case ConditionOp::EQ:
|
||||||
|
return ir.IEqual(lhs, rhs);
|
||||||
|
case ConditionOp::LG:
|
||||||
|
return ir.INotEqual(lhs, rhs);
|
||||||
|
case ConditionOp::GT:
|
||||||
|
return ir.IGreaterThan(lhs, rhs, is_signed);
|
||||||
|
case ConditionOp::GE:
|
||||||
|
return ir.IGreaterThanEqual(lhs, rhs, is_signed);
|
||||||
|
case ConditionOp::LT:
|
||||||
|
return ir.ILessThan(lhs, rhs, is_signed);
|
||||||
|
case ConditionOp::LE:
|
||||||
|
return ir.ILessThanEqual(lhs, rhs, is_signed);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
// ir.SetScc(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,45 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Load(IR::IREmitter& ir, int num_dwords, const IR::Value& handle, IR::ScalarReg dst_reg,
|
||||||
|
const IR::U32U64& address) {
|
||||||
|
for (u32 i = 0; i < num_dwords; i++) {
|
||||||
|
const IR::U32 value = handle.IsEmpty() ? ir.ReadConst(address, ir.Imm32(i))
|
||||||
|
: ir.ReadConstBuffer(handle, address, ir.Imm32(i));
|
||||||
|
ir.SetScalarReg(dst_reg++, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) {
|
||||||
|
const auto& smrd = inst.control.smrd;
|
||||||
|
const IR::ScalarReg sbase = IR::ScalarReg(inst.src[0].code * 2);
|
||||||
|
const IR::U32 offset =
|
||||||
|
smrd.imm ? ir.Imm32(smrd.offset * 4)
|
||||||
|
: IR::U32{ir.ShiftLeftLogical(ir.GetScalarReg(IR::ScalarReg(smrd.offset)),
|
||||||
|
ir.Imm32(2))};
|
||||||
|
const IR::U64 base =
|
||||||
|
ir.PackUint2x32(ir.CompositeConstruct(ir.GetScalarReg(sbase), ir.GetScalarReg(sbase + 1)));
|
||||||
|
const IR::U64 address = ir.IAdd(base, offset);
|
||||||
|
const IR::ScalarReg dst_reg{inst.dst[0].code};
|
||||||
|
Load(ir, num_dwords, {}, dst_reg, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::S_BUFFER_LOAD_DWORD(int num_dwords, const GcnInst& inst) {
|
||||||
|
const auto& smrd = inst.control.smrd;
|
||||||
|
const IR::ScalarReg sbase = IR::ScalarReg(inst.src[0].code * 2);
|
||||||
|
const IR::U32 offset =
|
||||||
|
smrd.imm ? ir.Imm32(smrd.offset * 4)
|
||||||
|
: IR::U32{ir.ShiftLeftLogical(ir.GetScalarReg(IR::ScalarReg(smrd.offset)),
|
||||||
|
ir.Imm32(2))};
|
||||||
|
const IR::Value vsharp =
|
||||||
|
ir.CompositeConstruct(ir.GetScalarReg(sbase), ir.GetScalarReg(sbase + 1),
|
||||||
|
ir.GetScalarReg(sbase + 2), ir.GetScalarReg(sbase + 3));
|
||||||
|
const IR::ScalarReg dst_reg{inst.dst[0].code};
|
||||||
|
Load(ir, num_dwords, vsharp, dst_reg, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,152 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/exception.h"
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
#include "shader_recompiler/runtime_info.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
Translator::Translator(IR::Block* block_, Stage stage) : block{block_}, ir{*block} {
|
||||||
|
IR::VectorReg dst_vreg = IR::VectorReg::V0;
|
||||||
|
switch (stage) {
|
||||||
|
case Stage::Vertex:
|
||||||
|
// https://github.com/chaotic-cx/mesa-mirror/blob/72326e15/src/amd/vulkan/radv_shader_args.c#L146C1-L146C23
|
||||||
|
ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::VertexId));
|
||||||
|
ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::InstanceId));
|
||||||
|
ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::PrimitiveId));
|
||||||
|
break;
|
||||||
|
case Stage::Fragment:
|
||||||
|
// https://github.com/chaotic-cx/mesa-mirror/blob/72326e15/src/amd/vulkan/radv_shader_args.c#L258
|
||||||
|
// The first two VGPRs are used for i/j barycentric coordinates. In the vast majority of
|
||||||
|
// cases it will be only those two, but if shader is using both e.g linear and perspective
|
||||||
|
// inputs it can be more For now assume that this isn't the case.
|
||||||
|
dst_vreg = IR::VectorReg::V2;
|
||||||
|
for (u32 i = 0; i < 4; i++) {
|
||||||
|
ir.SetVectorReg(dst_vreg++, ir.GetAttribute(IR::Attribute::FragCoord, i));
|
||||||
|
}
|
||||||
|
ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::IsFrontFace));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw NotImplementedException("Unknown shader stage");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize user data.
|
||||||
|
IR::ScalarReg dst_sreg = IR::ScalarReg::S0;
|
||||||
|
for (u32 i = 0; i < 16; i++) {
|
||||||
|
ir.SetScalarReg(dst_sreg++, ir.Imm32(0U));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::U32F32 Translator::GetSrc(const InstOperand& operand, bool force_flt) {
|
||||||
|
switch (operand.field) {
|
||||||
|
case OperandField::ScalarGPR:
|
||||||
|
if (operand.type == ScalarType::Float32 || force_flt) {
|
||||||
|
return ir.GetScalarReg<IR::F32>(IR::ScalarReg(operand.code));
|
||||||
|
} else {
|
||||||
|
return ir.GetScalarReg<IR::U32>(IR::ScalarReg(operand.code));
|
||||||
|
}
|
||||||
|
case OperandField::VectorGPR:
|
||||||
|
if (operand.type == ScalarType::Float32 || force_flt) {
|
||||||
|
return ir.GetVectorReg<IR::F32>(IR::VectorReg(operand.code));
|
||||||
|
} else {
|
||||||
|
return ir.GetVectorReg<IR::U32>(IR::VectorReg(operand.code));
|
||||||
|
}
|
||||||
|
case OperandField::ConstZero:
|
||||||
|
if (force_flt) {
|
||||||
|
return ir.Imm32(0.f);
|
||||||
|
} else {
|
||||||
|
return ir.Imm32(0U);
|
||||||
|
}
|
||||||
|
case OperandField::SignedConstIntPos:
|
||||||
|
ASSERT(!force_flt);
|
||||||
|
return ir.Imm32(operand.code - SignedConstIntPosMin + 1);
|
||||||
|
case OperandField::SignedConstIntNeg:
|
||||||
|
ASSERT(!force_flt);
|
||||||
|
return ir.Imm32(-s32(operand.code) + SignedConstIntNegMin - 1);
|
||||||
|
case OperandField::LiteralConst:
|
||||||
|
ASSERT(!force_flt);
|
||||||
|
return ir.Imm32(operand.code);
|
||||||
|
case OperandField::ConstFloatPos_1_0:
|
||||||
|
return ir.Imm32(1.f);
|
||||||
|
case OperandField::ConstFloatPos_0_5:
|
||||||
|
return ir.Imm32(0.5f);
|
||||||
|
case OperandField::ConstFloatNeg_0_5:
|
||||||
|
return ir.Imm32(-0.5f);
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) {
|
||||||
|
switch (operand.field) {
|
||||||
|
case OperandField::ScalarGPR:
|
||||||
|
return ir.SetScalarReg(IR::ScalarReg(operand.code), value);
|
||||||
|
case OperandField::VectorGPR:
|
||||||
|
return ir.SetVectorReg(IR::VectorReg(operand.code), value);
|
||||||
|
case OperandField::VccHi:
|
||||||
|
case OperandField::M0:
|
||||||
|
break; // Ignore for now
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translate(IR::Block* block, Stage stage, std::span<const GcnInst> inst_list) {
|
||||||
|
if (inst_list.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Translator translator{block, stage};
|
||||||
|
for (const auto& inst : inst_list) {
|
||||||
|
switch (inst.opcode) {
|
||||||
|
case Opcode::S_MOV_B32:
|
||||||
|
translator.S_MOV(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::S_MUL_I32:
|
||||||
|
translator.S_MUL_I32(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::V_MOV_B32:
|
||||||
|
translator.V_MOV(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::V_MAC_F32:
|
||||||
|
translator.V_MAC_F32(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::V_MUL_F32:
|
||||||
|
translator.V_MUL_F32(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::S_SWAPPC_B64:
|
||||||
|
case Opcode::S_WAITCNT:
|
||||||
|
break; // Ignore for now.
|
||||||
|
case Opcode::S_BUFFER_LOAD_DWORDX16:
|
||||||
|
translator.S_BUFFER_LOAD_DWORD(16, inst);
|
||||||
|
break;
|
||||||
|
case Opcode::EXP:
|
||||||
|
translator.EXP(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::V_INTERP_P2_F32:
|
||||||
|
translator.V_INTERP_P2_F32(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::V_CVT_PKRTZ_F16_F32:
|
||||||
|
translator.V_CVT_PKRTZ_F16_F32(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::IMAGE_SAMPLE:
|
||||||
|
translator.IMAGE_SAMPLE(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::V_CMP_EQ_U32:
|
||||||
|
translator.V_CMP_EQ_U32(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::V_CNDMASK_B32:
|
||||||
|
translator.V_CNDMASK_B32(inst);
|
||||||
|
break;
|
||||||
|
case Opcode::S_MOV_B64:
|
||||||
|
case Opcode::S_WQM_B64:
|
||||||
|
case Opcode::V_INTERP_P1_F32:
|
||||||
|
case Opcode::S_ENDPGM:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unknown opcode {}", u32(inst.opcode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,73 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include "shader_recompiler/frontend/instruction.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/ir_emitter.h"
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
enum class Stage : u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
enum class ConditionOp : u32 {
|
||||||
|
EQ,
|
||||||
|
LG,
|
||||||
|
GT,
|
||||||
|
GE,
|
||||||
|
LT,
|
||||||
|
LE,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Translator {
|
||||||
|
public:
|
||||||
|
explicit Translator(IR::Block* block_, Stage stage);
|
||||||
|
|
||||||
|
// Scalar ALU
|
||||||
|
void S_MOV(const GcnInst& inst);
|
||||||
|
void S_MUL_I32(const GcnInst& inst);
|
||||||
|
void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst);
|
||||||
|
|
||||||
|
// Scalar Memory
|
||||||
|
void S_LOAD_DWORD(int num_dwords, const GcnInst& inst);
|
||||||
|
void S_BUFFER_LOAD_DWORD(int num_dwords, const GcnInst& inst);
|
||||||
|
|
||||||
|
// Vector ALU
|
||||||
|
void V_MOV(const GcnInst& inst);
|
||||||
|
void V_SAD(const GcnInst& inst);
|
||||||
|
void V_MAC_F32(const GcnInst& inst);
|
||||||
|
void V_CVT_PKRTZ_F16_F32(const GcnInst& inst);
|
||||||
|
void V_MUL_F32(const GcnInst& inst);
|
||||||
|
void V_CMP_EQ_U32(const GcnInst& inst);
|
||||||
|
void V_CNDMASK_B32(const GcnInst& inst);
|
||||||
|
|
||||||
|
// Vector interpolation
|
||||||
|
void V_INTERP_P2_F32(const GcnInst& inst);
|
||||||
|
|
||||||
|
// Data share
|
||||||
|
void DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst);
|
||||||
|
void DS_WRITE(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst);
|
||||||
|
|
||||||
|
// MIMG
|
||||||
|
void IMAGE_GET_RESINFO(const GcnInst& inst);
|
||||||
|
void IMAGE_SAMPLE(const GcnInst& inst);
|
||||||
|
|
||||||
|
// Export
|
||||||
|
void EXP(const GcnInst& inst);
|
||||||
|
|
||||||
|
private:
|
||||||
|
IR::U32F32 GetSrc(const InstOperand& operand, bool flt_zero = false);
|
||||||
|
void SetDst(const InstOperand& operand, const IR::U32F32& value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
IR::Block* block;
|
||||||
|
IR::IREmitter ir;
|
||||||
|
};
|
||||||
|
|
||||||
|
void Translate(IR::Block* block, Stage stage, std::span<const GcnInst> inst_list);
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,65 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma clang optimize off
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Translator::V_MOV(const GcnInst& inst) {
|
||||||
|
SetDst(inst.dst[0], GetSrc(inst.src[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::V_SAD(const GcnInst& inst) {
|
||||||
|
const IR::U32 abs_diff = ir.IAbs(ir.ISub(GetSrc(inst.src[0]), GetSrc(inst.src[1])));
|
||||||
|
SetDst(inst.dst[0], ir.IAdd(abs_diff, GetSrc(inst.src[2])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::V_MAC_F32(const GcnInst& inst) {
|
||||||
|
SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), GetSrc(inst.dst[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) {
|
||||||
|
const IR::VectorReg dst_reg{inst.dst[0].code};
|
||||||
|
const IR::Value vec_f32 = ir.CompositeConstruct(ir.FPConvert(16, GetSrc(inst.src[0])),
|
||||||
|
ir.FPConvert(16, GetSrc(inst.src[1])));
|
||||||
|
ir.SetVectorReg(dst_reg, ir.PackFloat2x16(vec_f32));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::V_MUL_F32(const GcnInst& inst) {
|
||||||
|
const IR::VectorReg dst_reg{inst.dst[0].code};
|
||||||
|
ir.SetVectorReg(dst_reg, ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::V_CMP_EQ_U32(const GcnInst& inst) {
|
||||||
|
const IR::U1 result = ir.IEqual(GetSrc(inst.src[0]), GetSrc(inst.src[1]));
|
||||||
|
if (inst.dst[1].field == OperandField::VccLo) {
|
||||||
|
return ir.SetVcc(result);
|
||||||
|
} else if (inst.dst[1].field == OperandField::ScalarGPR) {
|
||||||
|
const IR::ScalarReg dst_reg{inst.dst[1].code};
|
||||||
|
return ir.SetScalarReg(dst_reg, IR::U32{ir.Select(result, ir.Imm32(1U), ir.Imm32(0U))});
|
||||||
|
}
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::V_CNDMASK_B32(const GcnInst& inst) {
|
||||||
|
const IR::VectorReg dst_reg{inst.dst[0].code};
|
||||||
|
const IR::ScalarReg flag_reg{inst.src[2].code};
|
||||||
|
const IR::U1 flag = inst.src[2].field == OperandField::ScalarGPR
|
||||||
|
? ir.INotEqual(ir.GetScalarReg(flag_reg), ir.Imm32(0U))
|
||||||
|
: ir.GetVcc();
|
||||||
|
|
||||||
|
// We can treat the instruction as integer most of the time, but when a source is
|
||||||
|
// a floating point constant we will force the other as float for better readability
|
||||||
|
// The other operand is also higly likely to be float as well.
|
||||||
|
const auto is_float_const = [](OperandField field) {
|
||||||
|
return field >= OperandField::ConstFloatPos_0_5 && field <= OperandField::ConstFloatNeg_4_0;
|
||||||
|
};
|
||||||
|
const bool has_flt_source =
|
||||||
|
is_float_const(inst.src[0].field) || is_float_const(inst.src[1].field);
|
||||||
|
const IR::U32F32 src0 = GetSrc(inst.src[0], has_flt_source);
|
||||||
|
const IR::U32F32 src1 = GetSrc(inst.src[1], has_flt_source);
|
||||||
|
const IR::Value result = ir.Select(flag, src1, src0);
|
||||||
|
ir.SetVectorReg(dst_reg, IR::U32F32{result});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Translator::V_INTERP_P2_F32(const GcnInst& inst) {
|
||||||
|
const IR::VectorReg dst_reg{inst.dst[0].code};
|
||||||
|
const IR::Attribute attrib{IR::Attribute::Param0 + inst.control.vintrp.attr};
|
||||||
|
ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,103 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/translate/translate.h"
|
||||||
|
|
||||||
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
|
void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) {
|
||||||
|
IR::VectorReg dst_reg{inst.src[1].code};
|
||||||
|
const IR::ScalarReg tsharp_reg{inst.src[2].code};
|
||||||
|
const auto flags = ImageResFlags(inst.control.mimg.dmask);
|
||||||
|
const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code));
|
||||||
|
const IR::Value tsharp =
|
||||||
|
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(tsharp_reg + 1),
|
||||||
|
ir.GetScalarReg(tsharp_reg + 2), ir.GetScalarReg(tsharp_reg + 3));
|
||||||
|
const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(false));
|
||||||
|
|
||||||
|
if (flags.test(ImageResComponent::Width)) {
|
||||||
|
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)});
|
||||||
|
}
|
||||||
|
if (flags.test(ImageResComponent::Height)) {
|
||||||
|
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)});
|
||||||
|
}
|
||||||
|
if (flags.test(ImageResComponent::Depth)) {
|
||||||
|
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)});
|
||||||
|
}
|
||||||
|
if (flags.test(ImageResComponent::MipCount)) {
|
||||||
|
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Translator::IMAGE_SAMPLE(const GcnInst& inst) {
|
||||||
|
const auto& mimg = inst.control.mimg;
|
||||||
|
ASSERT(!mimg.da);
|
||||||
|
|
||||||
|
IR::VectorReg addr_reg{inst.src[0].code};
|
||||||
|
IR::VectorReg dest_reg{inst.dst[0].code};
|
||||||
|
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
|
||||||
|
const IR::ScalarReg sampler_reg{inst.src[3].code * 4};
|
||||||
|
const auto flags = MimgModifierFlags(mimg.mod);
|
||||||
|
|
||||||
|
// Load first dword of T# and S#. We will use them as the handle that will guide resource
|
||||||
|
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
|
||||||
|
// binding index.
|
||||||
|
const IR::Value handle =
|
||||||
|
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
|
||||||
|
|
||||||
|
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
|
||||||
|
// Set Architecture
|
||||||
|
const IR::Value offset =
|
||||||
|
flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{};
|
||||||
|
const IR::F32 bias =
|
||||||
|
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
|
||||||
|
const IR::F32 dref =
|
||||||
|
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
|
||||||
|
|
||||||
|
// Derivatives are tricky because their number depends on the texture type which is located in
|
||||||
|
// T#. We don't have access to T# though until resource tracking pass. For now assume no
|
||||||
|
// derivatives are present, otherwise we don't know where coordinates are placed in the address
|
||||||
|
// stream.
|
||||||
|
ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction");
|
||||||
|
|
||||||
|
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
|
||||||
|
// Since these are at most 4 dwords, we load them into a single uvec4 and place them
|
||||||
|
// in coords field of the instruction. Then the resource tracking pass will patch the
|
||||||
|
// IR instruction to fill in lod_clamp field. The vector can also be used
|
||||||
|
// as coords directly as SPIR-V will ignore any extra parameters.
|
||||||
|
const IR::Value body =
|
||||||
|
ir.CompositeConstruct(ir.GetVectorReg(addr_reg++), ir.GetVectorReg(addr_reg++),
|
||||||
|
ir.GetVectorReg(addr_reg++), ir.GetVectorReg(addr_reg++));
|
||||||
|
|
||||||
|
// Issue IR instruction, leaving unknown fields blank to patch later.
|
||||||
|
const IR::Value texel = [&]() -> IR::Value {
|
||||||
|
const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{};
|
||||||
|
const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod);
|
||||||
|
if (!flags.test(MimgModifier::Pcf)) {
|
||||||
|
if (explicit_lod) {
|
||||||
|
return ir.ImageSampleExplicitLod(handle, body, lod, offset, {});
|
||||||
|
} else {
|
||||||
|
return ir.ImageSampleImplicitLod(handle, body, bias, offset, {}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (explicit_lod) {
|
||||||
|
return ir.ImageSampleDrefExplicitLod(handle, body, dref, lod, offset, {});
|
||||||
|
}
|
||||||
|
return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, {}, {});
|
||||||
|
}();
|
||||||
|
|
||||||
|
for (u32 i = 0; i < 4; i++) {
|
||||||
|
if (((mimg.dmask >> i) & 1) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IR::F32 value;
|
||||||
|
if (flags.test(MimgModifier::Pcf)) {
|
||||||
|
value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f);
|
||||||
|
} else {
|
||||||
|
value = IR::F32{ir.CompositeExtract(texel, i)};
|
||||||
|
}
|
||||||
|
ir.SetVectorReg(dest_reg++, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Gcn
|
|
@ -0,0 +1,56 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
class Block;
|
||||||
|
|
||||||
|
struct AbstractSyntaxNode {
|
||||||
|
enum class Type {
|
||||||
|
Block,
|
||||||
|
If,
|
||||||
|
EndIf,
|
||||||
|
Loop,
|
||||||
|
Repeat,
|
||||||
|
Break,
|
||||||
|
Return,
|
||||||
|
Unreachable,
|
||||||
|
};
|
||||||
|
union Data {
|
||||||
|
Block* block;
|
||||||
|
struct {
|
||||||
|
U1 cond;
|
||||||
|
Block* body;
|
||||||
|
Block* merge;
|
||||||
|
} if_node;
|
||||||
|
struct {
|
||||||
|
Block* merge;
|
||||||
|
} end_if;
|
||||||
|
struct {
|
||||||
|
Block* body;
|
||||||
|
Block* continue_block;
|
||||||
|
Block* merge;
|
||||||
|
} loop;
|
||||||
|
struct {
|
||||||
|
U1 cond;
|
||||||
|
Block* loop_header;
|
||||||
|
Block* merge;
|
||||||
|
} repeat;
|
||||||
|
struct {
|
||||||
|
U1 cond;
|
||||||
|
Block* merge;
|
||||||
|
Block* skip;
|
||||||
|
} break_node;
|
||||||
|
};
|
||||||
|
|
||||||
|
Data data{};
|
||||||
|
Type type{};
|
||||||
|
};
|
||||||
|
using AbstractSyntaxList = std::vector<AbstractSyntaxNode>;
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,115 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "shader_recompiler/ir/attribute.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
bool IsParam(Attribute attribute) noexcept {
|
||||||
|
return attribute >= Attribute::Param0 && attribute <= Attribute::Param31;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NameOf(Attribute attribute) {
|
||||||
|
switch (attribute) {
|
||||||
|
case Attribute::RenderTarget0:
|
||||||
|
return "RenderTarget0";
|
||||||
|
case Attribute::RenderTarget1:
|
||||||
|
return "RenderTarget1";
|
||||||
|
case Attribute::RenderTarget2:
|
||||||
|
return "RenderTarget2";
|
||||||
|
case Attribute::RenderTarget3:
|
||||||
|
return "RenderTarget3";
|
||||||
|
case Attribute::RenderTarget4:
|
||||||
|
return "RenderTarget4";
|
||||||
|
case Attribute::RenderTarget5:
|
||||||
|
return "RenderTarget5";
|
||||||
|
case Attribute::RenderTarget6:
|
||||||
|
return "RenderTarget6";
|
||||||
|
case Attribute::RenderTarget7:
|
||||||
|
return "RenderTarget7";
|
||||||
|
case Attribute::Depth:
|
||||||
|
return "Depth";
|
||||||
|
case Attribute::Null:
|
||||||
|
return "Null";
|
||||||
|
case Attribute::Position0:
|
||||||
|
return "Position0";
|
||||||
|
case Attribute::Position1:
|
||||||
|
return "Position1";
|
||||||
|
case Attribute::Position2:
|
||||||
|
return "Position2";
|
||||||
|
case Attribute::Position3:
|
||||||
|
return "Position3";
|
||||||
|
case Attribute::Param0:
|
||||||
|
return "Param0";
|
||||||
|
case Attribute::Param1:
|
||||||
|
return "Param1";
|
||||||
|
case Attribute::Param2:
|
||||||
|
return "Param2";
|
||||||
|
case Attribute::Param3:
|
||||||
|
return "Param3";
|
||||||
|
case Attribute::Param4:
|
||||||
|
return "Param4";
|
||||||
|
case Attribute::Param5:
|
||||||
|
return "Param5";
|
||||||
|
case Attribute::Param6:
|
||||||
|
return "Param6";
|
||||||
|
case Attribute::Param7:
|
||||||
|
return "Param7";
|
||||||
|
case Attribute::Param8:
|
||||||
|
return "Param8";
|
||||||
|
case Attribute::Param9:
|
||||||
|
return "Param9";
|
||||||
|
case Attribute::Param10:
|
||||||
|
return "Param10";
|
||||||
|
case Attribute::Param11:
|
||||||
|
return "Param11";
|
||||||
|
case Attribute::Param12:
|
||||||
|
return "Param12";
|
||||||
|
case Attribute::Param13:
|
||||||
|
return "Param13";
|
||||||
|
case Attribute::Param14:
|
||||||
|
return "Param14";
|
||||||
|
case Attribute::Param15:
|
||||||
|
return "Param15";
|
||||||
|
case Attribute::Param16:
|
||||||
|
return "Param16";
|
||||||
|
case Attribute::Param17:
|
||||||
|
return "Param17";
|
||||||
|
case Attribute::Param18:
|
||||||
|
return "Param18";
|
||||||
|
case Attribute::Param19:
|
||||||
|
return "Param19";
|
||||||
|
case Attribute::Param20:
|
||||||
|
return "Param20";
|
||||||
|
case Attribute::Param21:
|
||||||
|
return "Param21";
|
||||||
|
case Attribute::Param22:
|
||||||
|
return "Param22";
|
||||||
|
case Attribute::Param23:
|
||||||
|
return "Param23";
|
||||||
|
case Attribute::Param24:
|
||||||
|
return "Param24";
|
||||||
|
case Attribute::Param25:
|
||||||
|
return "Param25";
|
||||||
|
case Attribute::Param26:
|
||||||
|
return "Param26";
|
||||||
|
case Attribute::Param27:
|
||||||
|
return "Param27";
|
||||||
|
case Attribute::Param28:
|
||||||
|
return "Param28";
|
||||||
|
case Attribute::Param29:
|
||||||
|
return "Param29";
|
||||||
|
case Attribute::Param30:
|
||||||
|
return "Param30";
|
||||||
|
case Attribute::Param31:
|
||||||
|
return "Param31";
|
||||||
|
case Attribute::VertexId:
|
||||||
|
return "VertexId";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return fmt::format("<reserved attribute {}>", static_cast<int>(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,105 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "shader_recompiler/exception.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
enum class Attribute : u64 {
|
||||||
|
// Export targets
|
||||||
|
RenderTarget0 = 0,
|
||||||
|
RenderTarget1 = 1,
|
||||||
|
RenderTarget2 = 2,
|
||||||
|
RenderTarget3 = 3,
|
||||||
|
RenderTarget4 = 4,
|
||||||
|
RenderTarget5 = 5,
|
||||||
|
RenderTarget6 = 6,
|
||||||
|
RenderTarget7 = 7,
|
||||||
|
Depth = 8,
|
||||||
|
Null = 9,
|
||||||
|
Position0 = 12,
|
||||||
|
Position1 = 13,
|
||||||
|
Position2 = 14,
|
||||||
|
Position3 = 15,
|
||||||
|
Param0 = 32,
|
||||||
|
Param1 = 33,
|
||||||
|
Param2 = 34,
|
||||||
|
Param3 = 35,
|
||||||
|
Param4 = 36,
|
||||||
|
Param5 = 37,
|
||||||
|
Param6 = 38,
|
||||||
|
Param7 = 39,
|
||||||
|
Param8 = 40,
|
||||||
|
Param9 = 41,
|
||||||
|
Param10 = 42,
|
||||||
|
Param11 = 43,
|
||||||
|
Param12 = 44,
|
||||||
|
Param13 = 45,
|
||||||
|
Param14 = 46,
|
||||||
|
Param15 = 47,
|
||||||
|
Param16 = 48,
|
||||||
|
Param17 = 49,
|
||||||
|
Param18 = 50,
|
||||||
|
Param19 = 51,
|
||||||
|
Param20 = 52,
|
||||||
|
Param21 = 53,
|
||||||
|
Param22 = 54,
|
||||||
|
Param23 = 55,
|
||||||
|
Param24 = 56,
|
||||||
|
Param25 = 57,
|
||||||
|
Param26 = 58,
|
||||||
|
Param27 = 59,
|
||||||
|
Param28 = 60,
|
||||||
|
Param29 = 61,
|
||||||
|
Param30 = 62,
|
||||||
|
Param31 = 63,
|
||||||
|
// System values
|
||||||
|
ClipDistance = 64,
|
||||||
|
CullDistance = 65,
|
||||||
|
RenderTargetId = 66,
|
||||||
|
ViewportId = 67,
|
||||||
|
VertexId = 68,
|
||||||
|
PrimitiveId = 69,
|
||||||
|
InstanceId = 70,
|
||||||
|
IsFrontFace = 71,
|
||||||
|
SampleIndex = 72,
|
||||||
|
GlobalInvocationId = 73,
|
||||||
|
WorkgroupId = 74,
|
||||||
|
LocalInvocationId = 75,
|
||||||
|
LocalInvocationIndex = 76,
|
||||||
|
FragCoord = 77,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr size_t EXP_NUM_POS = 4;
|
||||||
|
constexpr size_t EXP_NUM_PARAM = 32;
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsParam(Attribute attribute) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string NameOf(Attribute attribute);
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Attribute operator+(Attribute attr, int num) {
|
||||||
|
const int result{static_cast<int>(attr) + num};
|
||||||
|
if (result > static_cast<int>(Attribute::Param31)) {
|
||||||
|
throw LogicError("Overflow on register arithmetic");
|
||||||
|
}
|
||||||
|
if (result < static_cast<int>(Attribute::Param0)) {
|
||||||
|
throw LogicError("Underflow on register arithmetic");
|
||||||
|
}
|
||||||
|
return static_cast<Attribute>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<Shader::IR::Attribute> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
auto format(const Shader::IR::Attribute& attribute, format_context& ctx) const {
|
||||||
|
return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(attribute));
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,149 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <map>
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
Block::Block(ObjectPool<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
|
||||||
|
|
||||||
|
Block::~Block() = default;
|
||||||
|
|
||||||
|
void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
|
||||||
|
PrependNewInst(end(), op, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
|
||||||
|
Inst* const inst{inst_pool->Create(base_inst)};
|
||||||
|
return instructions.insert(insertion_point, *inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode op,
|
||||||
|
std::initializer_list<Value> args, u32 flags) {
|
||||||
|
Inst* const inst{inst_pool->Create(op, flags)};
|
||||||
|
const auto result_it{instructions.insert(insertion_point, *inst)};
|
||||||
|
|
||||||
|
if (inst->NumArgs() != args.size()) {
|
||||||
|
throw InvalidArgument("Invalid number of arguments {} in {}", args.size(), op);
|
||||||
|
}
|
||||||
|
std::ranges::for_each(args, [inst, index = size_t{0}](const Value& arg) mutable {
|
||||||
|
inst->SetArg(index, arg);
|
||||||
|
++index;
|
||||||
|
});
|
||||||
|
return result_it;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Block::AddBranch(Block* block) {
|
||||||
|
if (std::ranges::find(imm_successors, block) != imm_successors.end()) {
|
||||||
|
throw LogicError("Successor already inserted");
|
||||||
|
}
|
||||||
|
if (std::ranges::find(block->imm_predecessors, this) != block->imm_predecessors.end()) {
|
||||||
|
throw LogicError("Predecessor already inserted");
|
||||||
|
}
|
||||||
|
imm_successors.push_back(block);
|
||||||
|
block->imm_predecessors.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string BlockToIndex(const std::map<const Block*, size_t>& block_to_index,
|
||||||
|
Block* block) {
|
||||||
|
if (const auto it{block_to_index.find(block)}; it != block_to_index.end()) {
|
||||||
|
return fmt::format("{{Block ${}}}", it->second);
|
||||||
|
}
|
||||||
|
return fmt::format("$<unknown block {:016x}>", reinterpret_cast<u64>(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t InstIndex(std::map<const Inst*, size_t>& inst_to_index, size_t& inst_index,
|
||||||
|
const Inst* inst) {
|
||||||
|
const auto [it, is_inserted]{inst_to_index.emplace(inst, inst_index + 1)};
|
||||||
|
if (is_inserted) {
|
||||||
|
++inst_index;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string ArgToIndex(std::map<const Inst*, size_t>& inst_to_index, size_t& inst_index,
|
||||||
|
const Value& arg) {
|
||||||
|
if (arg.IsEmpty()) {
|
||||||
|
return "<null>";
|
||||||
|
}
|
||||||
|
if (!arg.IsImmediate() || arg.IsIdentity()) {
|
||||||
|
return fmt::format("%{}", InstIndex(inst_to_index, inst_index, arg.Inst()));
|
||||||
|
}
|
||||||
|
switch (arg.Type()) {
|
||||||
|
case Type::U1:
|
||||||
|
return fmt::format("#{}", arg.U1() ? "true" : "false");
|
||||||
|
case Type::U8:
|
||||||
|
return fmt::format("#{}", arg.U8());
|
||||||
|
case Type::U16:
|
||||||
|
return fmt::format("#{}", arg.U16());
|
||||||
|
case Type::U32:
|
||||||
|
return fmt::format("#{}", arg.U32());
|
||||||
|
case Type::U64:
|
||||||
|
return fmt::format("#{}", arg.U64());
|
||||||
|
case Type::F32:
|
||||||
|
return fmt::format("#{}", arg.F32());
|
||||||
|
case Type::ScalarReg:
|
||||||
|
return fmt::format("{}", arg.ScalarReg());
|
||||||
|
case Type::VectorReg:
|
||||||
|
return fmt::format("{}", arg.VectorReg());
|
||||||
|
case Type::Attribute:
|
||||||
|
return fmt::format("{}", arg.Attribute());
|
||||||
|
default:
|
||||||
|
return "<unknown immediate type>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DumpBlock(const Block& block) {
|
||||||
|
size_t inst_index{0};
|
||||||
|
std::map<const Inst*, size_t> inst_to_index;
|
||||||
|
return DumpBlock(block, {}, inst_to_index, inst_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DumpBlock(const Block& block, const std::map<const Block*, size_t>& block_to_index,
|
||||||
|
std::map<const Inst*, size_t>& inst_to_index, size_t& inst_index) {
|
||||||
|
std::string ret{"Block"};
|
||||||
|
if (const auto it{block_to_index.find(&block)}; it != block_to_index.end()) {
|
||||||
|
ret += fmt::format(" ${}", it->second);
|
||||||
|
}
|
||||||
|
ret += '\n';
|
||||||
|
for (const Inst& inst : block) {
|
||||||
|
const Opcode op{inst.GetOpcode()};
|
||||||
|
ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
|
||||||
|
if (TypeOf(op) != Type::Void) {
|
||||||
|
ret += fmt::format("%{:<5} = {}", InstIndex(inst_to_index, inst_index, &inst), op);
|
||||||
|
} else {
|
||||||
|
ret += fmt::format(" {}", op); // '%00000 = ' -> 1 + 5 + 3 = 9 spaces
|
||||||
|
}
|
||||||
|
const size_t arg_count{inst.NumArgs()};
|
||||||
|
for (size_t arg_index = 0; arg_index < arg_count; ++arg_index) {
|
||||||
|
const Value arg{inst.Arg(arg_index)};
|
||||||
|
const std::string arg_str{ArgToIndex(inst_to_index, inst_index, arg)};
|
||||||
|
ret += arg_index != 0 ? ", " : " ";
|
||||||
|
if (op == Opcode::Phi) {
|
||||||
|
ret += fmt::format("[ {}, {} ]", arg_str,
|
||||||
|
BlockToIndex(block_to_index, inst.PhiBlock(arg_index)));
|
||||||
|
} else {
|
||||||
|
ret += arg_str;
|
||||||
|
}
|
||||||
|
if (op != Opcode::Phi) {
|
||||||
|
const Type actual_type{arg.Type()};
|
||||||
|
const Type expected_type{ArgTypeOf(op, arg_index)};
|
||||||
|
if (!AreTypesCompatible(actual_type, expected_type)) {
|
||||||
|
ret += fmt::format("<type error: {} != {}>", actual_type, expected_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TypeOf(op) != Type::Void) {
|
||||||
|
ret += fmt::format(" (uses: {})\n", inst.UseCount());
|
||||||
|
} else {
|
||||||
|
ret += '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,180 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <map>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
#include <boost/intrusive/list.hpp>
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "shader_recompiler/ir/reg.h"
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
#include "shader_recompiler/object_pool.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
class Block {
|
||||||
|
public:
|
||||||
|
using InstructionList = boost::intrusive::list<Inst>;
|
||||||
|
using size_type = InstructionList::size_type;
|
||||||
|
using iterator = InstructionList::iterator;
|
||||||
|
using const_iterator = InstructionList::const_iterator;
|
||||||
|
using reverse_iterator = InstructionList::reverse_iterator;
|
||||||
|
using const_reverse_iterator = InstructionList::const_reverse_iterator;
|
||||||
|
|
||||||
|
explicit Block(ObjectPool<Inst>& inst_pool_);
|
||||||
|
~Block();
|
||||||
|
|
||||||
|
Block(const Block&) = delete;
|
||||||
|
Block& operator=(const Block&) = delete;
|
||||||
|
|
||||||
|
Block(Block&&) = default;
|
||||||
|
Block& operator=(Block&&) = default;
|
||||||
|
|
||||||
|
/// Appends a new instruction to the end of this basic block.
|
||||||
|
void AppendNewInst(Opcode op, std::initializer_list<Value> args);
|
||||||
|
|
||||||
|
/// Prepends a copy of an instruction to this basic block before the insertion point.
|
||||||
|
iterator PrependNewInst(iterator insertion_point, const Inst& base_inst);
|
||||||
|
|
||||||
|
/// Prepends a new instruction to this basic block before the insertion point.
|
||||||
|
iterator PrependNewInst(iterator insertion_point, Opcode op,
|
||||||
|
std::initializer_list<Value> args = {}, u32 flags = 0);
|
||||||
|
|
||||||
|
/// Adds a new branch to this basic block.
|
||||||
|
void AddBranch(Block* block);
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the instruction list for this basic block.
|
||||||
|
[[nodiscard]] InstructionList& Instructions() noexcept {
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
/// Gets an immutable reference to the instruction list for this basic block.
|
||||||
|
[[nodiscard]] const InstructionList& Instructions() const noexcept {
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets an immutable span to the immediate predecessors.
|
||||||
|
[[nodiscard]] std::span<Block* const> ImmPredecessors() const noexcept {
|
||||||
|
return imm_predecessors;
|
||||||
|
}
|
||||||
|
/// Gets an immutable span to the immediate successors.
|
||||||
|
[[nodiscard]] std::span<Block* const> ImmSuccessors() const noexcept {
|
||||||
|
return imm_successors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intrusively store the host definition of this instruction.
|
||||||
|
template <typename T>
|
||||||
|
void SetDefinition(T def) {
|
||||||
|
definition = std::bit_cast<u32>(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the intrusively stored host definition of this instruction.
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T Definition() const noexcept {
|
||||||
|
return std::bit_cast<T>(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SsaSeal() noexcept {
|
||||||
|
is_ssa_sealed = true;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool IsSsaSealed() const noexcept {
|
||||||
|
return is_ssa_sealed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return instructions.empty();
|
||||||
|
}
|
||||||
|
[[nodiscard]] size_type size() const {
|
||||||
|
return instructions.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Inst& front() {
|
||||||
|
return instructions.front();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const Inst& front() const {
|
||||||
|
return instructions.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Inst& back() {
|
||||||
|
return instructions.back();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const Inst& back() const {
|
||||||
|
return instructions.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] iterator begin() {
|
||||||
|
return instructions.begin();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const_iterator begin() const {
|
||||||
|
return instructions.begin();
|
||||||
|
}
|
||||||
|
[[nodiscard]] iterator end() {
|
||||||
|
return instructions.end();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const_iterator end() const {
|
||||||
|
return instructions.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] reverse_iterator rbegin() {
|
||||||
|
return instructions.rbegin();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const_reverse_iterator rbegin() const {
|
||||||
|
return instructions.rbegin();
|
||||||
|
}
|
||||||
|
[[nodiscard]] reverse_iterator rend() {
|
||||||
|
return instructions.rend();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const_reverse_iterator rend() const {
|
||||||
|
return instructions.rend();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const_iterator cbegin() const {
|
||||||
|
return instructions.cbegin();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const_iterator cend() const {
|
||||||
|
return instructions.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const_reverse_iterator crbegin() const {
|
||||||
|
return instructions.crbegin();
|
||||||
|
}
|
||||||
|
[[nodiscard]] const_reverse_iterator crend() const {
|
||||||
|
return instructions.crend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intrusively store the value of a register in the block.
|
||||||
|
std::array<Value, NumScalarRegs> ssa_sreg_values;
|
||||||
|
std::array<Value, NumVectorRegs> ssa_vreg_values;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Memory pool for instruction list
|
||||||
|
ObjectPool<Inst>* inst_pool;
|
||||||
|
|
||||||
|
/// List of instructions in this block
|
||||||
|
InstructionList instructions;
|
||||||
|
|
||||||
|
/// Block immediate predecessors
|
||||||
|
std::vector<Block*> imm_predecessors;
|
||||||
|
/// Block immediate successors
|
||||||
|
std::vector<Block*> imm_successors;
|
||||||
|
|
||||||
|
/// Intrusively store if the block is sealed in the SSA pass.
|
||||||
|
bool is_ssa_sealed{false};
|
||||||
|
|
||||||
|
/// Intrusively stored host definition of this block.
|
||||||
|
u32 definition{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using BlockList = std::vector<Block*>;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string DumpBlock(const Block& block);
|
||||||
|
|
||||||
|
[[nodiscard]] std::string DumpBlock(const Block& block,
|
||||||
|
const std::map<const Block*, size_t>& block_to_index,
|
||||||
|
std::map<const Inst*, size_t>& inst_to_index,
|
||||||
|
size_t& inst_index);
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,50 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
enum class Condition : u32 {
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
Scc0,
|
||||||
|
Scc1,
|
||||||
|
Vccz,
|
||||||
|
Vccnz,
|
||||||
|
Execz,
|
||||||
|
Execnz,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::string_view NameOf(Condition condition) {
|
||||||
|
switch (condition) {
|
||||||
|
case Condition::False:
|
||||||
|
return "False";
|
||||||
|
case Condition::True:
|
||||||
|
return "True";
|
||||||
|
case Condition::Scc0:
|
||||||
|
return "Scc0";
|
||||||
|
case Condition::Scc1:
|
||||||
|
return "Scc1";
|
||||||
|
case Condition::Vccz:
|
||||||
|
return "Vccz";
|
||||||
|
case Condition::Vccnz:
|
||||||
|
return "Vccnz";
|
||||||
|
case Condition::Execz:
|
||||||
|
return "Execz";
|
||||||
|
case Condition::Execnz:
|
||||||
|
return "Execnz";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<Shader::IR::Condition> : formatter<std::string_view> {
|
||||||
|
auto format(const Shader::IR::Condition& cond, format_context& ctx) const {
|
||||||
|
return formatter<string_view>::format(NameOf(cond), ctx);
|
||||||
|
}
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,250 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/attribute.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/condition.h"
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
class IREmitter {
|
||||||
|
public:
|
||||||
|
explicit IREmitter(Block& block_) : block{&block_}, insertion_point{block->end()} {}
|
||||||
|
explicit IREmitter(Block& block_, Block::iterator insertion_point_)
|
||||||
|
: block{&block_}, insertion_point{insertion_point_} {}
|
||||||
|
|
||||||
|
Block* block;
|
||||||
|
|
||||||
|
[[nodiscard]] U1 Imm1(bool value) const;
|
||||||
|
[[nodiscard]] U8 Imm8(u8 value) const;
|
||||||
|
[[nodiscard]] U16 Imm16(u16 value) const;
|
||||||
|
[[nodiscard]] U32 Imm32(u32 value) const;
|
||||||
|
[[nodiscard]] U32 Imm32(s32 value) const;
|
||||||
|
[[nodiscard]] F32 Imm32(f32 value) const;
|
||||||
|
[[nodiscard]] U64 Imm64(u64 value) const;
|
||||||
|
[[nodiscard]] U64 Imm64(s64 value) const;
|
||||||
|
[[nodiscard]] F64 Imm64(f64 value) const;
|
||||||
|
|
||||||
|
template <typename Dest, typename Source>
|
||||||
|
[[nodiscard]] Dest BitCast(const Source& value);
|
||||||
|
|
||||||
|
U1 ConditionRef(const U1& value);
|
||||||
|
void Reference(const Value& value);
|
||||||
|
|
||||||
|
void PhiMove(IR::Inst& phi, const Value& value);
|
||||||
|
|
||||||
|
void Prologue();
|
||||||
|
void Epilogue();
|
||||||
|
|
||||||
|
template <typename T = U32>
|
||||||
|
[[nodiscard]] T GetScalarReg(IR::ScalarReg reg);
|
||||||
|
template <typename T = U32>
|
||||||
|
[[nodiscard]] T GetVectorReg(IR::VectorReg reg);
|
||||||
|
void SetScalarReg(IR::ScalarReg reg, const U32F32& value);
|
||||||
|
void SetVectorReg(IR::VectorReg reg, const U32F32& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U1 GetGotoVariable(u32 id);
|
||||||
|
void SetGotoVariable(u32 id, const U1& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U1 GetVcc();
|
||||||
|
|
||||||
|
void SetVcc(const U1& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U1 Condition(IR::Condition cond);
|
||||||
|
|
||||||
|
[[nodiscard]] F32 GetAttribute(IR::Attribute attribute);
|
||||||
|
[[nodiscard]] F32 GetAttribute(IR::Attribute attribute, u32 comp);
|
||||||
|
[[nodiscard]] U32 GetAttributeU32(IR::Attribute attribute);
|
||||||
|
[[nodiscard]] U32 GetAttributeU32(IR::Attribute attribute, u32 comp);
|
||||||
|
void SetAttribute(IR::Attribute attribute, const F32& value, u32 comp);
|
||||||
|
|
||||||
|
[[nodiscard]] U32U64 ReadShared(int bit_size, bool is_signed, const U32& offset);
|
||||||
|
void WriteShared(int bit_size, const Value& value, const U32& offset);
|
||||||
|
|
||||||
|
[[nodiscard]] U32 ReadConst(const U64& address, const U32& offset);
|
||||||
|
template <typename T = U32>
|
||||||
|
[[nodiscard]] T ReadConstBuffer(const Value& handle, const U32& index, const U32& offset);
|
||||||
|
|
||||||
|
[[nodiscard]] U1 GetZeroFromOp(const Value& op);
|
||||||
|
[[nodiscard]] U1 GetSignFromOp(const Value& op);
|
||||||
|
[[nodiscard]] U1 GetCarryFromOp(const Value& op);
|
||||||
|
[[nodiscard]] U1 GetOverflowFromOp(const Value& op);
|
||||||
|
[[nodiscard]] U1 GetSparseFromOp(const Value& op);
|
||||||
|
[[nodiscard]] U1 GetInBoundsFromOp(const Value& op);
|
||||||
|
|
||||||
|
[[nodiscard]] Value CompositeConstruct(const Value& e1, const Value& e2);
|
||||||
|
[[nodiscard]] Value CompositeConstruct(const Value& e1, const Value& e2, const Value& e3);
|
||||||
|
[[nodiscard]] Value CompositeConstruct(const Value& e1, const Value& e2, const Value& e3,
|
||||||
|
const Value& e4);
|
||||||
|
[[nodiscard]] Value CompositeExtract(const Value& vector, size_t element);
|
||||||
|
[[nodiscard]] Value CompositeInsert(const Value& vector, const Value& object, size_t element);
|
||||||
|
|
||||||
|
[[nodiscard]] Value Select(const U1& condition, const Value& true_value,
|
||||||
|
const Value& false_value);
|
||||||
|
|
||||||
|
[[nodiscard]] U64 PackUint2x32(const Value& vector);
|
||||||
|
[[nodiscard]] Value UnpackUint2x32(const U64& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U32 PackFloat2x16(const Value& vector);
|
||||||
|
[[nodiscard]] Value UnpackFloat2x16(const U32& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U32 PackHalf2x16(const Value& vector);
|
||||||
|
[[nodiscard]] Value UnpackHalf2x16(const U32& value);
|
||||||
|
|
||||||
|
[[nodiscard]] F32F64 FPAdd(const F32F64& a, const F32F64& b);
|
||||||
|
[[nodiscard]] F32F64 FPMul(const F32F64& a, const F32F64& b);
|
||||||
|
[[nodiscard]] F32F64 FPFma(const F32F64& a, const F32F64& b, const F32F64& c);
|
||||||
|
|
||||||
|
[[nodiscard]] F32F64 FPAbs(const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 FPNeg(const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 FPAbsNeg(const F32F64& value, bool abs, bool neg);
|
||||||
|
|
||||||
|
[[nodiscard]] F32 FPCos(const F32& value);
|
||||||
|
[[nodiscard]] F32 FPSin(const F32& value);
|
||||||
|
[[nodiscard]] F32 FPExp2(const F32& value);
|
||||||
|
[[nodiscard]] F32 FPLog2(const F32& value);
|
||||||
|
[[nodiscard]] F32F64 FPRecip(const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 FPRecipSqrt(const F32F64& value);
|
||||||
|
[[nodiscard]] F32 FPSqrt(const F32& value);
|
||||||
|
[[nodiscard]] F32F64 FPSaturate(const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 FPClamp(const F32F64& value, const F32F64& min_value,
|
||||||
|
const F32F64& max_value);
|
||||||
|
[[nodiscard]] F32F64 FPRoundEven(const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 FPFloor(const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 FPCeil(const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 FPTrunc(const F32F64& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U1 FPEqual(const F32F64& lhs, const F32F64& rhs, bool ordered = true);
|
||||||
|
[[nodiscard]] U1 FPNotEqual(const F32F64& lhs, const F32F64& rhs, bool ordered = true);
|
||||||
|
[[nodiscard]] U1 FPLessThanEqual(const F32F64& lhs, const F32F64& rhs, bool ordered = true);
|
||||||
|
[[nodiscard]] U1 FPGreaterThanEqual(const F32F64& lhs, const F32F64& rhs, bool ordered = true);
|
||||||
|
[[nodiscard]] U1 FPLessThan(const F32F64& lhs, const F32F64& rhs, bool ordered = true);
|
||||||
|
[[nodiscard]] U1 FPGreaterThan(const F32F64& lhs, const F32F64& rhs, bool ordered = true);
|
||||||
|
[[nodiscard]] U1 FPIsNan(const F32F64& value);
|
||||||
|
[[nodiscard]] U1 FPOrdered(const F32F64& lhs, const F32F64& rhs);
|
||||||
|
[[nodiscard]] U1 FPUnordered(const F32F64& lhs, const F32F64& rhs);
|
||||||
|
[[nodiscard]] F32F64 FPMax(const F32F64& lhs, const F32F64& rhs);
|
||||||
|
[[nodiscard]] F32F64 FPMin(const F32F64& lhs, const F32F64& rhs);
|
||||||
|
|
||||||
|
[[nodiscard]] U32U64 IAdd(const U32U64& a, const U32U64& b);
|
||||||
|
[[nodiscard]] U32U64 ISub(const U32U64& a, const U32U64& b);
|
||||||
|
[[nodiscard]] U32 IMul(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 IDiv(const U32& a, const U32& b, bool is_signed = false);
|
||||||
|
[[nodiscard]] U32U64 INeg(const U32U64& value);
|
||||||
|
[[nodiscard]] U32 IAbs(const U32& value);
|
||||||
|
[[nodiscard]] U32U64 ShiftLeftLogical(const U32U64& base, const U32& shift);
|
||||||
|
[[nodiscard]] U32U64 ShiftRightLogical(const U32U64& base, const U32& shift);
|
||||||
|
[[nodiscard]] U32U64 ShiftRightArithmetic(const U32U64& base, const U32& shift);
|
||||||
|
[[nodiscard]] U32 BitwiseAnd(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 BitwiseOr(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 BitwiseXor(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 BitFieldInsert(const U32& base, const U32& insert, const U32& offset,
|
||||||
|
const U32& count);
|
||||||
|
[[nodiscard]] U32 BitFieldExtract(const U32& base, const U32& offset, const U32& count,
|
||||||
|
bool is_signed = false);
|
||||||
|
[[nodiscard]] U32 BitReverse(const U32& value);
|
||||||
|
[[nodiscard]] U32 BitCount(const U32& value);
|
||||||
|
[[nodiscard]] U32 BitwiseNot(const U32& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U32 FindSMsb(const U32& value);
|
||||||
|
[[nodiscard]] U32 FindUMsb(const U32& value);
|
||||||
|
[[nodiscard]] U32 SMin(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 UMin(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 IMin(const U32& a, const U32& b, bool is_signed);
|
||||||
|
[[nodiscard]] U32 SMax(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 UMax(const U32& a, const U32& b);
|
||||||
|
[[nodiscard]] U32 IMax(const U32& a, const U32& b, bool is_signed);
|
||||||
|
[[nodiscard]] U32 SClamp(const U32& value, const U32& min, const U32& max);
|
||||||
|
[[nodiscard]] U32 UClamp(const U32& value, const U32& min, const U32& max);
|
||||||
|
|
||||||
|
[[nodiscard]] U1 ILessThan(const U32& lhs, const U32& rhs, bool is_signed);
|
||||||
|
[[nodiscard]] U1 IEqual(const U32U64& lhs, const U32U64& rhs);
|
||||||
|
[[nodiscard]] U1 ILessThanEqual(const U32& lhs, const U32& rhs, bool is_signed);
|
||||||
|
[[nodiscard]] U1 IGreaterThan(const U32& lhs, const U32& rhs, bool is_signed);
|
||||||
|
[[nodiscard]] U1 INotEqual(const U32& lhs, const U32& rhs);
|
||||||
|
[[nodiscard]] U1 IGreaterThanEqual(const U32& lhs, const U32& rhs, bool is_signed);
|
||||||
|
|
||||||
|
[[nodiscard]] U1 LogicalOr(const U1& a, const U1& b);
|
||||||
|
[[nodiscard]] U1 LogicalAnd(const U1& a, const U1& b);
|
||||||
|
[[nodiscard]] U1 LogicalXor(const U1& a, const U1& b);
|
||||||
|
[[nodiscard]] U1 LogicalNot(const U1& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U32U64 ConvertFToS(size_t bitsize, const F32F64& value);
|
||||||
|
[[nodiscard]] U32U64 ConvertFToU(size_t bitsize, const F32F64& value);
|
||||||
|
[[nodiscard]] U32U64 ConvertFToI(size_t bitsize, bool is_signed, const F32F64& value);
|
||||||
|
[[nodiscard]] F32F64 ConvertSToF(size_t dest_bitsize, size_t src_bitsize, const Value& value);
|
||||||
|
[[nodiscard]] F32F64 ConvertUToF(size_t dest_bitsize, size_t src_bitsize, const Value& value);
|
||||||
|
[[nodiscard]] F32F64 ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_signed,
|
||||||
|
const Value& value);
|
||||||
|
|
||||||
|
[[nodiscard]] U32U64 UConvert(size_t result_bitsize, const U32U64& value);
|
||||||
|
[[nodiscard]] F16F32F64 FPConvert(size_t result_bitsize, const F16F32F64& value);
|
||||||
|
|
||||||
|
[[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& coords,
|
||||||
|
const F32& bias, const Value& offset,
|
||||||
|
const F32& lod_clamp, TextureInstInfo info);
|
||||||
|
[[nodiscard]] Value ImageSampleExplicitLod(const Value& handle, const Value& coords,
|
||||||
|
const F32& lod, const Value& offset,
|
||||||
|
TextureInstInfo info);
|
||||||
|
[[nodiscard]] F32 ImageSampleDrefImplicitLod(const Value& handle, const Value& coords,
|
||||||
|
const F32& dref, const F32& bias,
|
||||||
|
const Value& offset, const F32& lod_clamp,
|
||||||
|
TextureInstInfo info);
|
||||||
|
[[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& coords,
|
||||||
|
const F32& dref, const F32& lod,
|
||||||
|
const Value& offset, TextureInstInfo info);
|
||||||
|
[[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod,
|
||||||
|
const IR::U1& skip_mips);
|
||||||
|
[[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod,
|
||||||
|
const IR::U1& skip_mips, TextureInstInfo info);
|
||||||
|
|
||||||
|
[[nodiscard]] Value ImageQueryLod(const Value& handle, const Value& coords,
|
||||||
|
TextureInstInfo info);
|
||||||
|
[[nodiscard]] Value ImageGather(const Value& handle, const Value& coords, const Value& offset,
|
||||||
|
const Value& offset2, TextureInstInfo info);
|
||||||
|
[[nodiscard]] Value ImageGatherDref(const Value& handle, const Value& coords,
|
||||||
|
const Value& offset, const Value& offset2, const F32& dref,
|
||||||
|
TextureInstInfo info);
|
||||||
|
[[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const Value& offset,
|
||||||
|
const U32& lod, const U32& multisampling, TextureInstInfo info);
|
||||||
|
[[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords,
|
||||||
|
const Value& derivatives, const Value& offset,
|
||||||
|
const F32& lod_clamp, TextureInstInfo info);
|
||||||
|
[[nodiscard]] Value ImageRead(const Value& handle, const Value& coords, TextureInstInfo info);
|
||||||
|
void ImageWrite(const Value& handle, const Value& coords, const Value& color,
|
||||||
|
TextureInstInfo info);
|
||||||
|
|
||||||
|
private:
|
||||||
|
IR::Block::iterator insertion_point;
|
||||||
|
|
||||||
|
template <typename T = Value, typename... Args>
|
||||||
|
T Inst(Opcode op, Args... args) {
|
||||||
|
auto it{block->PrependNewInst(insertion_point, op, {Value{args}...})};
|
||||||
|
return T{Value{&*it}};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires(sizeof(T) <= sizeof(u32) && std::is_trivially_copyable_v<T>)
|
||||||
|
struct Flags {
|
||||||
|
Flags() = default;
|
||||||
|
Flags(T proxy_) : proxy{proxy_} {}
|
||||||
|
|
||||||
|
T proxy;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T = Value, typename FlagType, typename... Args>
|
||||||
|
T Inst(Opcode op, Flags<FlagType> flags, Args... args) {
|
||||||
|
u32 raw_flags{};
|
||||||
|
std::memcpy(&raw_flags, &flags.proxy, sizeof(flags.proxy));
|
||||||
|
auto it{block->PrependNewInst(insertion_point, op, {Value{args}...}, raw_flags)};
|
||||||
|
return T{Value{&*it}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,167 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "shader_recompiler/exception.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/type.h"
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
Inst::Inst(IR::Opcode op_, u32 flags_) noexcept : op{op_}, flags{flags_} {
|
||||||
|
if (op == Opcode::Phi) {
|
||||||
|
std::construct_at(&phi_args);
|
||||||
|
} else {
|
||||||
|
std::construct_at(&args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Inst::Inst(const Inst& base) : op{base.op}, flags{base.flags} {
|
||||||
|
if (base.op == Opcode::Phi) {
|
||||||
|
throw NotImplementedException("Copying phi node");
|
||||||
|
}
|
||||||
|
std::construct_at(&args);
|
||||||
|
const size_t num_args{base.NumArgs()};
|
||||||
|
for (size_t index = 0; index < num_args; ++index) {
|
||||||
|
SetArg(index, base.Arg(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Inst::~Inst() {
|
||||||
|
if (op == Opcode::Phi) {
|
||||||
|
std::destroy_at(&phi_args);
|
||||||
|
} else {
|
||||||
|
std::destroy_at(&args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Inst::MayHaveSideEffects() const noexcept {
|
||||||
|
switch (op) {
|
||||||
|
case Opcode::ConditionRef:
|
||||||
|
case Opcode::Reference:
|
||||||
|
case Opcode::PhiMove:
|
||||||
|
case Opcode::Prologue:
|
||||||
|
case Opcode::Epilogue:
|
||||||
|
// case Opcode::Join:
|
||||||
|
// case Opcode::Barrier:
|
||||||
|
// case Opcode::WorkgroupMemoryBarrier:
|
||||||
|
// case Opcode::DeviceMemoryBarrier:
|
||||||
|
// case Opcode::EmitVertex:
|
||||||
|
// case Opcode::EndPrimitive:
|
||||||
|
case Opcode::SetAttribute:
|
||||||
|
// case Opcode::SetFragColor:
|
||||||
|
// case Opcode::SetFragDepth:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Inst::AreAllArgsImmediates() const {
|
||||||
|
if (op == Opcode::Phi) {
|
||||||
|
throw LogicError("Testing for all arguments are immediates on phi instruction");
|
||||||
|
}
|
||||||
|
return std::all_of(args.begin(), args.begin() + NumArgs(),
|
||||||
|
[](const IR::Value& value) { return value.IsImmediate(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::Type Inst::Type() const {
|
||||||
|
return TypeOf(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::SetArg(size_t index, Value value) {
|
||||||
|
if (index >= NumArgs()) {
|
||||||
|
throw InvalidArgument("Out of bounds argument index {} in opcode {}", index, op);
|
||||||
|
}
|
||||||
|
const IR::Value arg{Arg(index)};
|
||||||
|
if (!arg.IsImmediate()) {
|
||||||
|
UndoUse(arg);
|
||||||
|
}
|
||||||
|
if (!value.IsImmediate()) {
|
||||||
|
Use(value);
|
||||||
|
}
|
||||||
|
if (op == Opcode::Phi) {
|
||||||
|
phi_args[index].second = value;
|
||||||
|
} else {
|
||||||
|
args[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Block* Inst::PhiBlock(size_t index) const {
|
||||||
|
if (op != Opcode::Phi) {
|
||||||
|
throw LogicError("{} is not a Phi instruction", op);
|
||||||
|
}
|
||||||
|
if (index >= phi_args.size()) {
|
||||||
|
throw InvalidArgument("Out of bounds argument index {} in phi instruction");
|
||||||
|
}
|
||||||
|
return phi_args[index].first;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::AddPhiOperand(Block* predecessor, const Value& value) {
|
||||||
|
if (!value.IsImmediate()) {
|
||||||
|
Use(value);
|
||||||
|
}
|
||||||
|
phi_args.emplace_back(predecessor, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::Invalidate() {
|
||||||
|
ClearArgs();
|
||||||
|
ReplaceOpcode(Opcode::Void);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::ClearArgs() {
|
||||||
|
if (op == Opcode::Phi) {
|
||||||
|
for (auto& pair : phi_args) {
|
||||||
|
IR::Value& value{pair.second};
|
||||||
|
if (!value.IsImmediate()) {
|
||||||
|
UndoUse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
phi_args.clear();
|
||||||
|
} else {
|
||||||
|
for (auto& value : args) {
|
||||||
|
if (!value.IsImmediate()) {
|
||||||
|
UndoUse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reset arguments to null
|
||||||
|
// std::memset was measured to be faster on MSVC than std::ranges:fill
|
||||||
|
std::memset(reinterpret_cast<char*>(&args), 0, sizeof(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::ReplaceUsesWith(Value replacement) {
|
||||||
|
Invalidate();
|
||||||
|
ReplaceOpcode(Opcode::Identity);
|
||||||
|
if (!replacement.IsImmediate()) {
|
||||||
|
Use(replacement);
|
||||||
|
}
|
||||||
|
args[0] = replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::ReplaceOpcode(IR::Opcode opcode) {
|
||||||
|
if (opcode == IR::Opcode::Phi) {
|
||||||
|
throw LogicError("Cannot transition into Phi");
|
||||||
|
}
|
||||||
|
if (op == Opcode::Phi) {
|
||||||
|
// Transition out of phi arguments into non-phi
|
||||||
|
std::destroy_at(&phi_args);
|
||||||
|
std::construct_at(&args);
|
||||||
|
}
|
||||||
|
op = opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::Use(const Value& value) {
|
||||||
|
Inst* const inst{value.Inst()};
|
||||||
|
++inst->use_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::UndoUse(const Value& value) {
|
||||||
|
Inst* const inst{value.Inst()};
|
||||||
|
--inst->use_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/opcodes.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
std::string_view NameOf(Opcode op) {
|
||||||
|
return Detail::META_TABLE[static_cast<size_t>(op)].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,107 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "shader_recompiler/ir/type.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
enum class Opcode {
|
||||||
|
#define OPCODE(name, ...) name,
|
||||||
|
#include "opcodes.inc"
|
||||||
|
#undef OPCODE
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Detail {
|
||||||
|
struct OpcodeMeta {
|
||||||
|
std::string_view name;
|
||||||
|
Type type;
|
||||||
|
std::array<Type, 5> arg_types;
|
||||||
|
};
|
||||||
|
|
||||||
|
// using enum Type;
|
||||||
|
constexpr Type Void{Type::Void};
|
||||||
|
constexpr Type Opaque{Type::Opaque};
|
||||||
|
constexpr Type ScalarReg{Type::ScalarReg};
|
||||||
|
constexpr Type VectorReg{Type::VectorReg};
|
||||||
|
constexpr Type Attribute{Type::Attribute};
|
||||||
|
constexpr Type SystemValue{Type::SystemValue};
|
||||||
|
constexpr Type U1{Type::U1};
|
||||||
|
constexpr Type U8{Type::U8};
|
||||||
|
constexpr Type U16{Type::U16};
|
||||||
|
constexpr Type U32{Type::U32};
|
||||||
|
constexpr Type U64{Type::U64};
|
||||||
|
constexpr Type F16{Type::F16};
|
||||||
|
constexpr Type F32{Type::F32};
|
||||||
|
constexpr Type F64{Type::F64};
|
||||||
|
constexpr Type U32x2{Type::U32x2};
|
||||||
|
constexpr Type U32x3{Type::U32x3};
|
||||||
|
constexpr Type U32x4{Type::U32x4};
|
||||||
|
constexpr Type F16x2{Type::F16x2};
|
||||||
|
constexpr Type F16x3{Type::F16x3};
|
||||||
|
constexpr Type F16x4{Type::F16x4};
|
||||||
|
constexpr Type F32x2{Type::F32x2};
|
||||||
|
constexpr Type F32x3{Type::F32x3};
|
||||||
|
constexpr Type F32x4{Type::F32x4};
|
||||||
|
constexpr Type F64x2{Type::F64x2};
|
||||||
|
constexpr Type F64x3{Type::F64x3};
|
||||||
|
constexpr Type F64x4{Type::F64x4};
|
||||||
|
|
||||||
|
constexpr OpcodeMeta META_TABLE[]{
|
||||||
|
#define OPCODE(name_token, type_token, ...) \
|
||||||
|
{ \
|
||||||
|
.name{#name_token}, \
|
||||||
|
.type = type_token, \
|
||||||
|
.arg_types{__VA_ARGS__}, \
|
||||||
|
},
|
||||||
|
#include "opcodes.inc"
|
||||||
|
#undef OPCODE
|
||||||
|
};
|
||||||
|
constexpr size_t CalculateNumArgsOf(Opcode op) {
|
||||||
|
const auto& arg_types{META_TABLE[static_cast<size_t>(op)].arg_types};
|
||||||
|
return static_cast<size_t>(
|
||||||
|
std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void)));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u8 NUM_ARGS[]{
|
||||||
|
#define OPCODE(name_token, type_token, ...) static_cast<u8>(CalculateNumArgsOf(Opcode::name_token)),
|
||||||
|
#include "opcodes.inc"
|
||||||
|
#undef OPCODE
|
||||||
|
};
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
/// Get return type of an opcode
|
||||||
|
[[nodiscard]] inline Type TypeOf(Opcode op) noexcept {
|
||||||
|
return Detail::META_TABLE[static_cast<size_t>(op)].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of arguments an opcode accepts
|
||||||
|
[[nodiscard]] inline size_t NumArgsOf(Opcode op) noexcept {
|
||||||
|
return static_cast<size_t>(Detail::NUM_ARGS[static_cast<size_t>(op)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the required type of an argument of an opcode
|
||||||
|
[[nodiscard]] inline Type ArgTypeOf(Opcode op, size_t arg_index) noexcept {
|
||||||
|
return Detail::META_TABLE[static_cast<size_t>(op)].arg_types[arg_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the name of an opcode
|
||||||
|
[[nodiscard]] std::string_view NameOf(Opcode op);
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<Shader::IR::Opcode> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const Shader::IR::Opcode& op, FormatContext& ctx) const {
|
||||||
|
return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(op));
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,247 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// opcode name, return type, arg1 type, arg2 type, arg3 type, arg4 type, arg4 type, ...
|
||||||
|
OPCODE(Phi, Opaque, )
|
||||||
|
OPCODE(Identity, Opaque, Opaque, )
|
||||||
|
OPCODE(Void, Void, )
|
||||||
|
OPCODE(ConditionRef, U1, U1, )
|
||||||
|
OPCODE(Reference, Void, Opaque, )
|
||||||
|
OPCODE(PhiMove, Void, Opaque, Opaque, )
|
||||||
|
|
||||||
|
// Special operations
|
||||||
|
OPCODE(Prologue, Void, )
|
||||||
|
OPCODE(Epilogue, Void, )
|
||||||
|
|
||||||
|
// Constant memory operations
|
||||||
|
OPCODE(ReadConst, U32, U64, U32, )
|
||||||
|
OPCODE(ReadConstBuffer, U32, Opaque, U32, U32 )
|
||||||
|
OPCODE(ReadConstBufferF32, F32, Opaque, U32, U32 )
|
||||||
|
|
||||||
|
// Context getters/setters
|
||||||
|
OPCODE(GetScalarRegister, U32, ScalarReg, )
|
||||||
|
OPCODE(SetScalarRegister, Void, ScalarReg, U32, )
|
||||||
|
OPCODE(GetVectorRegister, U32, VectorReg, )
|
||||||
|
OPCODE(SetVectorRegister, Void, VectorReg, U32, )
|
||||||
|
OPCODE(GetGotoVariable, U1, U32, )
|
||||||
|
OPCODE(SetGotoVariable, Void, U32, U1, )
|
||||||
|
OPCODE(GetAttribute, F32, Attribute, U32, )
|
||||||
|
OPCODE(GetAttributeU32, U32, Attribute, U32, )
|
||||||
|
OPCODE(SetAttribute, Void, Attribute, F32, U32, )
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
//OPCODE(GetScc, U1, Void, )
|
||||||
|
OPCODE(GetVcc, U1, Void, )
|
||||||
|
//OPCODE(SetScc, Void, U1, )
|
||||||
|
OPCODE(SetVcc, Void, U1, )
|
||||||
|
|
||||||
|
// Undefined
|
||||||
|
OPCODE(UndefU1, U1, )
|
||||||
|
OPCODE(UndefU8, U8, )
|
||||||
|
OPCODE(UndefU16, U16, )
|
||||||
|
OPCODE(UndefU32, U32, )
|
||||||
|
OPCODE(UndefU64, U64, )
|
||||||
|
|
||||||
|
// Vector utility
|
||||||
|
OPCODE(CompositeConstructU32x2, U32x2, U32, U32, )
|
||||||
|
OPCODE(CompositeConstructU32x3, U32x3, U32, U32, U32, )
|
||||||
|
OPCODE(CompositeConstructU32x4, U32x4, U32, U32, U32, U32, )
|
||||||
|
OPCODE(CompositeExtractU32x2, U32, U32x2, U32, )
|
||||||
|
OPCODE(CompositeExtractU32x3, U32, U32x3, U32, )
|
||||||
|
OPCODE(CompositeExtractU32x4, U32, U32x4, U32, )
|
||||||
|
OPCODE(CompositeInsertU32x2, U32x2, U32x2, U32, U32, )
|
||||||
|
OPCODE(CompositeInsertU32x3, U32x3, U32x3, U32, U32, )
|
||||||
|
OPCODE(CompositeInsertU32x4, U32x4, U32x4, U32, U32, )
|
||||||
|
OPCODE(CompositeConstructF16x2, F16x2, F16, F16, )
|
||||||
|
OPCODE(CompositeConstructF16x3, F16x3, F16, F16, F16, )
|
||||||
|
OPCODE(CompositeConstructF16x4, F16x4, F16, F16, F16, F16, )
|
||||||
|
OPCODE(CompositeExtractF16x2, F16, F16x2, U32, )
|
||||||
|
OPCODE(CompositeExtractF16x3, F16, F16x3, U32, )
|
||||||
|
OPCODE(CompositeExtractF16x4, F16, F16x4, U32, )
|
||||||
|
OPCODE(CompositeInsertF16x2, F16x2, F16x2, F16, U32, )
|
||||||
|
OPCODE(CompositeInsertF16x3, F16x3, F16x3, F16, U32, )
|
||||||
|
OPCODE(CompositeInsertF16x4, F16x4, F16x4, F16, U32, )
|
||||||
|
OPCODE(CompositeConstructF32x2, F32x2, F32, F32, )
|
||||||
|
OPCODE(CompositeConstructF32x3, F32x3, F32, F32, F32, )
|
||||||
|
OPCODE(CompositeConstructF32x4, F32x4, F32, F32, F32, F32, )
|
||||||
|
OPCODE(CompositeExtractF32x2, F32, F32x2, U32, )
|
||||||
|
OPCODE(CompositeExtractF32x3, F32, F32x3, U32, )
|
||||||
|
OPCODE(CompositeExtractF32x4, F32, F32x4, U32, )
|
||||||
|
OPCODE(CompositeInsertF32x2, F32x2, F32x2, F32, U32, )
|
||||||
|
OPCODE(CompositeInsertF32x3, F32x3, F32x3, F32, U32, )
|
||||||
|
OPCODE(CompositeInsertF32x4, F32x4, F32x4, F32, U32, )
|
||||||
|
OPCODE(CompositeConstructF64x2, F64x2, F64, F64, )
|
||||||
|
OPCODE(CompositeConstructF64x3, F64x3, F64, F64, F64, )
|
||||||
|
OPCODE(CompositeConstructF64x4, F64x4, F64, F64, F64, F64, )
|
||||||
|
OPCODE(CompositeExtractF64x2, F64, F64x2, U32, )
|
||||||
|
OPCODE(CompositeExtractF64x3, F64, F64x3, U32, )
|
||||||
|
OPCODE(CompositeExtractF64x4, F64, F64x4, U32, )
|
||||||
|
OPCODE(CompositeInsertF64x2, F64x2, F64x2, F64, U32, )
|
||||||
|
OPCODE(CompositeInsertF64x3, F64x3, F64x3, F64, U32, )
|
||||||
|
OPCODE(CompositeInsertF64x4, F64x4, F64x4, F64, U32, )
|
||||||
|
|
||||||
|
// Select operations
|
||||||
|
OPCODE(SelectU1, U1, U1, U1, U1, )
|
||||||
|
OPCODE(SelectU8, U8, U1, U8, U8, )
|
||||||
|
OPCODE(SelectU16, U16, U1, U16, U16, )
|
||||||
|
OPCODE(SelectU32, U32, U1, U32, U32, )
|
||||||
|
OPCODE(SelectU64, U64, U1, U64, U64, )
|
||||||
|
OPCODE(SelectF32, F32, U1, F32, F32, )
|
||||||
|
OPCODE(SelectF64, F64, U1, F64, F64, )
|
||||||
|
|
||||||
|
// Bitwise conversions
|
||||||
|
OPCODE(BitCastU16F16, U16, F16, )
|
||||||
|
OPCODE(BitCastU32F32, U32, F32, )
|
||||||
|
OPCODE(BitCastU64F64, U64, F64, )
|
||||||
|
OPCODE(BitCastF16U16, F16, U16, )
|
||||||
|
OPCODE(BitCastF32U32, F32, U32, )
|
||||||
|
OPCODE(BitCastF64U64, F64, U64, )
|
||||||
|
OPCODE(PackUint2x32, U64, U32x2, )
|
||||||
|
OPCODE(UnpackUint2x32, U32x2, U64, )
|
||||||
|
OPCODE(PackFloat2x16, U32, F16x2, )
|
||||||
|
OPCODE(UnpackFloat2x16, F16x2, U32, )
|
||||||
|
OPCODE(PackHalf2x16, U32, F32x2, )
|
||||||
|
OPCODE(UnpackHalf2x16, F32x2, U32, )
|
||||||
|
|
||||||
|
// Floating-point operations
|
||||||
|
OPCODE(FPAbs32, F32, F32, )
|
||||||
|
OPCODE(FPAbs64, F64, F64, )
|
||||||
|
OPCODE(FPAdd32, F32, F32, F32, )
|
||||||
|
OPCODE(FPAdd64, F64, F64, F64, )
|
||||||
|
OPCODE(FPFma32, F32, F32, F32, F32, )
|
||||||
|
OPCODE(FPFma64, F64, F64, F64, F64, )
|
||||||
|
OPCODE(FPMax32, F32, F32, F32, )
|
||||||
|
OPCODE(FPMax64, F64, F64, F64, )
|
||||||
|
OPCODE(FPMin32, F32, F32, F32, )
|
||||||
|
OPCODE(FPMin64, F64, F64, F64, )
|
||||||
|
OPCODE(FPMul32, F32, F32, F32, )
|
||||||
|
OPCODE(FPMul64, F64, F64, F64, )
|
||||||
|
OPCODE(FPNeg32, F32, F32, )
|
||||||
|
OPCODE(FPNeg64, F64, F64, )
|
||||||
|
OPCODE(FPRecip32, F32, F32, )
|
||||||
|
OPCODE(FPRecip64, F64, F64, )
|
||||||
|
OPCODE(FPRecipSqrt32, F32, F32, )
|
||||||
|
OPCODE(FPRecipSqrt64, F64, F64, )
|
||||||
|
OPCODE(FPSqrt, F32, F32, )
|
||||||
|
OPCODE(FPSin, F32, F32, )
|
||||||
|
OPCODE(FPExp2, F32, F32, )
|
||||||
|
OPCODE(FPCos, F32, F32, )
|
||||||
|
OPCODE(FPLog2, F32, F32, )
|
||||||
|
OPCODE(FPSaturate32, F32, F32, )
|
||||||
|
OPCODE(FPSaturate64, F64, F64, )
|
||||||
|
OPCODE(FPClamp32, F32, F32, F32, F32, )
|
||||||
|
OPCODE(FPClamp64, F64, F64, F64, F64, )
|
||||||
|
OPCODE(FPRoundEven32, F32, F32, )
|
||||||
|
OPCODE(FPRoundEven64, F64, F64, )
|
||||||
|
OPCODE(FPFloor32, F32, F32, )
|
||||||
|
OPCODE(FPFloor64, F64, F64, )
|
||||||
|
OPCODE(FPCeil32, F32, F32, )
|
||||||
|
OPCODE(FPCeil64, F64, F64, )
|
||||||
|
OPCODE(FPTrunc32, F32, F32, )
|
||||||
|
OPCODE(FPTrunc64, F64, F64, )
|
||||||
|
|
||||||
|
OPCODE(FPOrdEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPOrdEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPUnordEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPUnordEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPOrdNotEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPOrdNotEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPUnordNotEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPUnordNotEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPOrdLessThan32, U1, F32, F32, )
|
||||||
|
OPCODE(FPOrdLessThan64, U1, F64, F64, )
|
||||||
|
OPCODE(FPUnordLessThan32, U1, F32, F32, )
|
||||||
|
OPCODE(FPUnordLessThan64, U1, F64, F64, )
|
||||||
|
OPCODE(FPOrdGreaterThan32, U1, F32, F32, )
|
||||||
|
OPCODE(FPOrdGreaterThan64, U1, F64, F64, )
|
||||||
|
OPCODE(FPUnordGreaterThan32, U1, F32, F32, )
|
||||||
|
OPCODE(FPUnordGreaterThan64, U1, F64, F64, )
|
||||||
|
OPCODE(FPOrdLessThanEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPOrdLessThanEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPUnordLessThanEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPUnordLessThanEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPOrdGreaterThanEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPOrdGreaterThanEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPUnordGreaterThanEqual32, U1, F32, F32, )
|
||||||
|
OPCODE(FPUnordGreaterThanEqual64, U1, F64, F64, )
|
||||||
|
OPCODE(FPIsNan32, U1, F32, )
|
||||||
|
OPCODE(FPIsNan64, U1, F64, )
|
||||||
|
|
||||||
|
// Integer operations
|
||||||
|
OPCODE(IAdd32, U32, U32, U32, )
|
||||||
|
OPCODE(IAdd64, U64, U64, U64, )
|
||||||
|
OPCODE(ISub32, U32, U32, U32, )
|
||||||
|
OPCODE(ISub64, U64, U64, U64, )
|
||||||
|
OPCODE(IMul32, U32, U32, U32, )
|
||||||
|
OPCODE(SDiv32, U32, U32, U32, )
|
||||||
|
OPCODE(UDiv32, U32, U32, U32, )
|
||||||
|
OPCODE(INeg32, U32, U32, )
|
||||||
|
OPCODE(INeg64, U64, U64, )
|
||||||
|
OPCODE(IAbs32, U32, U32, )
|
||||||
|
OPCODE(ShiftLeftLogical32, U32, U32, U32, )
|
||||||
|
OPCODE(ShiftLeftLogical64, U64, U64, U32, )
|
||||||
|
OPCODE(ShiftRightLogical32, U32, U32, U32, )
|
||||||
|
OPCODE(ShiftRightLogical64, U64, U64, U32, )
|
||||||
|
OPCODE(ShiftRightArithmetic32, U32, U32, U32, )
|
||||||
|
OPCODE(ShiftRightArithmetic64, U64, U64, U32, )
|
||||||
|
OPCODE(BitwiseAnd32, U32, U32, U32, )
|
||||||
|
OPCODE(BitwiseOr32, U32, U32, U32, )
|
||||||
|
OPCODE(BitwiseXor32, U32, U32, U32, )
|
||||||
|
OPCODE(BitFieldInsert, U32, U32, U32, U32, U32, )
|
||||||
|
OPCODE(BitFieldSExtract, U32, U32, U32, U32, )
|
||||||
|
OPCODE(BitFieldUExtract, U32, U32, U32, U32, )
|
||||||
|
OPCODE(BitReverse32, U32, U32, )
|
||||||
|
OPCODE(BitCount32, U32, U32, )
|
||||||
|
OPCODE(BitwiseNot32, U32, U32, )
|
||||||
|
|
||||||
|
OPCODE(FindSMsb32, U32, U32, )
|
||||||
|
OPCODE(FindUMsb32, U32, U32, )
|
||||||
|
OPCODE(SMin32, U32, U32, U32, )
|
||||||
|
OPCODE(UMin32, U32, U32, U32, )
|
||||||
|
OPCODE(SMax32, U32, U32, U32, )
|
||||||
|
OPCODE(UMax32, U32, U32, U32, )
|
||||||
|
OPCODE(SClamp32, U32, U32, U32, U32, )
|
||||||
|
OPCODE(UClamp32, U32, U32, U32, U32, )
|
||||||
|
OPCODE(SLessThan, U1, U32, U32, )
|
||||||
|
OPCODE(ULessThan, U1, U32, U32, )
|
||||||
|
OPCODE(IEqual, U1, U32, U32, )
|
||||||
|
OPCODE(SLessThanEqual, U1, U32, U32, )
|
||||||
|
OPCODE(ULessThanEqual, U1, U32, U32, )
|
||||||
|
OPCODE(SGreaterThan, U1, U32, U32, )
|
||||||
|
OPCODE(UGreaterThan, U1, U32, U32, )
|
||||||
|
OPCODE(INotEqual, U1, U32, U32, )
|
||||||
|
OPCODE(SGreaterThanEqual, U1, U32, U32, )
|
||||||
|
OPCODE(UGreaterThanEqual, U1, U32, U32, )
|
||||||
|
|
||||||
|
// Logical operations
|
||||||
|
OPCODE(LogicalOr, U1, U1, U1, )
|
||||||
|
OPCODE(LogicalAnd, U1, U1, U1, )
|
||||||
|
OPCODE(LogicalXor, U1, U1, U1, )
|
||||||
|
OPCODE(LogicalNot, U1, U1, )
|
||||||
|
|
||||||
|
// Conversion operations
|
||||||
|
OPCODE(ConvertS32F32, U32, F32, )
|
||||||
|
OPCODE(ConvertS32F64, U32, F64, )
|
||||||
|
OPCODE(ConvertU32F32, U32, F32, )
|
||||||
|
OPCODE(ConvertF16F32, F16, F32, )
|
||||||
|
OPCODE(ConvertF32F16, F32, F16, )
|
||||||
|
OPCODE(ConvertF32F64, F32, F64, )
|
||||||
|
OPCODE(ConvertF64F32, F64, F32, )
|
||||||
|
OPCODE(ConvertF32S32, F32, U32, )
|
||||||
|
OPCODE(ConvertF32U32, F32, U32, )
|
||||||
|
OPCODE(ConvertF64S32, F64, U32, )
|
||||||
|
OPCODE(ConvertF64U32, F64, U32, )
|
||||||
|
|
||||||
|
// Image operations
|
||||||
|
OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, F32, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, F32, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, Opaque, F32, )
|
||||||
|
OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, )
|
||||||
|
OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, )
|
||||||
|
OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageRead, U32x4, Opaque, Opaque, )
|
||||||
|
OPCODE(ImageWrite, Void, Opaque, Opaque, U32x4, )
|
|
@ -0,0 +1,403 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <optional>
|
||||||
|
#include <type_traits>
|
||||||
|
#include "common/func_traits.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
|
||||||
|
namespace Shader::Optimization {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] T Arg(const IR::Value& value) {
|
||||||
|
if constexpr (std::is_same_v<T, bool>) {
|
||||||
|
return value.U1();
|
||||||
|
} else if constexpr (std::is_same_v<T, u32>) {
|
||||||
|
return value.U32();
|
||||||
|
} else if constexpr (std::is_same_v<T, s32>) {
|
||||||
|
return static_cast<s32>(value.U32());
|
||||||
|
} else if constexpr (std::is_same_v<T, f32>) {
|
||||||
|
return value.F32();
|
||||||
|
} else if constexpr (std::is_same_v<T, u64>) {
|
||||||
|
return value.U64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Func, size_t... I>
|
||||||
|
IR::Value EvalImmediates(const IR::Inst& inst, Func&& func, std::index_sequence<I...>) {
|
||||||
|
using Traits = Common::LambdaTraits<decltype(func)>;
|
||||||
|
return IR::Value{func(Arg<typename Traits::template ArgType<I>>(inst.Arg(I))...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename ImmFn>
|
||||||
|
bool FoldCommutative(IR::Inst& inst, ImmFn&& imm_fn) {
|
||||||
|
const IR::Value lhs{inst.Arg(0)};
|
||||||
|
const IR::Value rhs{inst.Arg(1)};
|
||||||
|
|
||||||
|
const bool is_lhs_immediate{lhs.IsImmediate()};
|
||||||
|
const bool is_rhs_immediate{rhs.IsImmediate()};
|
||||||
|
|
||||||
|
if (is_lhs_immediate && is_rhs_immediate) {
|
||||||
|
const auto result{imm_fn(Arg<T>(lhs), Arg<T>(rhs))};
|
||||||
|
inst.ReplaceUsesWith(IR::Value{result});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (is_lhs_immediate && !is_rhs_immediate) {
|
||||||
|
IR::Inst* const rhs_inst{rhs.InstRecursive()};
|
||||||
|
if (rhs_inst->GetOpcode() == inst.GetOpcode() && rhs_inst->Arg(1).IsImmediate()) {
|
||||||
|
const auto combined{imm_fn(Arg<T>(lhs), Arg<T>(rhs_inst->Arg(1)))};
|
||||||
|
inst.SetArg(0, rhs_inst->Arg(0));
|
||||||
|
inst.SetArg(1, IR::Value{combined});
|
||||||
|
} else {
|
||||||
|
// Normalize
|
||||||
|
inst.SetArg(0, rhs);
|
||||||
|
inst.SetArg(1, lhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_lhs_immediate && is_rhs_immediate) {
|
||||||
|
const IR::Inst* const lhs_inst{lhs.InstRecursive()};
|
||||||
|
if (lhs_inst->GetOpcode() == inst.GetOpcode() && lhs_inst->Arg(1).IsImmediate()) {
|
||||||
|
const auto combined{imm_fn(Arg<T>(rhs), Arg<T>(lhs_inst->Arg(1)))};
|
||||||
|
inst.SetArg(0, lhs_inst->Arg(0));
|
||||||
|
inst.SetArg(1, IR::Value{combined});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Func>
|
||||||
|
bool FoldWhenAllImmediates(IR::Inst& inst, Func&& func) {
|
||||||
|
if (!inst.AreAllArgsImmediates() /*|| inst.HasAssociatedPseudoOperation()*/) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
using Indices = std::make_index_sequence<Common::LambdaTraits<decltype(func)>::NUM_ARGS>;
|
||||||
|
inst.ReplaceUsesWith(EvalImmediates(inst, func, Indices{}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <IR::Opcode op, typename Dest, typename Source>
|
||||||
|
void FoldBitCast(IR::Inst& inst, IR::Opcode reverse) {
|
||||||
|
const IR::Value value{inst.Arg(0)};
|
||||||
|
if (value.IsImmediate()) {
|
||||||
|
inst.ReplaceUsesWith(IR::Value{std::bit_cast<Dest>(Arg<Source>(value))});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IR::Inst* const arg_inst{value.InstRecursive()};
|
||||||
|
if (arg_inst->GetOpcode() == reverse) {
|
||||||
|
inst.ReplaceUsesWith(arg_inst->Arg(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if constexpr (op == IR::Opcode::BitCastF32U32) {
|
||||||
|
if (arg_inst->GetOpcode() == IR::Opcode::ReadConstBuffer) {
|
||||||
|
// Replace the bitcast with a typed constant buffer read
|
||||||
|
inst.ReplaceOpcode(IR::Opcode::ReadConstBufferF32);
|
||||||
|
inst.SetArg(0, arg_inst->Arg(0));
|
||||||
|
inst.SetArg(1, arg_inst->Arg(1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<IR::Value> FoldCompositeExtractImpl(IR::Value inst_value, IR::Opcode insert,
|
||||||
|
IR::Opcode construct, u32 first_index) {
|
||||||
|
IR::Inst* const inst{inst_value.InstRecursive()};
|
||||||
|
if (inst->GetOpcode() == construct) {
|
||||||
|
return inst->Arg(first_index);
|
||||||
|
}
|
||||||
|
if (inst->GetOpcode() != insert) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
IR::Value value_index{inst->Arg(2)};
|
||||||
|
if (!value_index.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const u32 second_index{value_index.U32()};
|
||||||
|
if (first_index != second_index) {
|
||||||
|
IR::Value value_composite{inst->Arg(0)};
|
||||||
|
if (value_composite.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return FoldCompositeExtractImpl(value_composite, insert, construct, first_index);
|
||||||
|
}
|
||||||
|
return inst->Arg(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldCompositeExtract(IR::Inst& inst, IR::Opcode construct, IR::Opcode insert) {
|
||||||
|
const IR::Value value_1{inst.Arg(0)};
|
||||||
|
const IR::Value value_2{inst.Arg(1)};
|
||||||
|
if (value_1.IsImmediate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!value_2.IsImmediate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const u32 first_index{value_2.U32()};
|
||||||
|
const std::optional result{FoldCompositeExtractImpl(value_1, insert, construct, first_index)};
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inst.ReplaceUsesWith(*result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldConvert(IR::Inst& inst, IR::Opcode opposite) {
|
||||||
|
const IR::Value value{inst.Arg(0)};
|
||||||
|
if (value.IsImmediate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IR::Inst* const producer{value.InstRecursive()};
|
||||||
|
if (producer->GetOpcode() == opposite) {
|
||||||
|
inst.ReplaceUsesWith(producer->Arg(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldLogicalAnd(IR::Inst& inst) {
|
||||||
|
if (!FoldCommutative<bool>(inst, [](bool a, bool b) { return a && b; })) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const IR::Value rhs{inst.Arg(1)};
|
||||||
|
if (rhs.IsImmediate()) {
|
||||||
|
if (rhs.U1()) {
|
||||||
|
inst.ReplaceUsesWith(inst.Arg(0));
|
||||||
|
} else {
|
||||||
|
inst.ReplaceUsesWith(IR::Value{false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldSelect(IR::Inst& inst) {
|
||||||
|
const IR::Value cond{inst.Arg(0)};
|
||||||
|
if (cond.IsImmediate()) {
|
||||||
|
inst.ReplaceUsesWith(cond.U1() ? inst.Arg(1) : inst.Arg(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldLogicalOr(IR::Inst& inst) {
|
||||||
|
if (!FoldCommutative<bool>(inst, [](bool a, bool b) { return a || b; })) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const IR::Value rhs{inst.Arg(1)};
|
||||||
|
if (rhs.IsImmediate()) {
|
||||||
|
if (rhs.U1()) {
|
||||||
|
inst.ReplaceUsesWith(IR::Value{true});
|
||||||
|
} else {
|
||||||
|
inst.ReplaceUsesWith(inst.Arg(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldLogicalNot(IR::Inst& inst) {
|
||||||
|
const IR::U1 value{inst.Arg(0)};
|
||||||
|
if (value.IsImmediate()) {
|
||||||
|
inst.ReplaceUsesWith(IR::Value{!value.U1()});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IR::Inst* const arg{value.InstRecursive()};
|
||||||
|
if (arg->GetOpcode() == IR::Opcode::LogicalNot) {
|
||||||
|
inst.ReplaceUsesWith(arg->Arg(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldInverseFunc(IR::Inst& inst, IR::Opcode reverse) {
|
||||||
|
const IR::Value value{inst.Arg(0)};
|
||||||
|
if (value.IsImmediate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IR::Inst* const arg_inst{value.InstRecursive()};
|
||||||
|
if (arg_inst->GetOpcode() == reverse) {
|
||||||
|
inst.ReplaceUsesWith(arg_inst->Arg(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void FoldAdd(IR::Block& block, IR::Inst& inst) {
|
||||||
|
if (!FoldCommutative<T>(inst, [](T a, T b) { return a + b; })) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const IR::Value rhs{inst.Arg(1)};
|
||||||
|
if (rhs.IsImmediate() && Arg<T>(rhs) == 0) {
|
||||||
|
inst.ReplaceUsesWith(inst.Arg(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <u32 idx>
|
||||||
|
bool IsArgImm(const IR::Inst& inst, u32 imm) {
|
||||||
|
const IR::Value& arg = inst.Arg(idx);
|
||||||
|
return arg.IsImmediate() && arg.U32() == imm;
|
||||||
|
};
|
||||||
|
|
||||||
|
void FoldBooleanConvert(IR::Inst& inst) {
|
||||||
|
// Eliminate pattern
|
||||||
|
// %4 = <some bool>
|
||||||
|
// %5 = SelectU32 %4, #1, #0 (uses: 2)
|
||||||
|
// %8 = INotEqual %5, #0 (uses: 1)
|
||||||
|
if (!IsArgImm<1>(inst, 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IR::Inst* prod = inst.Arg(0).TryInstRecursive();
|
||||||
|
if (!prod || prod->GetOpcode() != IR::Opcode::SelectU32) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsArgImm<1>(*prod, 1) && IsArgImm<2>(*prod, 0)) {
|
||||||
|
inst.ReplaceUsesWith(prod->Arg(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstantPropagation(IR::Block& block, IR::Inst& inst) {
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::IAdd32:
|
||||||
|
return FoldAdd<u32>(block, inst);
|
||||||
|
case IR::Opcode::IMul32:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a * b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::ShiftRightArithmetic32:
|
||||||
|
FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return static_cast<u32>(a >> b); });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::BitCastF32U32:
|
||||||
|
return FoldBitCast<IR::Opcode::BitCastF32U32, f32, u32>(inst, IR::Opcode::BitCastU32F32);
|
||||||
|
case IR::Opcode::BitCastU32F32:
|
||||||
|
return FoldBitCast<IR::Opcode::BitCastU32F32, u32, f32>(inst, IR::Opcode::BitCastF32U32);
|
||||||
|
case IR::Opcode::PackHalf2x16:
|
||||||
|
return FoldInverseFunc(inst, IR::Opcode::UnpackHalf2x16);
|
||||||
|
case IR::Opcode::UnpackHalf2x16:
|
||||||
|
return FoldInverseFunc(inst, IR::Opcode::PackHalf2x16);
|
||||||
|
case IR::Opcode::PackFloat2x16:
|
||||||
|
return FoldInverseFunc(inst, IR::Opcode::UnpackFloat2x16);
|
||||||
|
case IR::Opcode::UnpackFloat2x16:
|
||||||
|
return FoldInverseFunc(inst, IR::Opcode::PackFloat2x16);
|
||||||
|
case IR::Opcode::SelectU1:
|
||||||
|
case IR::Opcode::SelectU8:
|
||||||
|
case IR::Opcode::SelectU16:
|
||||||
|
case IR::Opcode::SelectU32:
|
||||||
|
case IR::Opcode::SelectU64:
|
||||||
|
case IR::Opcode::SelectF32:
|
||||||
|
case IR::Opcode::SelectF64:
|
||||||
|
return FoldSelect(inst);
|
||||||
|
case IR::Opcode::FPNeg32:
|
||||||
|
FoldWhenAllImmediates(inst, [](f32 a) { return -a; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::LogicalAnd:
|
||||||
|
return FoldLogicalAnd(inst);
|
||||||
|
case IR::Opcode::LogicalOr:
|
||||||
|
return FoldLogicalOr(inst);
|
||||||
|
case IR::Opcode::LogicalNot:
|
||||||
|
return FoldLogicalNot(inst);
|
||||||
|
case IR::Opcode::SLessThan:
|
||||||
|
FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a < b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::ULessThan:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a < b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::SLessThanEqual:
|
||||||
|
FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a <= b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::ULessThanEqual:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a <= b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::SGreaterThan:
|
||||||
|
FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a > b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::UGreaterThan:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a > b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::SGreaterThanEqual:
|
||||||
|
FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a >= b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::UGreaterThanEqual:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a >= b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::IEqual:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a == b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::INotEqual:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a != b; });
|
||||||
|
FoldBooleanConvert(inst);
|
||||||
|
return;
|
||||||
|
case IR::Opcode::BitwiseAnd32:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a & b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::BitwiseOr32:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a | b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::BitwiseXor32:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a ^ b; });
|
||||||
|
return;
|
||||||
|
case IR::Opcode::BitFieldUExtract:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 base, u32 shift, u32 count) {
|
||||||
|
if (static_cast<size_t>(shift) + static_cast<size_t>(count) > 32) {
|
||||||
|
throw LogicError("Undefined result in {}({}, {}, {})", IR::Opcode::BitFieldUExtract,
|
||||||
|
base, shift, count);
|
||||||
|
}
|
||||||
|
return (base >> shift) & ((1U << count) - 1);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case IR::Opcode::BitFieldSExtract:
|
||||||
|
FoldWhenAllImmediates(inst, [](s32 base, u32 shift, u32 count) {
|
||||||
|
const size_t back_shift{static_cast<size_t>(shift) + static_cast<size_t>(count)};
|
||||||
|
const size_t left_shift{32 - back_shift};
|
||||||
|
const size_t right_shift{static_cast<size_t>(32 - count)};
|
||||||
|
if (back_shift > 32 || left_shift >= 32 || right_shift >= 32) {
|
||||||
|
throw LogicError("Undefined result in {}({}, {}, {})", IR::Opcode::BitFieldSExtract,
|
||||||
|
base, shift, count);
|
||||||
|
}
|
||||||
|
return static_cast<u32>((base << left_shift) >> right_shift);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case IR::Opcode::BitFieldInsert:
|
||||||
|
FoldWhenAllImmediates(inst, [](u32 base, u32 insert, u32 offset, u32 bits) {
|
||||||
|
if (bits >= 32 || offset >= 32) {
|
||||||
|
throw LogicError("Undefined result in {}({}, {}, {}, {})",
|
||||||
|
IR::Opcode::BitFieldInsert, base, insert, offset, bits);
|
||||||
|
}
|
||||||
|
return (base & ~(~(~0u << bits) << offset)) | (insert << offset);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case IR::Opcode::CompositeExtractU32x2:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructU32x2,
|
||||||
|
IR::Opcode::CompositeInsertU32x2);
|
||||||
|
case IR::Opcode::CompositeExtractU32x3:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructU32x3,
|
||||||
|
IR::Opcode::CompositeInsertU32x3);
|
||||||
|
case IR::Opcode::CompositeExtractU32x4:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructU32x4,
|
||||||
|
IR::Opcode::CompositeInsertU32x4);
|
||||||
|
case IR::Opcode::CompositeExtractF32x2:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructF32x2,
|
||||||
|
IR::Opcode::CompositeInsertF32x2);
|
||||||
|
case IR::Opcode::CompositeExtractF32x3:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructF32x3,
|
||||||
|
IR::Opcode::CompositeInsertF32x3);
|
||||||
|
case IR::Opcode::CompositeExtractF32x4:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructF32x4,
|
||||||
|
IR::Opcode::CompositeInsertF32x4);
|
||||||
|
case IR::Opcode::CompositeExtractF16x2:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructF16x2,
|
||||||
|
IR::Opcode::CompositeInsertF16x2);
|
||||||
|
case IR::Opcode::CompositeExtractF16x3:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructF16x3,
|
||||||
|
IR::Opcode::CompositeInsertF16x3);
|
||||||
|
case IR::Opcode::CompositeExtractF16x4:
|
||||||
|
return FoldCompositeExtract(inst, IR::Opcode::CompositeConstructF16x4,
|
||||||
|
IR::Opcode::CompositeInsertF16x4);
|
||||||
|
case IR::Opcode::ConvertF32F16:
|
||||||
|
return FoldConvert(inst, IR::Opcode::ConvertF16F32);
|
||||||
|
case IR::Opcode::ConvertF16F32:
|
||||||
|
return FoldConvert(inst, IR::Opcode::ConvertF32F16);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstantPropagationPass(IR::BlockList& program) {
|
||||||
|
const auto end{program.rend()};
|
||||||
|
for (auto it = program.rbegin(); it != end; ++it) {
|
||||||
|
IR::Block* const block{*it};
|
||||||
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
|
ConstantPropagation(*block, inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Optimization
|
|
@ -0,0 +1,16 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
|
||||||
|
namespace Shader::Optimization {
|
||||||
|
|
||||||
|
void SsaRewritePass(IR::BlockList& program);
|
||||||
|
void IdentityRemovalPass(IR::BlockList& program);
|
||||||
|
void DeadCodeEliminationPass(IR::BlockList& program);
|
||||||
|
void ConstantPropagationPass(IR::BlockList& program);
|
||||||
|
void ResourceTrackingPass(IR::BlockList& program);
|
||||||
|
|
||||||
|
} // namespace Shader::Optimization
|
|
@ -0,0 +1,131 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <bit>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/ir_emitter.h"
|
||||||
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
#include "shader_recompiler/runtime_info.h"
|
||||||
|
|
||||||
|
namespace Shader::Optimization {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct SharpLocation {
|
||||||
|
IR::ScalarReg eud_ptr;
|
||||||
|
u32 index_dwords;
|
||||||
|
|
||||||
|
auto operator<=>(const SharpLocation&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsResourceInstruction(const IR::Inst& inst) {
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::ReadConstBuffer:
|
||||||
|
case IR::Opcode::ReadConstBufferF32:
|
||||||
|
case IR::Opcode::ImageSampleExplicitLod:
|
||||||
|
case IR::Opcode::ImageSampleImplicitLod:
|
||||||
|
case IR::Opcode::ImageSampleDrefExplicitLod:
|
||||||
|
case IR::Opcode::ImageSampleDrefImplicitLod:
|
||||||
|
case IR::Opcode::ImageFetch:
|
||||||
|
case IR::Opcode::ImageGather:
|
||||||
|
case IR::Opcode::ImageGatherDref:
|
||||||
|
case IR::Opcode::ImageQueryDimensions:
|
||||||
|
case IR::Opcode::ImageQueryLod:
|
||||||
|
case IR::Opcode::ImageGradient:
|
||||||
|
case IR::Opcode::ImageRead:
|
||||||
|
case IR::Opcode::ImageWrite:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*class Descriptors {
|
||||||
|
public:
|
||||||
|
explicit Descriptors(TextureDescriptors& texture_descriptors_)
|
||||||
|
: texture_descriptors{texture_descriptors_} {}
|
||||||
|
|
||||||
|
u32 Add(const TextureDescriptor& desc) {
|
||||||
|
const u32 index{Add(texture_descriptors, desc, [&desc](const auto& existing) {
|
||||||
|
return desc.type == existing.type && desc.is_depth == existing.is_depth &&
|
||||||
|
desc.has_secondary == existing.has_secondary &&
|
||||||
|
desc.cbuf_index == existing.cbuf_index &&
|
||||||
|
desc.cbuf_offset == existing.cbuf_offset &&
|
||||||
|
desc.shift_left == existing.shift_left &&
|
||||||
|
desc.secondary_cbuf_index == existing.secondary_cbuf_index &&
|
||||||
|
desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
|
||||||
|
desc.secondary_shift_left == existing.secondary_shift_left &&
|
||||||
|
desc.count == existing.count && desc.size_shift == existing.size_shift;
|
||||||
|
})};
|
||||||
|
// TODO: Read this from TIC
|
||||||
|
texture_descriptors[index].is_multisample |= desc.is_multisample;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename Descriptors, typename Descriptor, typename Func>
|
||||||
|
static u32 Add(Descriptors& descriptors, const Descriptor& desc, Func&& pred) {
|
||||||
|
// TODO: Handle arrays
|
||||||
|
const auto it{std::ranges::find_if(descriptors, pred)};
|
||||||
|
if (it != descriptors.end()) {
|
||||||
|
return static_cast<u32>(std::distance(descriptors.begin(), it));
|
||||||
|
}
|
||||||
|
descriptors.push_back(desc);
|
||||||
|
return static_cast<u32>(descriptors.size()) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureDescriptors& texture_descriptors;
|
||||||
|
};*/
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
SharpLocation TrackSharp(const IR::Value& handle) {
|
||||||
|
IR::Inst* inst = handle.InstRecursive();
|
||||||
|
if (inst->GetOpcode() == IR::Opcode::GetScalarRegister) {
|
||||||
|
return SharpLocation{
|
||||||
|
.eud_ptr = IR::ScalarReg::Max,
|
||||||
|
.index_dwords = inst->Arg(0).U32(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ASSERT_MSG(inst->GetOpcode() == IR::Opcode::ReadConst, "Sharp load not from constant memory");
|
||||||
|
|
||||||
|
// Retrieve offset from base.
|
||||||
|
IR::Inst* addr = inst->Arg(0).InstRecursive();
|
||||||
|
u32 dword_offset = addr->Arg(1).U32();
|
||||||
|
addr = addr->Arg(0).InstRecursive();
|
||||||
|
ASSERT_MSG(addr->Arg(1).IsImmediate(), "Bindless not supported");
|
||||||
|
dword_offset += addr->Arg(1).U32() >> 2;
|
||||||
|
|
||||||
|
// Retrieve SGPR that holds sbase
|
||||||
|
inst = addr->Arg(0).InstRecursive()->Arg(0).InstRecursive();
|
||||||
|
ASSERT_MSG(inst->GetOpcode() == IR::Opcode::GetScalarRegister,
|
||||||
|
"Nested resource loads not supported");
|
||||||
|
const IR::ScalarReg base = inst->Arg(0).ScalarReg();
|
||||||
|
|
||||||
|
// Return retrieved location.
|
||||||
|
return SharpLocation{
|
||||||
|
.eud_ptr = base,
|
||||||
|
.index_dwords = dword_offset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceTrackingPass(IR::BlockList& program) {
|
||||||
|
for (IR::Block* const block : program) {
|
||||||
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
|
if (!IsResourceInstruction(inst)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
printf("ff\n");
|
||||||
|
IR::Inst* producer = inst.Arg(0).InstRecursive();
|
||||||
|
const auto loc = TrackSharp(producer->Arg(0));
|
||||||
|
fmt::print("Found resource s[{}:{}] is_eud = {}\n", loc.index_dwords,
|
||||||
|
loc.index_dwords + 4, loc.eud_ptr != IR::ScalarReg::Max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Optimization
|
|
@ -0,0 +1,408 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// This file implements the SSA rewriting algorithm proposed in
|
||||||
|
//
|
||||||
|
// Simple and Efficient Construction of Static Single Assignment Form.
|
||||||
|
// Braun M., Buchwald S., Hack S., Leiba R., Mallon C., Zwinkau A. (2013)
|
||||||
|
// In: Jhala R., De Bosschere K. (eds)
|
||||||
|
// Compiler Construction. CC 2013.
|
||||||
|
// Lecture Notes in Computer Science, vol 7791.
|
||||||
|
// Springer, Berlin, Heidelberg
|
||||||
|
//
|
||||||
|
// https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <span>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/ir_emitter.h"
|
||||||
|
#include "shader_recompiler/ir/opcodes.h"
|
||||||
|
#include "shader_recompiler/ir/reg.h"
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
|
||||||
|
namespace Shader::Optimization {
|
||||||
|
namespace {
|
||||||
|
struct FlagTag {
|
||||||
|
auto operator<=>(const FlagTag&) const noexcept = default;
|
||||||
|
};
|
||||||
|
struct ZeroFlagTag : FlagTag {};
|
||||||
|
struct SignFlagTag : FlagTag {};
|
||||||
|
struct CarryFlagTag : FlagTag {};
|
||||||
|
struct OverflowFlagTag : FlagTag {};
|
||||||
|
struct VccFlagTag : FlagTag {};
|
||||||
|
|
||||||
|
struct GotoVariable : FlagTag {
|
||||||
|
GotoVariable() = default;
|
||||||
|
explicit GotoVariable(u32 index_) : index{index_} {}
|
||||||
|
|
||||||
|
auto operator<=>(const GotoVariable&) const noexcept = default;
|
||||||
|
|
||||||
|
u32 index;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Variant = std::variant<IR::ScalarReg, IR::VectorReg, ZeroFlagTag, SignFlagTag, CarryFlagTag,
|
||||||
|
OverflowFlagTag, GotoVariable, VccFlagTag>;
|
||||||
|
using ValueMap = std::unordered_map<IR::Block*, IR::Value>;
|
||||||
|
|
||||||
|
struct DefTable {
|
||||||
|
const IR::Value& Def(IR::Block* block, IR::ScalarReg variable) {
|
||||||
|
return block->ssa_sreg_values[RegIndex(variable)];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, IR::ScalarReg variable, const IR::Value& value) {
|
||||||
|
block->ssa_sreg_values[RegIndex(variable)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Value& Def(IR::Block* block, IR::VectorReg variable) {
|
||||||
|
return block->ssa_vreg_values[RegIndex(variable)];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, IR::VectorReg variable, const IR::Value& value) {
|
||||||
|
block->ssa_vreg_values[RegIndex(variable)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Value& Def(IR::Block* block, GotoVariable variable) {
|
||||||
|
return goto_vars[variable.index][block];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, GotoVariable variable, const IR::Value& value) {
|
||||||
|
goto_vars[variable.index].insert_or_assign(block, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Value& Def(IR::Block* block, ZeroFlagTag) {
|
||||||
|
return zero_flag[block];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, ZeroFlagTag, const IR::Value& value) {
|
||||||
|
zero_flag.insert_or_assign(block, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Value& Def(IR::Block* block, SignFlagTag) {
|
||||||
|
return sign_flag[block];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, SignFlagTag, const IR::Value& value) {
|
||||||
|
sign_flag.insert_or_assign(block, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Value& Def(IR::Block* block, CarryFlagTag) {
|
||||||
|
return carry_flag[block];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, CarryFlagTag, const IR::Value& value) {
|
||||||
|
carry_flag.insert_or_assign(block, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Value& Def(IR::Block* block, OverflowFlagTag) {
|
||||||
|
return overflow_flag[block];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, OverflowFlagTag, const IR::Value& value) {
|
||||||
|
overflow_flag.insert_or_assign(block, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Value& Def(IR::Block* block, VccFlagTag) {
|
||||||
|
return vcc_flag[block];
|
||||||
|
}
|
||||||
|
void SetDef(IR::Block* block, VccFlagTag, const IR::Value& value) {
|
||||||
|
vcc_flag.insert_or_assign(block, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<u32, ValueMap> goto_vars;
|
||||||
|
ValueMap indirect_branch_var;
|
||||||
|
ValueMap zero_flag;
|
||||||
|
ValueMap sign_flag;
|
||||||
|
ValueMap carry_flag;
|
||||||
|
ValueMap overflow_flag;
|
||||||
|
ValueMap vcc_flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
IR::Opcode UndefOpcode(IR::ScalarReg) noexcept {
|
||||||
|
return IR::Opcode::UndefU32;
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::Opcode UndefOpcode(IR::VectorReg) noexcept {
|
||||||
|
return IR::Opcode::UndefU32;
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::Opcode UndefOpcode(const FlagTag&) noexcept {
|
||||||
|
return IR::Opcode::UndefU1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
Start,
|
||||||
|
SetValue,
|
||||||
|
PreparePhiArgument,
|
||||||
|
PushPhiArgument,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
struct ReadState {
|
||||||
|
ReadState(IR::Block* block_) : block{block_} {}
|
||||||
|
ReadState() = default;
|
||||||
|
|
||||||
|
IR::Block* block{};
|
||||||
|
IR::Value result{};
|
||||||
|
IR::Inst* phi{};
|
||||||
|
IR::Block* const* pred_it{};
|
||||||
|
IR::Block* const* pred_end{};
|
||||||
|
Status pc{Status::Start};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Pass {
|
||||||
|
public:
|
||||||
|
template <typename Type>
|
||||||
|
void WriteVariable(Type variable, IR::Block* block, const IR::Value& value) {
|
||||||
|
current_def.SetDef(block, variable, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
IR::Value ReadVariable(Type variable, IR::Block* root_block) {
|
||||||
|
boost::container::small_vector<ReadState<Type>, 64> stack{
|
||||||
|
ReadState<Type>(nullptr),
|
||||||
|
ReadState<Type>(root_block),
|
||||||
|
};
|
||||||
|
const auto prepare_phi_operand = [&] {
|
||||||
|
if (stack.back().pred_it == stack.back().pred_end) {
|
||||||
|
IR::Inst* const phi{stack.back().phi};
|
||||||
|
IR::Block* const block{stack.back().block};
|
||||||
|
const IR::Value result{TryRemoveTrivialPhi(*phi, block, UndefOpcode(variable))};
|
||||||
|
stack.pop_back();
|
||||||
|
stack.back().result = result;
|
||||||
|
WriteVariable(variable, block, result);
|
||||||
|
} else {
|
||||||
|
IR::Block* const imm_pred{*stack.back().pred_it};
|
||||||
|
stack.back().pc = Status::PushPhiArgument;
|
||||||
|
stack.emplace_back(imm_pred);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
do {
|
||||||
|
IR::Block* const block{stack.back().block};
|
||||||
|
switch (stack.back().pc) {
|
||||||
|
case Status::Start: {
|
||||||
|
if (const IR::Value& def = current_def.Def(block, variable); !def.IsEmpty()) {
|
||||||
|
stack.back().result = def;
|
||||||
|
} else if (!block->IsSsaSealed()) {
|
||||||
|
// Incomplete CFG
|
||||||
|
IR::Inst* phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)};
|
||||||
|
phi->SetFlags(IR::TypeOf(UndefOpcode(variable)));
|
||||||
|
|
||||||
|
incomplete_phis[block].insert_or_assign(variable, phi);
|
||||||
|
stack.back().result = IR::Value{&*phi};
|
||||||
|
} else if (const std::span imm_preds = block->ImmPredecessors();
|
||||||
|
imm_preds.size() == 1) {
|
||||||
|
// Optimize the common case of one predecessor: no phi needed
|
||||||
|
stack.back().pc = Status::SetValue;
|
||||||
|
stack.emplace_back(imm_preds.front());
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Break potential cycles with operandless phi
|
||||||
|
IR::Inst* const phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)};
|
||||||
|
phi->SetFlags(IR::TypeOf(UndefOpcode(variable)));
|
||||||
|
|
||||||
|
WriteVariable(variable, block, IR::Value{phi});
|
||||||
|
|
||||||
|
stack.back().phi = phi;
|
||||||
|
stack.back().pred_it = imm_preds.data();
|
||||||
|
stack.back().pred_end = imm_preds.data() + imm_preds.size();
|
||||||
|
prepare_phi_operand();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case Status::SetValue: {
|
||||||
|
const IR::Value result{stack.back().result};
|
||||||
|
WriteVariable(variable, block, result);
|
||||||
|
stack.pop_back();
|
||||||
|
stack.back().result = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Status::PushPhiArgument: {
|
||||||
|
IR::Inst* const phi{stack.back().phi};
|
||||||
|
phi->AddPhiOperand(*stack.back().pred_it, stack.back().result);
|
||||||
|
++stack.back().pred_it;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case Status::PreparePhiArgument:
|
||||||
|
prepare_phi_operand();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (stack.size() > 1);
|
||||||
|
return stack.back().result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SealBlock(IR::Block* block) {
|
||||||
|
const auto it{incomplete_phis.find(block)};
|
||||||
|
if (it != incomplete_phis.end()) {
|
||||||
|
for (auto& pair : it->second) {
|
||||||
|
auto& variant{pair.first};
|
||||||
|
auto& phi{pair.second};
|
||||||
|
std::visit([&](auto& variable) { AddPhiOperands(variable, *phi, block); }, variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block->SsaSeal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename Type>
|
||||||
|
IR::Value AddPhiOperands(Type variable, IR::Inst& phi, IR::Block* block) {
|
||||||
|
for (IR::Block* const imm_pred : block->ImmPredecessors()) {
|
||||||
|
phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred));
|
||||||
|
}
|
||||||
|
return TryRemoveTrivialPhi(phi, block, UndefOpcode(variable));
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::Value TryRemoveTrivialPhi(IR::Inst& phi, IR::Block* block, IR::Opcode undef_opcode) {
|
||||||
|
IR::Value same;
|
||||||
|
const size_t num_args{phi.NumArgs()};
|
||||||
|
for (size_t arg_index = 0; arg_index < num_args; ++arg_index) {
|
||||||
|
const IR::Value& op{phi.Arg(arg_index)};
|
||||||
|
if (op.Resolve() == same.Resolve() || op == IR::Value{&phi}) {
|
||||||
|
// Unique value or self-reference
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!same.IsEmpty()) {
|
||||||
|
// The phi merges at least two values: not trivial
|
||||||
|
return IR::Value{&phi};
|
||||||
|
}
|
||||||
|
same = op;
|
||||||
|
}
|
||||||
|
// Remove the phi node from the block, it will be reinserted
|
||||||
|
IR::Block::InstructionList& list{block->Instructions()};
|
||||||
|
list.erase(IR::Block::InstructionList::s_iterator_to(phi));
|
||||||
|
|
||||||
|
// Find the first non-phi instruction and use it as an insertion point
|
||||||
|
IR::Block::iterator reinsert_point{std::ranges::find_if_not(list, IR::IsPhi)};
|
||||||
|
if (same.IsEmpty()) {
|
||||||
|
// The phi is unreachable or in the start block
|
||||||
|
// Insert an undefined instruction and make it the phi node replacement
|
||||||
|
// The "phi" node reinsertion point is specified after this instruction
|
||||||
|
reinsert_point = block->PrependNewInst(reinsert_point, undef_opcode);
|
||||||
|
same = IR::Value{&*reinsert_point};
|
||||||
|
++reinsert_point;
|
||||||
|
}
|
||||||
|
// Reinsert the phi node and reroute all its uses to the "same" value
|
||||||
|
list.insert(reinsert_point, phi);
|
||||||
|
phi.ReplaceUsesWith(same);
|
||||||
|
// TODO: Try to recursively remove all phi users, which might have become trivial
|
||||||
|
return same;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<IR::Block*, std::map<Variant, IR::Inst*>> incomplete_phis;
|
||||||
|
DefTable current_def;
|
||||||
|
};
|
||||||
|
|
||||||
|
void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) {
|
||||||
|
const IR::Opcode opcode{inst.GetOpcode()};
|
||||||
|
switch (opcode) {
|
||||||
|
case IR::Opcode::SetScalarRegister: {
|
||||||
|
const IR::ScalarReg reg{inst.Arg(0).ScalarReg()};
|
||||||
|
pass.WriteVariable(reg, block, inst.Arg(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::Opcode::SetVectorRegister: {
|
||||||
|
const IR::VectorReg reg{inst.Arg(0).VectorReg()};
|
||||||
|
pass.WriteVariable(reg, block, inst.Arg(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::Opcode::SetGotoVariable:
|
||||||
|
pass.WriteVariable(GotoVariable{inst.Arg(0).U32()}, block, inst.Arg(1));
|
||||||
|
break;
|
||||||
|
case IR::Opcode::SetVcc:
|
||||||
|
pass.WriteVariable(VccFlagTag{}, block, inst.Arg(0));
|
||||||
|
break;
|
||||||
|
// case IR::Opcode::SetSFlag:
|
||||||
|
// pass.WriteVariable(SignFlagTag{}, block, inst.Arg(0));
|
||||||
|
// break;
|
||||||
|
// case IR::Opcode::SetCFlag:
|
||||||
|
// pass.WriteVariable(CarryFlagTag{}, block, inst.Arg(0));
|
||||||
|
// break;
|
||||||
|
// case IR::Opcode::SetOFlag:
|
||||||
|
// pass.WriteVariable(OverflowFlagTag{}, block, inst.Arg(0));
|
||||||
|
// break;
|
||||||
|
case IR::Opcode::GetScalarRegister: {
|
||||||
|
const IR::ScalarReg reg{inst.Arg(0).ScalarReg()};
|
||||||
|
inst.ReplaceUsesWith(pass.ReadVariable(reg, block));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::Opcode::GetVectorRegister: {
|
||||||
|
const IR::VectorReg reg{inst.Arg(0).VectorReg()};
|
||||||
|
inst.ReplaceUsesWith(pass.ReadVariable(reg, block));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::Opcode::GetGotoVariable:
|
||||||
|
inst.ReplaceUsesWith(pass.ReadVariable(GotoVariable{inst.Arg(0).U32()}, block));
|
||||||
|
break;
|
||||||
|
case IR::Opcode::GetVcc:
|
||||||
|
inst.ReplaceUsesWith(pass.ReadVariable(VccFlagTag{}, block));
|
||||||
|
break;
|
||||||
|
// case IR::Opcode::GetSFlag:
|
||||||
|
// inst.ReplaceUsesWith(pass.ReadVariable(SignFlagTag{}, block));
|
||||||
|
// break;
|
||||||
|
// case IR::Opcode::GetCFlag:
|
||||||
|
// inst.ReplaceUsesWith(pass.ReadVariable(CarryFlagTag{}, block));
|
||||||
|
// break;
|
||||||
|
// case IR::Opcode::GetOFlag:
|
||||||
|
// inst.ReplaceUsesWith(pass.ReadVariable(OverflowFlagTag{}, block));
|
||||||
|
// break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisitBlock(Pass& pass, IR::Block* block) {
|
||||||
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
|
VisitInst(pass, block, inst);
|
||||||
|
}
|
||||||
|
pass.SealBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
void SsaRewritePass(IR::BlockList& program) {
|
||||||
|
Pass pass;
|
||||||
|
const auto end{program.rend()};
|
||||||
|
for (auto block = program.rbegin(); block != end; ++block) {
|
||||||
|
VisitBlock(pass, *block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityRemovalPass(IR::BlockList& program) {
|
||||||
|
std::vector<IR::Inst*> to_invalidate;
|
||||||
|
for (IR::Block* const block : program) {
|
||||||
|
for (auto inst = block->begin(); inst != block->end();) {
|
||||||
|
const size_t num_args{inst->NumArgs()};
|
||||||
|
for (size_t i = 0; i < num_args; ++i) {
|
||||||
|
IR::Value arg;
|
||||||
|
while ((arg = inst->Arg(i)).IsIdentity()) {
|
||||||
|
inst->SetArg(i, arg.Inst()->Arg(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inst->GetOpcode() == IR::Opcode::Identity ||
|
||||||
|
inst->GetOpcode() == IR::Opcode::Void) {
|
||||||
|
to_invalidate.push_back(&*inst);
|
||||||
|
inst = block->Instructions().erase(inst);
|
||||||
|
} else {
|
||||||
|
++inst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (IR::Inst* const inst : to_invalidate) {
|
||||||
|
inst->Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeadCodeEliminationPass(IR::BlockList& program) {
|
||||||
|
// We iterate over the instructions in reverse order.
|
||||||
|
// This is because removing an instruction reduces the number of uses for earlier instructions.
|
||||||
|
for (IR::Block* const block : program) {
|
||||||
|
auto it{block->end()};
|
||||||
|
while (it != block->begin()) {
|
||||||
|
--it;
|
||||||
|
if (!it->HasUses() && !it->MayHaveSideEffects()) {
|
||||||
|
it->Invalidate();
|
||||||
|
it = block->Instructions().erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Optimization
|
|
@ -0,0 +1,42 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/container/flat_set.hpp>
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
#include "shader_recompiler/ir/post_order.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
BlockList PostOrder(const AbstractSyntaxNode& root) {
|
||||||
|
boost::container::small_vector<Block*, 16> block_stack;
|
||||||
|
boost::container::flat_set<Block*> visited;
|
||||||
|
BlockList post_order_blocks;
|
||||||
|
|
||||||
|
if (root.type != AbstractSyntaxNode::Type::Block) {
|
||||||
|
throw LogicError("First node in abstract syntax list root is not a block");
|
||||||
|
}
|
||||||
|
Block* const first_block{root.data.block};
|
||||||
|
visited.insert(first_block);
|
||||||
|
block_stack.push_back(first_block);
|
||||||
|
|
||||||
|
while (!block_stack.empty()) {
|
||||||
|
Block* const block = block_stack.back();
|
||||||
|
const auto visit = [&](Block* branch) {
|
||||||
|
if (!visited.insert(branch).second) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Calling push_back twice is faster than insert on MSVC
|
||||||
|
block_stack.push_back(block);
|
||||||
|
block_stack.push_back(branch);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
block_stack.pop_back();
|
||||||
|
if (std::ranges::none_of(block->ImmSuccessors(), visit)) {
|
||||||
|
post_order_blocks.push_back(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return post_order_blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,13 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/abstract_syntax_list.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
BlockList PostOrder(const AbstractSyntaxNode& root);
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,31 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
std::string DumpProgram(const Program& program) {
|
||||||
|
size_t index{0};
|
||||||
|
std::map<const IR::Inst*, size_t> inst_to_index;
|
||||||
|
std::map<const IR::Block*, size_t> block_to_index;
|
||||||
|
|
||||||
|
for (const IR::Block* const block : program.blocks) {
|
||||||
|
block_to_index.emplace(block, index);
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
std::string ret;
|
||||||
|
for (const auto& block : program.blocks) {
|
||||||
|
ret += IR::DumpBlock(*block, block_to_index, inst_to_index, index) + '\n';
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,28 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include "shader_recompiler/frontend/instruction.h"
|
||||||
|
#include "shader_recompiler/ir/abstract_syntax_list.h"
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
enum class Stage : u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
struct Program {
|
||||||
|
AbstractSyntaxList syntax_list;
|
||||||
|
BlockList blocks;
|
||||||
|
BlockList post_order_blocks;
|
||||||
|
std::vector<Gcn::GcnInst> ins_list;
|
||||||
|
Stage stage;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::string DumpProgram(const Program& program);
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,471 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "shader_recompiler/exception.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
enum class FpRoundMode : u32 {
|
||||||
|
NearestEven = 0,
|
||||||
|
PlusInf = 1,
|
||||||
|
MinInf = 2,
|
||||||
|
ToZero = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FpDenormMode : u32 {
|
||||||
|
InOutFlush = 0,
|
||||||
|
InAllowOutFlush = 1,
|
||||||
|
InFlushOutAllow = 2,
|
||||||
|
InOutAllow = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
union Mode {
|
||||||
|
BitField<0, 4, FpRoundMode> fp_round;
|
||||||
|
BitField<4, 2, FpDenormMode> fp_denorm_single;
|
||||||
|
BitField<6, 2, FpDenormMode> fp_denorm_double;
|
||||||
|
BitField<8, 1, u32> dx10_clamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
union TextureInstInfo {
|
||||||
|
u32 raw;
|
||||||
|
BitField<0, 16, u32> descriptor_index;
|
||||||
|
BitField<19, 1, u32> is_depth;
|
||||||
|
BitField<20, 1, u32> has_bias;
|
||||||
|
BitField<21, 1, u32> has_lod_clamp;
|
||||||
|
BitField<22, 1, u32> relaxed_precision;
|
||||||
|
BitField<23, 2, u32> gather_component;
|
||||||
|
BitField<25, 2, u32> num_derivatives;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ScalarReg : u32 {
|
||||||
|
S0,
|
||||||
|
S1,
|
||||||
|
S2,
|
||||||
|
S3,
|
||||||
|
S4,
|
||||||
|
S5,
|
||||||
|
S6,
|
||||||
|
S7,
|
||||||
|
S8,
|
||||||
|
S9,
|
||||||
|
S10,
|
||||||
|
S11,
|
||||||
|
S12,
|
||||||
|
S13,
|
||||||
|
S14,
|
||||||
|
S15,
|
||||||
|
S16,
|
||||||
|
S17,
|
||||||
|
S18,
|
||||||
|
S19,
|
||||||
|
S20,
|
||||||
|
S21,
|
||||||
|
S22,
|
||||||
|
S23,
|
||||||
|
S24,
|
||||||
|
S25,
|
||||||
|
S26,
|
||||||
|
S27,
|
||||||
|
S28,
|
||||||
|
S29,
|
||||||
|
S30,
|
||||||
|
S31,
|
||||||
|
S32,
|
||||||
|
S33,
|
||||||
|
S34,
|
||||||
|
S35,
|
||||||
|
S36,
|
||||||
|
S37,
|
||||||
|
S38,
|
||||||
|
S39,
|
||||||
|
S40,
|
||||||
|
S41,
|
||||||
|
S42,
|
||||||
|
S43,
|
||||||
|
S44,
|
||||||
|
S45,
|
||||||
|
S46,
|
||||||
|
S47,
|
||||||
|
S48,
|
||||||
|
S49,
|
||||||
|
S50,
|
||||||
|
S51,
|
||||||
|
S52,
|
||||||
|
S53,
|
||||||
|
S54,
|
||||||
|
S55,
|
||||||
|
S56,
|
||||||
|
S57,
|
||||||
|
S58,
|
||||||
|
S59,
|
||||||
|
S60,
|
||||||
|
S61,
|
||||||
|
S62,
|
||||||
|
S63,
|
||||||
|
S64,
|
||||||
|
S65,
|
||||||
|
S66,
|
||||||
|
S67,
|
||||||
|
S68,
|
||||||
|
S69,
|
||||||
|
S70,
|
||||||
|
S71,
|
||||||
|
S72,
|
||||||
|
S73,
|
||||||
|
S74,
|
||||||
|
S75,
|
||||||
|
S76,
|
||||||
|
S77,
|
||||||
|
S78,
|
||||||
|
S79,
|
||||||
|
S80,
|
||||||
|
S81,
|
||||||
|
S82,
|
||||||
|
S83,
|
||||||
|
S84,
|
||||||
|
S85,
|
||||||
|
S86,
|
||||||
|
S87,
|
||||||
|
S88,
|
||||||
|
S89,
|
||||||
|
S90,
|
||||||
|
S91,
|
||||||
|
S92,
|
||||||
|
S93,
|
||||||
|
S94,
|
||||||
|
S95,
|
||||||
|
S96,
|
||||||
|
S97,
|
||||||
|
S98,
|
||||||
|
S99,
|
||||||
|
S100,
|
||||||
|
S101,
|
||||||
|
S102,
|
||||||
|
S103,
|
||||||
|
Max,
|
||||||
|
};
|
||||||
|
static constexpr size_t NumScalarRegs = static_cast<size_t>(ScalarReg::Max);
|
||||||
|
|
||||||
|
enum class VectorReg : u32 {
|
||||||
|
V0,
|
||||||
|
V1,
|
||||||
|
V2,
|
||||||
|
V3,
|
||||||
|
V4,
|
||||||
|
V5,
|
||||||
|
V6,
|
||||||
|
V7,
|
||||||
|
V8,
|
||||||
|
V9,
|
||||||
|
V10,
|
||||||
|
V11,
|
||||||
|
V12,
|
||||||
|
V13,
|
||||||
|
V14,
|
||||||
|
V15,
|
||||||
|
V16,
|
||||||
|
V17,
|
||||||
|
V18,
|
||||||
|
V19,
|
||||||
|
V20,
|
||||||
|
V21,
|
||||||
|
V22,
|
||||||
|
V23,
|
||||||
|
V24,
|
||||||
|
V25,
|
||||||
|
V26,
|
||||||
|
V27,
|
||||||
|
V28,
|
||||||
|
V29,
|
||||||
|
V30,
|
||||||
|
V31,
|
||||||
|
V32,
|
||||||
|
V33,
|
||||||
|
V34,
|
||||||
|
V35,
|
||||||
|
V36,
|
||||||
|
V37,
|
||||||
|
V38,
|
||||||
|
V39,
|
||||||
|
V40,
|
||||||
|
V41,
|
||||||
|
V42,
|
||||||
|
V43,
|
||||||
|
V44,
|
||||||
|
V45,
|
||||||
|
V46,
|
||||||
|
V47,
|
||||||
|
V48,
|
||||||
|
V49,
|
||||||
|
V50,
|
||||||
|
V51,
|
||||||
|
V52,
|
||||||
|
V53,
|
||||||
|
V54,
|
||||||
|
V55,
|
||||||
|
V56,
|
||||||
|
V57,
|
||||||
|
V58,
|
||||||
|
V59,
|
||||||
|
V60,
|
||||||
|
V61,
|
||||||
|
V62,
|
||||||
|
V63,
|
||||||
|
V64,
|
||||||
|
V65,
|
||||||
|
V66,
|
||||||
|
V67,
|
||||||
|
V68,
|
||||||
|
V69,
|
||||||
|
V70,
|
||||||
|
V71,
|
||||||
|
V72,
|
||||||
|
V73,
|
||||||
|
V74,
|
||||||
|
V75,
|
||||||
|
V76,
|
||||||
|
V77,
|
||||||
|
V78,
|
||||||
|
V79,
|
||||||
|
V80,
|
||||||
|
V81,
|
||||||
|
V82,
|
||||||
|
V83,
|
||||||
|
V84,
|
||||||
|
V85,
|
||||||
|
V86,
|
||||||
|
V87,
|
||||||
|
V88,
|
||||||
|
V89,
|
||||||
|
V90,
|
||||||
|
V91,
|
||||||
|
V92,
|
||||||
|
V93,
|
||||||
|
V94,
|
||||||
|
V95,
|
||||||
|
V96,
|
||||||
|
V97,
|
||||||
|
V98,
|
||||||
|
V99,
|
||||||
|
V100,
|
||||||
|
V101,
|
||||||
|
V102,
|
||||||
|
V103,
|
||||||
|
V104,
|
||||||
|
V105,
|
||||||
|
V106,
|
||||||
|
V107,
|
||||||
|
V108,
|
||||||
|
V109,
|
||||||
|
V110,
|
||||||
|
V111,
|
||||||
|
V112,
|
||||||
|
V113,
|
||||||
|
V114,
|
||||||
|
V115,
|
||||||
|
V116,
|
||||||
|
V117,
|
||||||
|
V118,
|
||||||
|
V119,
|
||||||
|
V120,
|
||||||
|
V121,
|
||||||
|
V122,
|
||||||
|
V123,
|
||||||
|
V124,
|
||||||
|
V125,
|
||||||
|
V126,
|
||||||
|
V127,
|
||||||
|
V128,
|
||||||
|
V129,
|
||||||
|
V130,
|
||||||
|
V131,
|
||||||
|
V132,
|
||||||
|
V133,
|
||||||
|
V134,
|
||||||
|
V135,
|
||||||
|
V136,
|
||||||
|
V137,
|
||||||
|
V138,
|
||||||
|
V139,
|
||||||
|
V140,
|
||||||
|
V141,
|
||||||
|
V142,
|
||||||
|
V143,
|
||||||
|
V144,
|
||||||
|
V145,
|
||||||
|
V146,
|
||||||
|
V147,
|
||||||
|
V148,
|
||||||
|
V149,
|
||||||
|
V150,
|
||||||
|
V151,
|
||||||
|
V152,
|
||||||
|
V153,
|
||||||
|
V154,
|
||||||
|
V155,
|
||||||
|
V156,
|
||||||
|
V157,
|
||||||
|
V158,
|
||||||
|
V159,
|
||||||
|
V160,
|
||||||
|
V161,
|
||||||
|
V162,
|
||||||
|
V163,
|
||||||
|
V164,
|
||||||
|
V165,
|
||||||
|
V166,
|
||||||
|
V167,
|
||||||
|
V168,
|
||||||
|
V169,
|
||||||
|
V170,
|
||||||
|
V171,
|
||||||
|
V172,
|
||||||
|
V173,
|
||||||
|
V174,
|
||||||
|
V175,
|
||||||
|
V176,
|
||||||
|
V177,
|
||||||
|
V178,
|
||||||
|
V179,
|
||||||
|
V180,
|
||||||
|
V181,
|
||||||
|
V182,
|
||||||
|
V183,
|
||||||
|
V184,
|
||||||
|
V185,
|
||||||
|
V186,
|
||||||
|
V187,
|
||||||
|
V188,
|
||||||
|
V189,
|
||||||
|
V190,
|
||||||
|
V191,
|
||||||
|
V192,
|
||||||
|
V193,
|
||||||
|
V194,
|
||||||
|
V195,
|
||||||
|
V196,
|
||||||
|
V197,
|
||||||
|
V198,
|
||||||
|
V199,
|
||||||
|
V200,
|
||||||
|
V201,
|
||||||
|
V202,
|
||||||
|
V203,
|
||||||
|
V204,
|
||||||
|
V205,
|
||||||
|
V206,
|
||||||
|
V207,
|
||||||
|
V208,
|
||||||
|
V209,
|
||||||
|
V210,
|
||||||
|
V211,
|
||||||
|
V212,
|
||||||
|
V213,
|
||||||
|
V214,
|
||||||
|
V215,
|
||||||
|
V216,
|
||||||
|
V217,
|
||||||
|
V218,
|
||||||
|
V219,
|
||||||
|
V220,
|
||||||
|
V221,
|
||||||
|
V222,
|
||||||
|
V223,
|
||||||
|
V224,
|
||||||
|
V225,
|
||||||
|
V226,
|
||||||
|
V227,
|
||||||
|
V228,
|
||||||
|
V229,
|
||||||
|
V230,
|
||||||
|
V231,
|
||||||
|
V232,
|
||||||
|
V233,
|
||||||
|
V234,
|
||||||
|
V235,
|
||||||
|
V236,
|
||||||
|
V237,
|
||||||
|
V238,
|
||||||
|
V239,
|
||||||
|
V240,
|
||||||
|
V241,
|
||||||
|
V242,
|
||||||
|
V243,
|
||||||
|
V244,
|
||||||
|
V245,
|
||||||
|
V246,
|
||||||
|
V247,
|
||||||
|
V248,
|
||||||
|
V249,
|
||||||
|
V250,
|
||||||
|
V251,
|
||||||
|
V252,
|
||||||
|
V253,
|
||||||
|
V254,
|
||||||
|
V255,
|
||||||
|
Max,
|
||||||
|
};
|
||||||
|
static constexpr size_t NumVectorRegs = static_cast<size_t>(VectorReg::Max);
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
concept RegT = std::is_same_v<T, ScalarReg> || std::is_same_v<T, VectorReg>;
|
||||||
|
|
||||||
|
template <RegT Reg>
|
||||||
|
[[nodiscard]] constexpr Reg operator+(Reg reg, int num) {
|
||||||
|
const int result{static_cast<int>(reg) + num};
|
||||||
|
if (result >= static_cast<int>(Reg::Max)) {
|
||||||
|
throw LogicError("Overflow on register arithmetic");
|
||||||
|
}
|
||||||
|
if (result < 0) {
|
||||||
|
throw LogicError("Underflow on register arithmetic");
|
||||||
|
}
|
||||||
|
return static_cast<Reg>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <RegT Reg>
|
||||||
|
[[nodiscard]] constexpr Reg operator-(Reg reg, int num) {
|
||||||
|
return reg + (-num);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <RegT Reg>
|
||||||
|
constexpr Reg operator++(Reg& reg) {
|
||||||
|
reg = reg + 1;
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <RegT Reg>
|
||||||
|
constexpr Reg operator++(Reg& reg, int) {
|
||||||
|
const Reg copy{reg};
|
||||||
|
reg = reg + 1;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <RegT Reg>
|
||||||
|
[[nodiscard]] constexpr size_t RegIndex(Reg reg) noexcept {
|
||||||
|
return static_cast<size_t>(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<Shader::IR::ScalarReg> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
auto format(Shader::IR::ScalarReg reg, format_context& ctx) const {
|
||||||
|
return fmt::format_to(ctx.out(), "SGPR{}", static_cast<u32>(reg));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<Shader::IR::VectorReg> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
auto format(Shader::IR::VectorReg reg, format_context& ctx) const {
|
||||||
|
return fmt::format_to(ctx.out(), "VGPR{}", static_cast<u32>(reg));
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include "shader_recompiler/ir/type.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
std::string NameOf(Type type) {
|
||||||
|
static constexpr std::array names{
|
||||||
|
"Opaque", "Label", "Reg", "Pred", "Attribute", "U1", "U8", "U16", "U32",
|
||||||
|
"U64", "F16", "F32", "F64", "U32x2", "U32x3", "U32x4", "F16x2", "F16x3",
|
||||||
|
"F16x4", "F32x2", "F32x3", "F32x4", "F64x2", "F64x3", "F64x4",
|
||||||
|
};
|
||||||
|
const size_t bits{static_cast<size_t>(type)};
|
||||||
|
if (bits == 0) {
|
||||||
|
return "Void";
|
||||||
|
}
|
||||||
|
std::string result;
|
||||||
|
for (size_t i = 0; i < names.size(); i++) {
|
||||||
|
if ((bits & (size_t{1} << i)) != 0) {
|
||||||
|
if (!result.empty()) {
|
||||||
|
result += '|';
|
||||||
|
}
|
||||||
|
result += names[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreTypesCompatible(Type lhs, Type rhs) noexcept {
|
||||||
|
return lhs == rhs || lhs == Type::Opaque || rhs == Type::Opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,56 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/enum.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
Void = 0,
|
||||||
|
Opaque = 1 << 0,
|
||||||
|
ScalarReg = 1 << 1,
|
||||||
|
VectorReg = 1 << 2,
|
||||||
|
Attribute = 1 << 3,
|
||||||
|
SystemValue = 1 << 4,
|
||||||
|
U1 = 1 << 5,
|
||||||
|
U8 = 1 << 6,
|
||||||
|
U16 = 1 << 7,
|
||||||
|
U32 = 1 << 8,
|
||||||
|
U64 = 1 << 9,
|
||||||
|
F16 = 1 << 10,
|
||||||
|
F32 = 1 << 11,
|
||||||
|
F64 = 1 << 12,
|
||||||
|
U32x2 = 1 << 13,
|
||||||
|
U32x3 = 1 << 14,
|
||||||
|
U32x4 = 1 << 15,
|
||||||
|
F16x2 = 1 << 16,
|
||||||
|
F16x3 = 1 << 17,
|
||||||
|
F16x4 = 1 << 18,
|
||||||
|
F32x2 = 1 << 19,
|
||||||
|
F32x3 = 1 << 20,
|
||||||
|
F32x4 = 1 << 21,
|
||||||
|
F64x2 = 1 << 22,
|
||||||
|
F64x3 = 1 << 23,
|
||||||
|
F64x4 = 1 << 24,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(Type)
|
||||||
|
|
||||||
|
[[nodiscard]] std::string NameOf(Type type);
|
||||||
|
|
||||||
|
[[nodiscard]] bool AreTypesCompatible(Type lhs, Type rhs) noexcept;
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<Shader::IR::Type> {
|
||||||
|
constexpr auto parse(format_parse_context& ctx) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
auto format(Shader::IR::Type type, format_context& ctx) const {
|
||||||
|
return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(type));
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,93 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/value.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
Value::Value(IR::Inst* value) noexcept : type{Type::Opaque}, inst{value} {}
|
||||||
|
|
||||||
|
Value::Value(IR::ScalarReg reg) noexcept : type{Type::ScalarReg}, sreg{reg} {}
|
||||||
|
|
||||||
|
Value::Value(IR::VectorReg reg) noexcept : type{Type::VectorReg}, vreg{reg} {}
|
||||||
|
|
||||||
|
Value::Value(IR::Attribute value) noexcept : type{Type::Attribute}, attribute{value} {}
|
||||||
|
|
||||||
|
Value::Value(bool value) noexcept : type{Type::U1}, imm_u1{value} {}
|
||||||
|
|
||||||
|
Value::Value(u8 value) noexcept : type{Type::U8}, imm_u8{value} {}
|
||||||
|
|
||||||
|
Value::Value(u16 value) noexcept : type{Type::U16}, imm_u16{value} {}
|
||||||
|
|
||||||
|
Value::Value(u32 value) noexcept : type{Type::U32}, imm_u32{value} {}
|
||||||
|
|
||||||
|
Value::Value(f32 value) noexcept : type{Type::F32}, imm_f32{value} {}
|
||||||
|
|
||||||
|
Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {}
|
||||||
|
|
||||||
|
Value::Value(f64 value) noexcept : type{Type::F64}, imm_f64{value} {}
|
||||||
|
|
||||||
|
IR::Type Value::Type() const noexcept {
|
||||||
|
if (IsPhi()) {
|
||||||
|
// The type of a phi node is stored in its flags
|
||||||
|
return inst->Flags<IR::Type>();
|
||||||
|
}
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).Type();
|
||||||
|
}
|
||||||
|
if (type == Type::Opaque) {
|
||||||
|
return inst->Type();
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Value::operator==(const Value& other) const {
|
||||||
|
if (type != other.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case Type::Void:
|
||||||
|
return true;
|
||||||
|
case Type::Opaque:
|
||||||
|
return inst == other.inst;
|
||||||
|
case Type::ScalarReg:
|
||||||
|
return sreg == other.sreg;
|
||||||
|
case Type::VectorReg:
|
||||||
|
return vreg == other.vreg;
|
||||||
|
case Type::Attribute:
|
||||||
|
return attribute == other.attribute;
|
||||||
|
case Type::U1:
|
||||||
|
return imm_u1 == other.imm_u1;
|
||||||
|
case Type::U8:
|
||||||
|
return imm_u8 == other.imm_u8;
|
||||||
|
case Type::U16:
|
||||||
|
case Type::F16:
|
||||||
|
return imm_u16 == other.imm_u16;
|
||||||
|
case Type::U32:
|
||||||
|
case Type::F32:
|
||||||
|
return imm_u32 == other.imm_u32;
|
||||||
|
case Type::U64:
|
||||||
|
case Type::F64:
|
||||||
|
return imm_u64 == other.imm_u64;
|
||||||
|
case Type::U32x2:
|
||||||
|
case Type::U32x3:
|
||||||
|
case Type::U32x4:
|
||||||
|
case Type::F16x2:
|
||||||
|
case Type::F16x3:
|
||||||
|
case Type::F16x4:
|
||||||
|
case Type::F32x2:
|
||||||
|
case Type::F32x3:
|
||||||
|
case Type::F32x4:
|
||||||
|
case Type::F64x2:
|
||||||
|
case Type::F64x3:
|
||||||
|
case Type::F64x4:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw LogicError("Invalid type {}", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Value::operator!=(const Value& other) const {
|
||||||
|
return !operator==(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,353 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <bit>
|
||||||
|
#include <cstring>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
#include <boost/intrusive/list.hpp>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "shader_recompiler/exception.h"
|
||||||
|
#include "shader_recompiler/ir/attribute.h"
|
||||||
|
#include "shader_recompiler/ir/opcodes.h"
|
||||||
|
#include "shader_recompiler/ir/reg.h"
|
||||||
|
#include "shader_recompiler/ir/type.h"
|
||||||
|
|
||||||
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
class Block;
|
||||||
|
class Inst;
|
||||||
|
|
||||||
|
struct AssociatedInsts;
|
||||||
|
|
||||||
|
class Value {
|
||||||
|
public:
|
||||||
|
Value() noexcept = default;
|
||||||
|
explicit Value(IR::Inst* value) noexcept;
|
||||||
|
explicit Value(IR::ScalarReg reg) noexcept;
|
||||||
|
explicit Value(IR::VectorReg reg) noexcept;
|
||||||
|
explicit Value(IR::Attribute value) noexcept;
|
||||||
|
explicit Value(bool value) noexcept;
|
||||||
|
explicit Value(u8 value) noexcept;
|
||||||
|
explicit Value(u16 value) noexcept;
|
||||||
|
explicit Value(u32 value) noexcept;
|
||||||
|
explicit Value(f32 value) noexcept;
|
||||||
|
explicit Value(u64 value) noexcept;
|
||||||
|
explicit Value(f64 value) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsIdentity() const noexcept;
|
||||||
|
[[nodiscard]] bool IsPhi() const noexcept;
|
||||||
|
[[nodiscard]] bool IsEmpty() const noexcept;
|
||||||
|
[[nodiscard]] bool IsImmediate() const noexcept;
|
||||||
|
[[nodiscard]] IR::Type Type() const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] IR::Inst* Inst() const;
|
||||||
|
[[nodiscard]] IR::Inst* InstRecursive() const;
|
||||||
|
[[nodiscard]] IR::Inst* TryInstRecursive() const;
|
||||||
|
[[nodiscard]] IR::Value Resolve() const;
|
||||||
|
[[nodiscard]] IR::ScalarReg ScalarReg() const;
|
||||||
|
[[nodiscard]] IR::VectorReg VectorReg() const;
|
||||||
|
[[nodiscard]] IR::Attribute Attribute() const;
|
||||||
|
[[nodiscard]] bool U1() const;
|
||||||
|
[[nodiscard]] u8 U8() const;
|
||||||
|
[[nodiscard]] u16 U16() const;
|
||||||
|
[[nodiscard]] u32 U32() const;
|
||||||
|
[[nodiscard]] f32 F32() const;
|
||||||
|
[[nodiscard]] u64 U64() const;
|
||||||
|
[[nodiscard]] f64 F64() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator==(const Value& other) const;
|
||||||
|
[[nodiscard]] bool operator!=(const Value& other) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
IR::Type type{};
|
||||||
|
union {
|
||||||
|
IR::Inst* inst{};
|
||||||
|
IR::ScalarReg sreg;
|
||||||
|
IR::VectorReg vreg;
|
||||||
|
IR::Attribute attribute;
|
||||||
|
bool imm_u1;
|
||||||
|
u8 imm_u8;
|
||||||
|
u16 imm_u16;
|
||||||
|
u32 imm_u32;
|
||||||
|
f32 imm_f32;
|
||||||
|
u64 imm_u64;
|
||||||
|
f64 imm_f64;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(static_cast<u32>(IR::Type::Void) == 0, "memset relies on IR::Type being zero");
|
||||||
|
static_assert(std::is_trivially_copyable_v<Value>);
|
||||||
|
|
||||||
|
template <IR::Type type_>
|
||||||
|
class TypedValue : public Value {
|
||||||
|
public:
|
||||||
|
TypedValue() = default;
|
||||||
|
|
||||||
|
template <IR::Type other_type>
|
||||||
|
requires((other_type & type_) != IR::Type::Void)
|
||||||
|
explicit(false) TypedValue(const TypedValue<other_type>& value) : Value(value) {}
|
||||||
|
|
||||||
|
explicit TypedValue(const Value& value) : Value(value) {
|
||||||
|
if ((value.Type() & type_) == IR::Type::Void) {
|
||||||
|
throw InvalidArgument("Incompatible types {} and {}", type_, value.Type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit TypedValue(IR::Inst* inst_) : TypedValue(Value(inst_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Inst : public boost::intrusive::list_base_hook<> {
|
||||||
|
public:
|
||||||
|
explicit Inst(IR::Opcode op_, u32 flags_) noexcept;
|
||||||
|
explicit Inst(const Inst& base);
|
||||||
|
~Inst();
|
||||||
|
|
||||||
|
Inst& operator=(const Inst&) = delete;
|
||||||
|
|
||||||
|
Inst& operator=(Inst&&) = delete;
|
||||||
|
Inst(Inst&&) = delete;
|
||||||
|
|
||||||
|
/// Get the number of uses this instruction has.
|
||||||
|
[[nodiscard]] int UseCount() const noexcept {
|
||||||
|
return use_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines whether this instruction has uses or not.
|
||||||
|
[[nodiscard]] bool HasUses() const noexcept {
|
||||||
|
return use_count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the opcode this microinstruction represents.
|
||||||
|
[[nodiscard]] IR::Opcode GetOpcode() const noexcept {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines whether or not this instruction may have side effects.
|
||||||
|
[[nodiscard]] bool MayHaveSideEffects() const noexcept;
|
||||||
|
|
||||||
|
/// Determines if all arguments of this instruction are immediates.
|
||||||
|
[[nodiscard]] bool AreAllArgsImmediates() const;
|
||||||
|
|
||||||
|
/// Get the type this instruction returns.
|
||||||
|
[[nodiscard]] IR::Type Type() const;
|
||||||
|
|
||||||
|
/// Get the number of arguments this instruction has.
|
||||||
|
[[nodiscard]] size_t NumArgs() const {
|
||||||
|
return op == IR::Opcode::Phi ? phi_args.size() : NumArgsOf(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of a given argument index.
|
||||||
|
[[nodiscard]] Value Arg(size_t index) const noexcept {
|
||||||
|
if (op == IR::Opcode::Phi) {
|
||||||
|
return phi_args[index].second;
|
||||||
|
} else {
|
||||||
|
return args[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the value of a given argument index.
|
||||||
|
void SetArg(size_t index, Value value);
|
||||||
|
|
||||||
|
/// Get a pointer to the block of a phi argument.
|
||||||
|
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
||||||
|
/// Add phi operand to a phi instruction.
|
||||||
|
void AddPhiOperand(Block* predecessor, const Value& value);
|
||||||
|
|
||||||
|
void Invalidate();
|
||||||
|
void ClearArgs();
|
||||||
|
|
||||||
|
void ReplaceUsesWith(Value replacement);
|
||||||
|
|
||||||
|
void ReplaceOpcode(IR::Opcode opcode);
|
||||||
|
|
||||||
|
template <typename FlagsType>
|
||||||
|
requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>)
|
||||||
|
[[nodiscard]] FlagsType Flags() const noexcept {
|
||||||
|
FlagsType ret;
|
||||||
|
std::memcpy(reinterpret_cast<char*>(&ret), &flags, sizeof(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FlagsType>
|
||||||
|
requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>)
|
||||||
|
void SetFlags(FlagsType value) noexcept {
|
||||||
|
std::memcpy(&flags, &value, sizeof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intrusively store the host definition of this instruction.
|
||||||
|
template <typename DefinitionType>
|
||||||
|
void SetDefinition(DefinitionType def) {
|
||||||
|
definition = std::bit_cast<u32>(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the intrusively stored host definition of this instruction.
|
||||||
|
template <typename DefinitionType>
|
||||||
|
[[nodiscard]] DefinitionType Definition() const noexcept {
|
||||||
|
return std::bit_cast<DefinitionType>(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct NonTriviallyDummy {
|
||||||
|
NonTriviallyDummy() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
void Use(const Value& value);
|
||||||
|
void UndoUse(const Value& value);
|
||||||
|
|
||||||
|
IR::Opcode op{};
|
||||||
|
int use_count{};
|
||||||
|
u32 flags{};
|
||||||
|
u32 definition{};
|
||||||
|
union {
|
||||||
|
NonTriviallyDummy dummy{};
|
||||||
|
boost::container::small_vector<std::pair<Block*, Value>, 2> phi_args;
|
||||||
|
std::array<Value, 5> args;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Inst) <= 128, "Inst size unintentionally increased");
|
||||||
|
|
||||||
|
using U1 = TypedValue<Type::U1>;
|
||||||
|
using U8 = TypedValue<Type::U8>;
|
||||||
|
using U16 = TypedValue<Type::U16>;
|
||||||
|
using U32 = TypedValue<Type::U32>;
|
||||||
|
using U64 = TypedValue<Type::U64>;
|
||||||
|
using F16 = TypedValue<Type::F16>;
|
||||||
|
using F32 = TypedValue<Type::F32>;
|
||||||
|
using F64 = TypedValue<Type::F64>;
|
||||||
|
using U32F32 = TypedValue<Type::U32 | Type::F32>;
|
||||||
|
using U32U64 = TypedValue<Type::U32 | Type::U64>;
|
||||||
|
using F32F64 = TypedValue<Type::F32 | Type::F64>;
|
||||||
|
using F16F32F64 = TypedValue<Type::F16 | Type::F32 | Type::F64>;
|
||||||
|
using UAny = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>;
|
||||||
|
|
||||||
|
inline bool Value::IsIdentity() const noexcept {
|
||||||
|
return type == Type::Opaque && inst->GetOpcode() == Opcode::Identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Value::IsPhi() const noexcept {
|
||||||
|
return type == Type::Opaque && inst->GetOpcode() == Opcode::Phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Value::IsEmpty() const noexcept {
|
||||||
|
return type == Type::Void;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Value::IsImmediate() const noexcept {
|
||||||
|
IR::Type current_type{type};
|
||||||
|
const IR::Inst* current_inst{inst};
|
||||||
|
while (current_type == Type::Opaque && current_inst->GetOpcode() == Opcode::Identity) {
|
||||||
|
const Value& arg{current_inst->Arg(0)};
|
||||||
|
current_type = arg.type;
|
||||||
|
current_inst = arg.inst;
|
||||||
|
}
|
||||||
|
return current_type != Type::Opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IR::Inst* Value::Inst() const {
|
||||||
|
DEBUG_ASSERT(type == Type::Opaque);
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IR::Inst* Value::InstRecursive() const {
|
||||||
|
DEBUG_ASSERT(type == Type::Opaque);
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).InstRecursive();
|
||||||
|
}
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IR::Inst* Value::TryInstRecursive() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).TryInstRecursive();
|
||||||
|
}
|
||||||
|
return type == Type::Opaque ? inst : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IR::Value Value::Resolve() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).Resolve();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IR::ScalarReg Value::ScalarReg() const {
|
||||||
|
DEBUG_ASSERT(type == Type::ScalarReg);
|
||||||
|
return sreg;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IR::VectorReg Value::VectorReg() const {
|
||||||
|
DEBUG_ASSERT(type == Type::VectorReg);
|
||||||
|
return vreg;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IR::Attribute Value::Attribute() const {
|
||||||
|
DEBUG_ASSERT(type == Type::Attribute);
|
||||||
|
return attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Value::U1() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).U1();
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(type == Type::U1);
|
||||||
|
return imm_u1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u8 Value::U8() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).U8();
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(type == Type::U8);
|
||||||
|
return imm_u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u16 Value::U16() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).U16();
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(type == Type::U16);
|
||||||
|
return imm_u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u32 Value::U32() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).U32();
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(type == Type::U32);
|
||||||
|
return imm_u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline f32 Value::F32() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).F32();
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(type == Type::F32);
|
||||||
|
return imm_f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u64 Value::U64() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).U64();
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(type == Type::U64);
|
||||||
|
return imm_u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline f64 Value::F64() const {
|
||||||
|
if (IsIdentity()) {
|
||||||
|
return inst->Arg(0).F64();
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(type == Type::F64);
|
||||||
|
return imm_f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool IsPhi(const Inst& inst) {
|
||||||
|
return inst.GetOpcode() == Opcode::Phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::IR
|
|
@ -0,0 +1,107 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires std::is_destructible_v<T>
|
||||||
|
class ObjectPool {
|
||||||
|
public:
|
||||||
|
explicit ObjectPool(size_t chunk_size = 8192) : new_chunk_size{chunk_size} {
|
||||||
|
node = &chunks.emplace_back(new_chunk_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
requires std::is_constructible_v<T, Args...>
|
||||||
|
[[nodiscard]] T* Create(Args&&... args) {
|
||||||
|
return std::construct_at(Memory(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseContents() {
|
||||||
|
if (chunks.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Chunk& root{chunks.front()};
|
||||||
|
if (root.used_objects == root.num_objects) {
|
||||||
|
// Root chunk has been filled, squash allocations into it
|
||||||
|
const size_t total_objects{root.num_objects + new_chunk_size * (chunks.size() - 1)};
|
||||||
|
chunks.clear();
|
||||||
|
chunks.emplace_back(total_objects);
|
||||||
|
} else {
|
||||||
|
root.Release();
|
||||||
|
chunks.resize(1);
|
||||||
|
}
|
||||||
|
chunks.shrink_to_fit();
|
||||||
|
node = &chunks.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct NonTrivialDummy {
|
||||||
|
NonTrivialDummy() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
union Storage {
|
||||||
|
Storage() noexcept {}
|
||||||
|
~Storage() noexcept {}
|
||||||
|
|
||||||
|
NonTrivialDummy dummy{};
|
||||||
|
T object;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Chunk {
|
||||||
|
explicit Chunk() = default;
|
||||||
|
explicit Chunk(size_t size)
|
||||||
|
: num_objects{size}, storage{std::make_unique<Storage[]>(size)} {}
|
||||||
|
|
||||||
|
Chunk& operator=(Chunk&& rhs) noexcept {
|
||||||
|
Release();
|
||||||
|
used_objects = std::exchange(rhs.used_objects, 0);
|
||||||
|
num_objects = std::exchange(rhs.num_objects, 0);
|
||||||
|
storage = std::move(rhs.storage);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk(Chunk&& rhs) noexcept
|
||||||
|
: used_objects{std::exchange(rhs.used_objects, 0)},
|
||||||
|
num_objects{std::exchange(rhs.num_objects, 0)}, storage{std::move(rhs.storage)} {}
|
||||||
|
|
||||||
|
~Chunk() {
|
||||||
|
Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release() {
|
||||||
|
std::destroy_n(storage.get(), used_objects);
|
||||||
|
used_objects = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t used_objects{};
|
||||||
|
size_t num_objects{};
|
||||||
|
std::unique_ptr<Storage[]> storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] T* Memory() {
|
||||||
|
Chunk* const chunk{FreeChunk()};
|
||||||
|
return &chunk->storage[chunk->used_objects++].object;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Chunk* FreeChunk() {
|
||||||
|
if (node->used_objects != node->num_objects) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
node = &chunks.emplace_back(new_chunk_size);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk* node{};
|
||||||
|
std::vector<Chunk> chunks;
|
||||||
|
size_t new_chunk_size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
|
||||||
|
struct Profile {
|
||||||
|
u32 supported_spirv{0x00010000};
|
||||||
|
bool unified_descriptor_binding{};
|
||||||
|
bool support_descriptor_aliasing{};
|
||||||
|
bool support_int8{};
|
||||||
|
bool support_int16{};
|
||||||
|
bool support_int64{};
|
||||||
|
bool support_vertex_instance_id{};
|
||||||
|
bool support_float_controls{};
|
||||||
|
bool support_separate_denorm_behavior{};
|
||||||
|
bool support_separate_rounding_mode{};
|
||||||
|
bool support_fp16_denorm_preserve{};
|
||||||
|
bool support_fp32_denorm_preserve{};
|
||||||
|
bool support_fp16_denorm_flush{};
|
||||||
|
bool support_fp32_denorm_flush{};
|
||||||
|
bool support_fp16_signed_zero_nan_preserve{};
|
||||||
|
bool support_fp32_signed_zero_nan_preserve{};
|
||||||
|
bool support_fp64_signed_zero_nan_preserve{};
|
||||||
|
bool support_explicit_workgroup_layout{};
|
||||||
|
bool support_vote{};
|
||||||
|
bool support_viewport_mask{};
|
||||||
|
bool support_typeless_image_loads{};
|
||||||
|
bool support_derivative_control{};
|
||||||
|
bool support_geometry_shader_passthrough{};
|
||||||
|
bool support_native_ndc{};
|
||||||
|
bool support_scaled_attributes{};
|
||||||
|
bool support_multi_viewport{};
|
||||||
|
bool support_geometry_streams{};
|
||||||
|
|
||||||
|
bool warp_size_potentially_larger_than_guest{};
|
||||||
|
|
||||||
|
bool lower_left_origin_mode{};
|
||||||
|
/// Fragment outputs have to be declared even if they are not written to avoid undefined values.
|
||||||
|
/// See Ori and the Blind Forest's main menu for reference.
|
||||||
|
bool need_declared_frag_colors{};
|
||||||
|
/// Prevents fast math optimizations that may cause inaccuracies
|
||||||
|
bool need_fastmath_off{};
|
||||||
|
|
||||||
|
/// OpFClamp is broken and OpFMax + OpFMin should be used instead
|
||||||
|
bool has_broken_spirv_clamp{};
|
||||||
|
/// The Position builtin needs to be wrapped in a struct when used as an input
|
||||||
|
bool has_broken_spirv_position_input{};
|
||||||
|
/// Offset image operands with an unsigned type do not work
|
||||||
|
bool has_broken_unsigned_image_offsets{};
|
||||||
|
/// Signed instructions with unsigned data types are misinterpreted
|
||||||
|
bool has_broken_signed_operations{};
|
||||||
|
/// Float controls break when fp16 is enabled
|
||||||
|
bool has_broken_fp16_float_controls{};
|
||||||
|
/// Ignores SPIR-V ordered vs unordered using GLSL semantics
|
||||||
|
bool ignore_nan_fp_comparisons{};
|
||||||
|
|
||||||
|
/// Maxwell and earlier nVidia architectures have broken robust support
|
||||||
|
bool has_broken_robust{};
|
||||||
|
|
||||||
|
u64 min_ssbo_alignment{};
|
||||||
|
|
||||||
|
u32 max_user_clip_distances{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader
|
|
@ -0,0 +1,72 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include "shader_recompiler/backend/spirv/emit_spirv.h"
|
||||||
|
#include "shader_recompiler/frontend/control_flow_graph.h"
|
||||||
|
#include "shader_recompiler/frontend/decode.h"
|
||||||
|
#include "shader_recompiler/frontend/structured_control_flow.h"
|
||||||
|
#include "shader_recompiler/ir/passes/passes.h"
|
||||||
|
#include "shader_recompiler/ir/post_order.h"
|
||||||
|
#include "shader_recompiler/recompiler.h"
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
|
||||||
|
IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) {
|
||||||
|
size_t num_syntax_blocks{};
|
||||||
|
for (const auto& node : syntax_list) {
|
||||||
|
if (node.type == IR::AbstractSyntaxNode::Type::Block) {
|
||||||
|
++num_syntax_blocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IR::BlockList blocks;
|
||||||
|
blocks.reserve(num_syntax_blocks);
|
||||||
|
u32 order_index{};
|
||||||
|
for (const auto& node : syntax_list) {
|
||||||
|
if (node.type == IR::AbstractSyntaxNode::Type::Block) {
|
||||||
|
blocks.push_back(node.data.block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> TranslateProgram(ObjectPool<IR::Inst>& inst_pool,
|
||||||
|
ObjectPool<IR::Block>& block_pool, Stage stage,
|
||||||
|
std::span<const u32> token) {
|
||||||
|
// Ensure first instruction is expected.
|
||||||
|
constexpr u32 token_mov_vcchi = 0xBEEB03FF;
|
||||||
|
ASSERT_MSG(token[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm");
|
||||||
|
|
||||||
|
Gcn::GcnCodeSlice slice(token.data(), token.data() + token.size());
|
||||||
|
Gcn::GcnDecodeContext decoder;
|
||||||
|
|
||||||
|
// Decode and save instructions
|
||||||
|
IR::Program program;
|
||||||
|
program.ins_list.reserve(token.size());
|
||||||
|
while (!slice.atEnd()) {
|
||||||
|
program.ins_list.emplace_back(decoder.decodeInstruction(slice));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create control flow graph
|
||||||
|
ObjectPool<Gcn::Block> gcn_block_pool{64};
|
||||||
|
Gcn::CFG cfg{gcn_block_pool, program.ins_list};
|
||||||
|
|
||||||
|
// Structurize control flow graph and create program.
|
||||||
|
program.syntax_list = Shader::Gcn::BuildASL(inst_pool, block_pool, cfg, stage);
|
||||||
|
program.blocks = GenerateBlocks(program.syntax_list);
|
||||||
|
program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front());
|
||||||
|
program.stage = stage;
|
||||||
|
|
||||||
|
// Run optimization passes
|
||||||
|
Shader::Optimization::SsaRewritePass(program.post_order_blocks);
|
||||||
|
Shader::Optimization::ConstantPropagationPass(program.post_order_blocks);
|
||||||
|
Shader::Optimization::IdentityRemovalPass(program.blocks);
|
||||||
|
// Shader::Optimization::ResourceTrackingPass(program.post_order_blocks);
|
||||||
|
Shader::Optimization::DeadCodeEliminationPass(program.blocks);
|
||||||
|
|
||||||
|
// TODO: Pass profile from vulkan backend
|
||||||
|
const auto code = Backend::SPIRV::EmitSPIRV(Profile{}, program);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader
|
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
|
||||||
|
struct BinaryInfo {
|
||||||
|
u8 signature[7];
|
||||||
|
u8 version;
|
||||||
|
u32 pssl_or_cg : 1;
|
||||||
|
u32 cached : 1;
|
||||||
|
u32 type : 4;
|
||||||
|
u32 source_type : 2;
|
||||||
|
u32 length : 24;
|
||||||
|
u8 chunk_usage_base_offset_in_dw;
|
||||||
|
u8 num_input_usage_slots;
|
||||||
|
u8 is_srt : 1;
|
||||||
|
u8 is_srt_used_info_valid : 1;
|
||||||
|
u8 is_extended_usage_info : 1;
|
||||||
|
u8 reserved2 : 5;
|
||||||
|
u8 reserved3;
|
||||||
|
u64 shader_hash;
|
||||||
|
u32 crc32;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<u32> TranslateProgram(ObjectPool<IR::Inst>& inst_pool,
|
||||||
|
ObjectPool<IR::Block>& block_pool, Stage stage,
|
||||||
|
std::span<const u32> code);
|
||||||
|
|
||||||
|
} // namespace Shader
|
|
@ -0,0 +1,139 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <boost/container/small_vector.hpp>
|
||||||
|
#include "shader_recompiler/ir/type.h"
|
||||||
|
|
||||||
|
namespace Shader {
|
||||||
|
|
||||||
|
enum class AttributeType : u8 {
|
||||||
|
Float,
|
||||||
|
SignedInt,
|
||||||
|
UnsignedInt,
|
||||||
|
SignedScaled,
|
||||||
|
UnsignedScaled,
|
||||||
|
Disabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class InputTopology {
|
||||||
|
Points,
|
||||||
|
Lines,
|
||||||
|
LinesAdjacency,
|
||||||
|
Triangles,
|
||||||
|
TrianglesAdjacency,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CompareFunction {
|
||||||
|
Never,
|
||||||
|
Less,
|
||||||
|
Equal,
|
||||||
|
LessThanEqual,
|
||||||
|
Greater,
|
||||||
|
NotEqual,
|
||||||
|
GreaterThanEqual,
|
||||||
|
Always,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Stage : u32 {
|
||||||
|
Vertex,
|
||||||
|
TessellationControl,
|
||||||
|
TessellationEval,
|
||||||
|
Geometry,
|
||||||
|
Fragment,
|
||||||
|
Compute,
|
||||||
|
};
|
||||||
|
constexpr u32 MaxStageTypes = 6;
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Stage StageFromIndex(size_t index) noexcept {
|
||||||
|
return static_cast<Stage>(static_cast<size_t>(Stage::Vertex) + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TextureType : u32 {
|
||||||
|
Color1D,
|
||||||
|
ColorArray1D,
|
||||||
|
Color2D,
|
||||||
|
ColorArray2D,
|
||||||
|
Color3D,
|
||||||
|
ColorCube,
|
||||||
|
Buffer,
|
||||||
|
};
|
||||||
|
constexpr u32 NUM_TEXTURE_TYPES = 7;
|
||||||
|
|
||||||
|
enum class Interpolation {
|
||||||
|
Smooth,
|
||||||
|
Flat,
|
||||||
|
NoPerspective,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConstantBufferDescriptor {
|
||||||
|
u32 index;
|
||||||
|
u32 count;
|
||||||
|
|
||||||
|
auto operator<=>(const ConstantBufferDescriptor&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextureDescriptor {
|
||||||
|
TextureType type;
|
||||||
|
bool is_eud;
|
||||||
|
bool is_depth;
|
||||||
|
bool is_multisample;
|
||||||
|
bool is_storage;
|
||||||
|
u32 count;
|
||||||
|
u32 eud_offset_dwords;
|
||||||
|
u32 ud_index_dwords;
|
||||||
|
|
||||||
|
auto operator<=>(const TextureDescriptor&) const = default;
|
||||||
|
};
|
||||||
|
using TextureDescriptors = boost::container::small_vector<TextureDescriptor, 12>;
|
||||||
|
|
||||||
|
struct Info {
|
||||||
|
bool uses_workgroup_id{};
|
||||||
|
bool uses_local_invocation_id{};
|
||||||
|
bool uses_invocation_id{};
|
||||||
|
bool uses_invocation_info{};
|
||||||
|
bool uses_sample_id{};
|
||||||
|
|
||||||
|
std::array<Interpolation, 32> interpolation{};
|
||||||
|
// VaryingState loads;
|
||||||
|
// VaryingState stores;
|
||||||
|
// VaryingState passthrough;
|
||||||
|
|
||||||
|
std::array<bool, 8> stores_frag_color{};
|
||||||
|
bool stores_sample_mask{};
|
||||||
|
bool stores_frag_depth{};
|
||||||
|
|
||||||
|
bool uses_fp16{};
|
||||||
|
bool uses_fp64{};
|
||||||
|
bool uses_fp16_denorms_flush{};
|
||||||
|
bool uses_fp16_denorms_preserve{};
|
||||||
|
bool uses_fp32_denorms_flush{};
|
||||||
|
bool uses_fp32_denorms_preserve{};
|
||||||
|
bool uses_int8{};
|
||||||
|
bool uses_int16{};
|
||||||
|
bool uses_int64{};
|
||||||
|
bool uses_image_1d{};
|
||||||
|
bool uses_sampled_1d{};
|
||||||
|
bool uses_subgroup_vote{};
|
||||||
|
bool uses_subgroup_mask{};
|
||||||
|
bool uses_derivatives{};
|
||||||
|
|
||||||
|
IR::Type used_constant_buffer_types{};
|
||||||
|
IR::Type used_storage_buffer_types{};
|
||||||
|
IR::Type used_indirect_cbuf_types{};
|
||||||
|
|
||||||
|
// std::array<u32, MAX_CBUFS> constant_buffer_used_sizes{};
|
||||||
|
u32 used_clip_distances{};
|
||||||
|
|
||||||
|
// boost::container::static_vector<ConstantBufferDescriptor, MAX_CBUFS>
|
||||||
|
// constant_buffer_descriptors;
|
||||||
|
// boost::container::static_vector<StorageBufferDescriptor, MAX_SSBOS>
|
||||||
|
// storage_buffers_descriptors; TextureBufferDescriptors texture_buffer_descriptors;
|
||||||
|
// ImageBufferDescriptors image_buffer_descriptors;
|
||||||
|
// TextureDescriptors texture_descriptors;
|
||||||
|
// ImageDescriptors image_descriptors;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Shader
|
|
@ -6,6 +6,7 @@
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "video_core/amdgpu/liverpool.h"
|
#include "video_core/amdgpu/liverpool.h"
|
||||||
#include "video_core/amdgpu/pm4_cmds.h"
|
#include "video_core/amdgpu/pm4_cmds.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
|
|
||||||
namespace AmdGpu {
|
namespace AmdGpu {
|
||||||
|
|
||||||
|
@ -107,7 +108,7 @@ void Liverpool::ProcessCmdList(const u32* cmdbuf, u32 size_in_bytes) {
|
||||||
regs.index_base_address.base_addr_hi.Assign(draw_index->index_base_hi);
|
regs.index_base_address.base_addr_hi.Assign(draw_index->index_base_hi);
|
||||||
regs.num_indices = draw_index->index_count;
|
regs.num_indices = draw_index->index_count;
|
||||||
regs.draw_initiator = draw_index->draw_initiator;
|
regs.draw_initiator = draw_index->draw_initiator;
|
||||||
// rasterizer->DrawIndex();
|
rasterizer->DrawIndex();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PM4ItOpcode::DrawIndexAuto: {
|
case PM4ItOpcode::DrawIndexAuto: {
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
class Rasterizer;
|
||||||
|
}
|
||||||
|
|
||||||
namespace AmdGpu {
|
namespace AmdGpu {
|
||||||
|
|
||||||
#define GFX6_3D_REG_INDEX(field_name) (offsetof(AmdGpu::Liverpool::Regs, field_name) / sizeof(u32))
|
#define GFX6_3D_REG_INDEX(field_name) (offsetof(AmdGpu::Liverpool::Regs, field_name) / sizeof(u32))
|
||||||
|
@ -46,9 +50,10 @@ struct Liverpool {
|
||||||
} settings;
|
} settings;
|
||||||
UserData user_data;
|
UserData user_data;
|
||||||
|
|
||||||
const u8* Address() const {
|
template <typename T = u8>
|
||||||
|
const T* Address() const {
|
||||||
const uintptr_t addr = uintptr_t(address_hi) << 40 | uintptr_t(address_lo) << 8;
|
const uintptr_t addr = uintptr_t(address_hi) << 40 | uintptr_t(address_lo) << 8;
|
||||||
return reinterpret_cast<const u8*>(addr);
|
return reinterpret_cast<const T*>(addr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -631,10 +636,15 @@ public:
|
||||||
|
|
||||||
void WaitGpuIdle();
|
void WaitGpuIdle();
|
||||||
|
|
||||||
|
void BindRasterizer(Vulkan::Rasterizer* rasterizer_) {
|
||||||
|
rasterizer = rasterizer_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ProcessCmdList(const u32* cmdbuf, u32 size_in_bytes);
|
void ProcessCmdList(const u32* cmdbuf, u32 size_in_bytes);
|
||||||
void Process(std::stop_token stoken);
|
void Process(std::stop_token stoken);
|
||||||
|
|
||||||
|
Vulkan::Rasterizer* rasterizer;
|
||||||
std::jthread process_thread{};
|
std::jthread process_thread{};
|
||||||
std::queue<std::span<const u32>> gfx_ring{};
|
std::queue<std::span<const u32>> gfx_ring{};
|
||||||
std::condition_variable_any cv_submit{};
|
std::condition_variable_any cv_submit{};
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
|
||||||
|
|
||||||
|
namespace Vulkan::LiverpoolToVK {
|
||||||
|
|
||||||
|
vk::StencilOp StencilOp(Liverpool::StencilFunc op) {
|
||||||
|
switch (op) {
|
||||||
|
case Liverpool::StencilFunc::Keep:
|
||||||
|
return vk::StencilOp::eKeep;
|
||||||
|
case Liverpool::StencilFunc::Zero:
|
||||||
|
return vk::StencilOp::eZero;
|
||||||
|
case Liverpool::StencilFunc::AddClamp:
|
||||||
|
return vk::StencilOp::eIncrementAndClamp;
|
||||||
|
case Liverpool::StencilFunc::SubClamp:
|
||||||
|
return vk::StencilOp::eDecrementAndClamp;
|
||||||
|
case Liverpool::StencilFunc::Invert:
|
||||||
|
return vk::StencilOp::eInvert;
|
||||||
|
case Liverpool::StencilFunc::AddWrap:
|
||||||
|
return vk::StencilOp::eIncrementAndWrap;
|
||||||
|
case Liverpool::StencilFunc::SubWrap:
|
||||||
|
return vk::StencilOp::eDecrementAndWrap;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return vk::StencilOp::eKeep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::CompareOp CompareOp(Liverpool::CompareFunc func) {
|
||||||
|
switch (func) {
|
||||||
|
case Liverpool::CompareFunc::Always:
|
||||||
|
return vk::CompareOp::eAlways;
|
||||||
|
case Liverpool::CompareFunc::Equal:
|
||||||
|
return vk::CompareOp::eEqual;
|
||||||
|
case Liverpool::CompareFunc::GreaterEqual:
|
||||||
|
return vk::CompareOp::eGreaterOrEqual;
|
||||||
|
case Liverpool::CompareFunc::Greater:
|
||||||
|
return vk::CompareOp::eGreater;
|
||||||
|
case Liverpool::CompareFunc::LessEqual:
|
||||||
|
return vk::CompareOp::eLessOrEqual;
|
||||||
|
case Liverpool::CompareFunc::Less:
|
||||||
|
return vk::CompareOp::eLess;
|
||||||
|
case Liverpool::CompareFunc::NotEqual:
|
||||||
|
return vk::CompareOp::eNotEqual;
|
||||||
|
case Liverpool::CompareFunc::Never:
|
||||||
|
return vk::CompareOp::eNever;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return vk::CompareOp::eAlways;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::PrimitiveTopology PrimitiveType(Liverpool::PrimitiveType type) {
|
||||||
|
switch (type) {
|
||||||
|
case Liverpool::PrimitiveType::PointList:
|
||||||
|
return vk::PrimitiveTopology::ePointList;
|
||||||
|
case Liverpool::PrimitiveType::LineList:
|
||||||
|
return vk::PrimitiveTopology::eLineList;
|
||||||
|
case Liverpool::PrimitiveType::LineStrip:
|
||||||
|
return vk::PrimitiveTopology::eLineStrip;
|
||||||
|
case Liverpool::PrimitiveType::TriangleList:
|
||||||
|
return vk::PrimitiveTopology::eTriangleList;
|
||||||
|
case Liverpool::PrimitiveType::TriangleFan:
|
||||||
|
return vk::PrimitiveTopology::eTriangleFan;
|
||||||
|
case Liverpool::PrimitiveType::TriangleStrip:
|
||||||
|
return vk::PrimitiveTopology::eTriangleStrip;
|
||||||
|
case Liverpool::PrimitiveType::AdjLineList:
|
||||||
|
return vk::PrimitiveTopology::eLineListWithAdjacency;
|
||||||
|
case Liverpool::PrimitiveType::AdjLineStrip:
|
||||||
|
return vk::PrimitiveTopology::eLineStripWithAdjacency;
|
||||||
|
case Liverpool::PrimitiveType::AdjTriangleList:
|
||||||
|
return vk::PrimitiveTopology::eTriangleListWithAdjacency;
|
||||||
|
case Liverpool::PrimitiveType::AdjTriangleStrip:
|
||||||
|
return vk::PrimitiveTopology::eTriangleStripWithAdjacency;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return vk::PrimitiveTopology::eTriangleList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::PolygonMode PolygonMode(Liverpool::PolygonMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case Liverpool::PolygonMode::Point:
|
||||||
|
return vk::PolygonMode::ePoint;
|
||||||
|
case Liverpool::PolygonMode::Line:
|
||||||
|
return vk::PolygonMode::eLine;
|
||||||
|
case Liverpool::PolygonMode::Fill:
|
||||||
|
return vk::PolygonMode::eFill;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return vk::PolygonMode::eFill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::CullModeFlags CullMode(Liverpool::CullMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case Liverpool::CullMode::None:
|
||||||
|
return vk::CullModeFlagBits::eNone;
|
||||||
|
case Liverpool::CullMode::Front:
|
||||||
|
return vk::CullModeFlagBits::eFront;
|
||||||
|
case Liverpool::CullMode::Back:
|
||||||
|
return vk::CullModeFlagBits::eBack;
|
||||||
|
case Liverpool::CullMode::FrontAndBack:
|
||||||
|
return vk::CullModeFlagBits::eFrontAndBack;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return vk::CullModeFlagBits::eNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan::LiverpoolToVK
|
|
@ -0,0 +1,23 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "video_core/amdgpu/liverpool.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_common.h"
|
||||||
|
|
||||||
|
namespace Vulkan::LiverpoolToVK {
|
||||||
|
|
||||||
|
using Liverpool = AmdGpu::Liverpool;
|
||||||
|
|
||||||
|
vk::StencilOp StencilOp(Liverpool::StencilFunc op);
|
||||||
|
|
||||||
|
vk::CompareOp CompareOp(Liverpool::CompareFunc func);
|
||||||
|
|
||||||
|
vk::PrimitiveTopology PrimitiveType(Liverpool::PrimitiveType type);
|
||||||
|
|
||||||
|
vk::PolygonMode PolygonMode(Liverpool::PolygonMode mode);
|
||||||
|
|
||||||
|
vk::CullModeFlags CullMode(Liverpool::CullMode mode);
|
||||||
|
|
||||||
|
} // namespace Vulkan::LiverpoolToVK
|
|
@ -7,6 +7,7 @@
|
||||||
#include "core/libraries/system/systemservice.h"
|
#include "core/libraries/system/systemservice.h"
|
||||||
#include "sdl_window.h"
|
#include "sdl_window.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
|
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
|
@ -60,9 +61,10 @@ bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format for
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_)
|
RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool)
|
||||||
: window{window_}, instance{window, Config::getGpuId()}, scheduler{instance},
|
: window{window_}, instance{window, Config::getGpuId()}, scheduler{instance},
|
||||||
swapchain{instance, window}, texture_cache{instance, scheduler} {
|
swapchain{instance, window}, texture_cache{instance, scheduler} {
|
||||||
|
rasterizer = std::make_unique<Rasterizer>(instance, scheduler, texture_cache, liverpool);
|
||||||
const u32 num_images = swapchain.GetImageCount();
|
const u32 num_images = swapchain.GetImageCount();
|
||||||
const vk::Device device = instance.GetDevice();
|
const vk::Device device = instance.GetDevice();
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ namespace Frontend {
|
||||||
class WindowSDL;
|
class WindowSDL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace AmdGpu {
|
||||||
|
struct Liverpool;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
struct Frame {
|
struct Frame {
|
||||||
|
@ -26,9 +30,11 @@ struct Frame {
|
||||||
vk::CommandBuffer cmdbuf;
|
vk::CommandBuffer cmdbuf;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Rasterizer;
|
||||||
|
|
||||||
class RendererVulkan {
|
class RendererVulkan {
|
||||||
public:
|
public:
|
||||||
explicit RendererVulkan(Frontend::WindowSDL& window);
|
explicit RendererVulkan(Frontend::WindowSDL& window, AmdGpu::Liverpool* liverpool);
|
||||||
~RendererVulkan();
|
~RendererVulkan();
|
||||||
|
|
||||||
Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute,
|
Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute,
|
||||||
|
@ -47,6 +53,7 @@ private:
|
||||||
Instance instance;
|
Instance instance;
|
||||||
Scheduler scheduler;
|
Scheduler scheduler;
|
||||||
Swapchain swapchain;
|
Swapchain swapchain;
|
||||||
|
std::unique_ptr<Rasterizer> rasterizer;
|
||||||
VideoCore::TextureCache texture_cache;
|
VideoCore::TextureCache texture_cache;
|
||||||
vk::UniqueCommandPool command_pool;
|
vk::UniqueCommandPool command_pool;
|
||||||
std::vector<Frame> present_frames;
|
std::vector<Frame> present_frames;
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <boost/container/static_vector.hpp>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
GraphicsPipeline::GraphicsPipeline(const Instance& instance_, const PipelineKey& key_,
|
||||||
|
vk::PipelineCache pipeline_cache_, vk::PipelineLayout layout_,
|
||||||
|
std::array<vk::ShaderModule, MaxShaderStages> modules)
|
||||||
|
: instance{instance_}, pipeline_layout{layout_}, pipeline_cache{pipeline_cache_}, key{key_} {
|
||||||
|
const vk::Device device = instance.GetDevice();
|
||||||
|
|
||||||
|
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
||||||
|
.vertexBindingDescriptionCount = 0U,
|
||||||
|
.pVertexBindingDescriptions = nullptr,
|
||||||
|
.vertexAttributeDescriptionCount = 0U,
|
||||||
|
.pVertexAttributeDescriptions = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
|
||||||
|
.topology = LiverpoolToVK::PrimitiveType(key.prim_type),
|
||||||
|
.primitiveRestartEnable = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineRasterizationStateCreateInfo raster_state = {
|
||||||
|
.depthClampEnable = false,
|
||||||
|
.rasterizerDiscardEnable = false,
|
||||||
|
.polygonMode = LiverpoolToVK::PolygonMode(key.polygon_mode),
|
||||||
|
.cullMode = LiverpoolToVK::CullMode(key.cull_mode),
|
||||||
|
.frontFace = vk::FrontFace::eClockwise,
|
||||||
|
.depthBiasEnable = false,
|
||||||
|
.lineWidth = 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineMultisampleStateCreateInfo multisampling = {
|
||||||
|
.rasterizationSamples = vk::SampleCountFlagBits::e1,
|
||||||
|
.sampleShadingEnable = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineColorBlendAttachmentState colorblend_attachment = {
|
||||||
|
.blendEnable = false,
|
||||||
|
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||||
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineColorBlendStateCreateInfo color_blending = {
|
||||||
|
.logicOpEnable = false,
|
||||||
|
.logicOp = vk::LogicOp::eCopy,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = &colorblend_attachment,
|
||||||
|
.blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f},
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::Viewport viewport = {
|
||||||
|
.x = 0.0f,
|
||||||
|
.y = 0.0f,
|
||||||
|
.width = 1.0f,
|
||||||
|
.height = 1.0f,
|
||||||
|
.minDepth = 0.0f,
|
||||||
|
.maxDepth = 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::Rect2D scissor = {
|
||||||
|
.offset = {0, 0},
|
||||||
|
.extent = {1, 1},
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineViewportStateCreateInfo viewport_info = {
|
||||||
|
.viewportCount = 1,
|
||||||
|
.pViewports = &viewport,
|
||||||
|
.scissorCount = 1,
|
||||||
|
.pScissors = &scissor,
|
||||||
|
};
|
||||||
|
|
||||||
|
boost::container::static_vector<vk::DynamicState, 14> dynamic_states = {
|
||||||
|
vk::DynamicState::eViewport,
|
||||||
|
vk::DynamicState::eScissor,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
|
||||||
|
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
|
||||||
|
.pDynamicStates = dynamic_states.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::PipelineDepthStencilStateCreateInfo depth_info = {
|
||||||
|
.depthTestEnable = key.depth.depth_enable,
|
||||||
|
.depthWriteEnable = key.depth.depth_write_enable,
|
||||||
|
.depthCompareOp = LiverpoolToVK::CompareOp(key.depth.depth_func),
|
||||||
|
.depthBoundsTestEnable = key.depth.depth_bounds_enable,
|
||||||
|
.stencilTestEnable = key.depth.stencil_enable,
|
||||||
|
.front{
|
||||||
|
.failOp = LiverpoolToVK::StencilOp(key.stencil.stencil_fail_front),
|
||||||
|
.passOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zpass_front),
|
||||||
|
.depthFailOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zfail_front),
|
||||||
|
.compareOp = LiverpoolToVK::CompareOp(key.depth.stencil_ref_func),
|
||||||
|
.compareMask = key.stencil_ref_front.stencil_mask,
|
||||||
|
.writeMask = key.stencil_ref_front.stencil_write_mask,
|
||||||
|
.reference = key.stencil_ref_front.stencil_test_val,
|
||||||
|
},
|
||||||
|
.back{
|
||||||
|
.failOp = LiverpoolToVK::StencilOp(key.stencil.stencil_fail_back),
|
||||||
|
.passOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zpass_back),
|
||||||
|
.depthFailOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zfail_back),
|
||||||
|
.compareOp = LiverpoolToVK::CompareOp(key.depth.stencil_bf_func),
|
||||||
|
.compareMask = key.stencil_ref_back.stencil_mask,
|
||||||
|
.writeMask = key.stencil_ref_back.stencil_write_mask,
|
||||||
|
.reference = key.stencil_ref_back.stencil_test_val,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
u32 shader_count = 2;
|
||||||
|
std::array<vk::PipelineShaderStageCreateInfo, MaxShaderStages> shader_stages;
|
||||||
|
shader_stages[0] = vk::PipelineShaderStageCreateInfo{
|
||||||
|
.stage = vk::ShaderStageFlagBits::eVertex,
|
||||||
|
.module = modules[0],
|
||||||
|
.pName = "main",
|
||||||
|
};
|
||||||
|
shader_stages[1] = vk::PipelineShaderStageCreateInfo{
|
||||||
|
.stage = vk::ShaderStageFlagBits::eFragment,
|
||||||
|
.module = modules[4],
|
||||||
|
.pName = "main",
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::Format color_format = vk::Format::eB8G8R8A8Srgb;
|
||||||
|
const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci = {
|
||||||
|
.colorAttachmentCount = 1,
|
||||||
|
.pColorAttachmentFormats = &color_format,
|
||||||
|
.depthAttachmentFormat = vk::Format::eUndefined,
|
||||||
|
.stencilAttachmentFormat = vk::Format::eUndefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vk::GraphicsPipelineCreateInfo pipeline_info = {
|
||||||
|
.pNext = &pipeline_rendering_ci,
|
||||||
|
.stageCount = shader_count,
|
||||||
|
.pStages = shader_stages.data(),
|
||||||
|
.pVertexInputState = &vertex_input_info,
|
||||||
|
.pInputAssemblyState = &input_assembly,
|
||||||
|
.pViewportState = &viewport_info,
|
||||||
|
.pRasterizationState = &raster_state,
|
||||||
|
.pMultisampleState = &multisampling,
|
||||||
|
.pDepthStencilState = &depth_info,
|
||||||
|
.pColorBlendState = &color_blending,
|
||||||
|
.pDynamicState = &dynamic_info,
|
||||||
|
.layout = pipeline_layout,
|
||||||
|
};
|
||||||
|
|
||||||
|
auto result = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info);
|
||||||
|
if (result.result == vk::Result::eSuccess) {
|
||||||
|
pipeline = std::move(result.value);
|
||||||
|
} else {
|
||||||
|
UNREACHABLE_MSG("Graphics pipeline creation failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::~GraphicsPipeline() = default;
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -0,0 +1,46 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_common.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
static constexpr u32 MaxShaderStages = 5;
|
||||||
|
|
||||||
|
class Instance;
|
||||||
|
|
||||||
|
using Liverpool = AmdGpu::Liverpool;
|
||||||
|
|
||||||
|
struct PipelineKey {
|
||||||
|
Liverpool::DepthControl depth;
|
||||||
|
Liverpool::StencilControl stencil;
|
||||||
|
Liverpool::StencilRefMask stencil_ref_front;
|
||||||
|
Liverpool::StencilRefMask stencil_ref_back;
|
||||||
|
Liverpool::PrimitiveType prim_type;
|
||||||
|
Liverpool::PolygonMode polygon_mode;
|
||||||
|
Liverpool::CullMode cull_mode;
|
||||||
|
};
|
||||||
|
static_assert(std::has_unique_object_representations_v<PipelineKey>);
|
||||||
|
|
||||||
|
class GraphicsPipeline {
|
||||||
|
public:
|
||||||
|
explicit GraphicsPipeline(const Instance& instance, const PipelineKey& key,
|
||||||
|
vk::PipelineCache pipeline_cache, vk::PipelineLayout layout,
|
||||||
|
std::array<vk::ShaderModule, MaxShaderStages> modules);
|
||||||
|
~GraphicsPipeline();
|
||||||
|
|
||||||
|
[[nodiscard]] vk::Pipeline Handle() const noexcept {
|
||||||
|
return *pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Instance& instance;
|
||||||
|
vk::UniquePipeline pipeline;
|
||||||
|
vk::PipelineLayout pipeline_layout;
|
||||||
|
vk::PipelineCache pipeline_cache;
|
||||||
|
PipelineKey key;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -196,9 +196,15 @@ bool Instance::CreateDevice() {
|
||||||
.shaderClipDistance = features.shaderClipDistance,
|
.shaderClipDistance = features.shaderClipDistance,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
vk::PhysicalDeviceVulkan11Features{
|
||||||
|
.shaderDrawParameters = true,
|
||||||
|
},
|
||||||
vk::PhysicalDeviceVulkan12Features{
|
vk::PhysicalDeviceVulkan12Features{
|
||||||
.timelineSemaphore = true,
|
.timelineSemaphore = true,
|
||||||
},
|
},
|
||||||
|
vk::PhysicalDeviceVulkan13Features{
|
||||||
|
.dynamicRendering = true,
|
||||||
|
},
|
||||||
vk::PhysicalDeviceCustomBorderColorFeaturesEXT{
|
vk::PhysicalDeviceCustomBorderColorFeaturesEXT{
|
||||||
.customBorderColors = true,
|
.customBorderColors = true,
|
||||||
.customBorderColorWithoutFormat = true,
|
.customBorderColorWithoutFormat = true,
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "shader_recompiler/recompiler.h"
|
||||||
|
#include "shader_recompiler/runtime_info.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||||
|
AmdGpu::Liverpool* liverpool_)
|
||||||
|
: instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, inst_pool{4096},
|
||||||
|
block_pool{512} {
|
||||||
|
const vk::PipelineLayoutCreateInfo layout_info = {
|
||||||
|
.setLayoutCount = 0U,
|
||||||
|
.pSetLayouts = nullptr,
|
||||||
|
.pushConstantRangeCount = 0,
|
||||||
|
.pPushConstantRanges = nullptr,
|
||||||
|
};
|
||||||
|
pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info);
|
||||||
|
pipeline_cache = instance.GetDevice().createPipelineCacheUnique({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineCache::BindPipeline() {
|
||||||
|
SCOPE_EXIT {
|
||||||
|
const auto cmdbuf = scheduler.CommandBuffer();
|
||||||
|
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline->Handle());
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pipeline) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto get_program = [&](const AmdGpu::Liverpool::ShaderProgram& pgm, Shader::Stage stage) {
|
||||||
|
const u32* token = pgm.Address<u32>();
|
||||||
|
|
||||||
|
// Retrieve shader header.
|
||||||
|
Shader::BinaryInfo bininfo;
|
||||||
|
std::memcpy(&bininfo, token + (token[1] + 1) * 2, sizeof(bininfo));
|
||||||
|
|
||||||
|
// Lookup if the shader already exists.
|
||||||
|
const auto it = module_map.find(bininfo.shader_hash);
|
||||||
|
if (it != module_map.end()) {
|
||||||
|
return *it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile and cache shader.
|
||||||
|
const auto data = std::span{token, bininfo.length / sizeof(u32)};
|
||||||
|
const auto program = Shader::TranslateProgram(inst_pool, block_pool, stage, data);
|
||||||
|
return CompileSPV(program, instance.GetDevice());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Retrieve shader stage modules.
|
||||||
|
// TODO: Only do this when program address is changed.
|
||||||
|
stages[0] = get_program(liverpool->regs.vs_program, Shader::Stage::Vertex);
|
||||||
|
stages[4] = get_program(liverpool->regs.ps_program, Shader::Stage::Fragment);
|
||||||
|
|
||||||
|
// Bind pipeline.
|
||||||
|
// TODO: Read entire key based on reg state.
|
||||||
|
graphics_key.prim_type = liverpool->regs.primitive_type;
|
||||||
|
graphics_key.polygon_mode = liverpool->regs.polygon_control.PolyMode();
|
||||||
|
pipeline = std::make_unique<GraphicsPipeline>(instance, graphics_key, *pipeline_cache,
|
||||||
|
*pipeline_layout, stages);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -0,0 +1,40 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tsl/robin_map.h>
|
||||||
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
#include "shader_recompiler/object_pool.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Instance;
|
||||||
|
class Scheduler;
|
||||||
|
|
||||||
|
class PipelineCache {
|
||||||
|
static constexpr size_t MaxShaderStages = 5;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PipelineCache(const Instance& instance, Scheduler& scheduler,
|
||||||
|
AmdGpu::Liverpool* liverpool);
|
||||||
|
~PipelineCache() = default;
|
||||||
|
|
||||||
|
void BindPipeline();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Instance& instance;
|
||||||
|
Scheduler& scheduler;
|
||||||
|
AmdGpu::Liverpool* liverpool;
|
||||||
|
vk::UniquePipelineCache pipeline_cache;
|
||||||
|
vk::UniquePipelineLayout pipeline_layout;
|
||||||
|
tsl::robin_map<size_t, vk::UniqueShaderModule> module_map;
|
||||||
|
std::array<vk::ShaderModule, MaxShaderStages> stages{};
|
||||||
|
std::unique_ptr<GraphicsPipeline> pipeline;
|
||||||
|
PipelineKey graphics_key{};
|
||||||
|
Shader::ObjectPool<Shader::IR::Inst> inst_pool;
|
||||||
|
Shader::ObjectPool<Shader::IR::Block> block_pool;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -0,0 +1,99 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "video_core/amdgpu/liverpool.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
#include "video_core/texture_cache/image_view.h"
|
||||||
|
#include "video_core/texture_cache/texture_cache.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
static constexpr vk::BufferUsageFlags VertexIndexFlags = vk::BufferUsageFlagBits::eVertexBuffer |
|
||||||
|
vk::BufferUsageFlagBits::eIndexBuffer |
|
||||||
|
vk::BufferUsageFlagBits::eTransferDst;
|
||||||
|
|
||||||
|
Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_,
|
||||||
|
VideoCore::TextureCache& texture_cache_, AmdGpu::Liverpool* liverpool_)
|
||||||
|
: instance{instance_}, scheduler{scheduler_}, texture_cache{texture_cache_},
|
||||||
|
liverpool{liverpool_}, pipeline_cache{instance, scheduler, liverpool},
|
||||||
|
vertex_index_buffer{instance, scheduler, VertexIndexFlags, 64_MB} {
|
||||||
|
liverpool->BindRasterizer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rasterizer::~Rasterizer() = default;
|
||||||
|
|
||||||
|
void Rasterizer::DrawIndex() {
|
||||||
|
const auto cmdbuf = scheduler.CommandBuffer();
|
||||||
|
auto& regs = liverpool->regs;
|
||||||
|
|
||||||
|
static bool first_time = true;
|
||||||
|
if (first_time) {
|
||||||
|
first_time = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateDynamicState();
|
||||||
|
|
||||||
|
pipeline_cache.BindPipeline();
|
||||||
|
|
||||||
|
const u32 pitch = regs.color_buffers[0].Pitch();
|
||||||
|
const u32 height = regs.color_buffers[0].Height();
|
||||||
|
const u32 tile_max = regs.color_buffers[0].slice.tile_max;
|
||||||
|
auto& image_view = texture_cache.RenderTarget(regs.color_buffers[0].Address(), pitch);
|
||||||
|
|
||||||
|
const vk::RenderingAttachmentInfo color_info = {
|
||||||
|
.imageView = *image_view.image_view,
|
||||||
|
.imageLayout = vk::ImageLayout::eGeneral,
|
||||||
|
.loadOp = vk::AttachmentLoadOp::eLoad,
|
||||||
|
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Don't restart renderpass every draw
|
||||||
|
const vk::RenderingInfo rendering_info = {
|
||||||
|
.renderArea = {.offset = {0, 0}, .extent = {1920, 1080}},
|
||||||
|
.layerCount = 1,
|
||||||
|
.colorAttachmentCount = 1,
|
||||||
|
.pColorAttachments = &color_info,
|
||||||
|
};
|
||||||
|
|
||||||
|
cmdbuf.beginRendering(rendering_info);
|
||||||
|
cmdbuf.bindIndexBuffer(vertex_index_buffer.Handle(), 0, vk::IndexType::eUint32);
|
||||||
|
cmdbuf.bindVertexBuffers(0, vertex_index_buffer.Handle(), vk::DeviceSize(0));
|
||||||
|
cmdbuf.draw(regs.num_indices, regs.num_instances.NumInstances(), 0, 0);
|
||||||
|
cmdbuf.endRendering();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::UpdateDynamicState() {
|
||||||
|
UpdateViewportScissorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::UpdateViewportScissorState() {
|
||||||
|
auto& regs = liverpool->regs;
|
||||||
|
|
||||||
|
const auto cmdbuf = scheduler.CommandBuffer();
|
||||||
|
const vk::Viewport viewport{
|
||||||
|
.x = regs.viewports[0].xoffset - regs.viewports[0].xscale,
|
||||||
|
.y = regs.viewports[0].yoffset - regs.viewports[0].yscale,
|
||||||
|
.width = regs.viewports[0].xscale * 2.0f,
|
||||||
|
.height = regs.viewports[0].yscale * 2.0f,
|
||||||
|
.minDepth = regs.viewports[0].zoffset - regs.viewports[0].zscale,
|
||||||
|
.maxDepth = regs.viewports[0].zscale + regs.viewports[0].zoffset,
|
||||||
|
};
|
||||||
|
const vk::Rect2D scissor{
|
||||||
|
.offset = {regs.screen_scissor.top_left_x, regs.screen_scissor.top_left_y},
|
||||||
|
.extent = {regs.screen_scissor.GetWidth(), regs.screen_scissor.GetHeight()},
|
||||||
|
};
|
||||||
|
cmdbuf.setViewport(0, viewport);
|
||||||
|
cmdbuf.setScissor(0, scissor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::UpdateDepthStencilState() {
|
||||||
|
auto& depth = liverpool->regs.depth_control;
|
||||||
|
|
||||||
|
const auto cmdbuf = scheduler.CommandBuffer();
|
||||||
|
cmdbuf.setDepthBoundsTestEnable(depth.depth_bounds_enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -0,0 +1,51 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
||||||
|
|
||||||
|
namespace AmdGpu {
|
||||||
|
struct Liverpool;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
class TextureCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Scheduler;
|
||||||
|
class GraphicsPipeline;
|
||||||
|
|
||||||
|
class Rasterizer {
|
||||||
|
public:
|
||||||
|
explicit Rasterizer(const Instance& instance, Scheduler& scheduler,
|
||||||
|
VideoCore::TextureCache& texture_cache, AmdGpu::Liverpool* liverpool);
|
||||||
|
~Rasterizer();
|
||||||
|
|
||||||
|
/// Performs a draw call with an index buffer.
|
||||||
|
void DrawIndex();
|
||||||
|
|
||||||
|
/// Updates graphics state that is not part of the bound pipeline.
|
||||||
|
void UpdateDynamicState();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Updates viewport and scissor from liverpool registers.
|
||||||
|
void UpdateViewportScissorState();
|
||||||
|
|
||||||
|
/// Updates depth and stencil pipeline state from liverpool registers.
|
||||||
|
void UpdateDepthStencilState();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Instance& instance;
|
||||||
|
Scheduler& scheduler;
|
||||||
|
VideoCore::TextureCache& texture_cache;
|
||||||
|
AmdGpu::Liverpool* liverpool;
|
||||||
|
PipelineCache pipeline_cache;
|
||||||
|
StreamBuffer vertex_index_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -38,7 +38,7 @@ using Libraries::VideoOut::TilingMode;
|
||||||
if (false /*&& IsDepthStencilFormat(format)*/) {
|
if (false /*&& IsDepthStencilFormat(format)*/) {
|
||||||
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||||
} else {
|
} else {
|
||||||
// usage |= vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage;
|
usage |= vk::ImageUsageFlagBits::eColorAttachment;
|
||||||
}
|
}
|
||||||
return usage;
|
return usage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "core/libraries/videoout/buffer.h"
|
#include "core/libraries/videoout/buffer.h"
|
||||||
#include "video_core/renderer_vulkan/vk_common.h"
|
#include "video_core/renderer_vulkan/vk_common.h"
|
||||||
|
#include "video_core/texture_cache/image_view.h"
|
||||||
#include "video_core/texture_cache/types.h"
|
#include "video_core/texture_cache/types.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
@ -41,23 +42,6 @@ struct ImageInfo {
|
||||||
u32 guest_size_bytes = 0;
|
u32 guest_size_bytes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Handle {
|
|
||||||
VmaAllocation allocation;
|
|
||||||
VkImage image;
|
|
||||||
|
|
||||||
Handle() = default;
|
|
||||||
|
|
||||||
Handle(Handle&& other)
|
|
||||||
: image{std::exchange(other.image, VK_NULL_HANDLE)},
|
|
||||||
allocation{std::exchange(other.allocation, VK_NULL_HANDLE)} {}
|
|
||||||
|
|
||||||
Handle& operator=(Handle&& other) {
|
|
||||||
image = std::exchange(other.image, VK_NULL_HANDLE);
|
|
||||||
allocation = std::exchange(other.allocation, VK_NULL_HANDLE);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UniqueImage {
|
struct UniqueImage {
|
||||||
explicit UniqueImage(vk::Device device, VmaAllocator allocator);
|
explicit UniqueImage(vk::Device device, VmaAllocator allocator);
|
||||||
~UniqueImage();
|
~UniqueImage();
|
||||||
|
@ -100,6 +84,14 @@ struct Image {
|
||||||
return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;
|
return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageViewId FindView(const ImageViewInfo& info) const {
|
||||||
|
const auto it = std::ranges::find(image_view_infos, info);
|
||||||
|
if (it == image_view_infos.end()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return image_view_ids[std::distance(it, image_view_infos.begin())];
|
||||||
|
}
|
||||||
|
|
||||||
void Transit(vk::ImageLayout dst_layout, vk::Flags<vk::AccessFlagBits> dst_mask);
|
void Transit(vk::ImageLayout dst_layout, vk::Flags<vk::AccessFlagBits> dst_mask);
|
||||||
|
|
||||||
const Vulkan::Instance* instance;
|
const Vulkan::Instance* instance;
|
||||||
|
@ -110,6 +102,8 @@ struct Image {
|
||||||
ImageFlagBits flags = ImageFlagBits::CpuModified;
|
ImageFlagBits flags = ImageFlagBits::CpuModified;
|
||||||
VAddr cpu_addr = 0;
|
VAddr cpu_addr = 0;
|
||||||
VAddr cpu_addr_end = 0;
|
VAddr cpu_addr_end = 0;
|
||||||
|
std::vector<ImageViewInfo> image_view_infos;
|
||||||
|
std::vector<ImageViewId> image_view_ids;
|
||||||
|
|
||||||
// Resource state tracking
|
// Resource state tracking
|
||||||
vk::Flags<vk::PipelineStageFlagBits> pl_stage = vk::PipelineStageFlagBits::eAllCommands;
|
vk::Flags<vk::PipelineStageFlagBits> pl_stage = vk::PipelineStageFlagBits::eAllCommands;
|
||||||
|
|
|
@ -13,20 +13,9 @@ class Scheduler;
|
||||||
|
|
||||||
namespace VideoCore {
|
namespace VideoCore {
|
||||||
|
|
||||||
enum class ImageViewType : u32 {
|
|
||||||
e1D,
|
|
||||||
e2D,
|
|
||||||
Cube,
|
|
||||||
e3D,
|
|
||||||
e1DArray,
|
|
||||||
e2DArray,
|
|
||||||
CubeArray,
|
|
||||||
Buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ImageViewInfo {
|
struct ImageViewInfo {
|
||||||
vk::ImageViewType type{};
|
vk::ImageViewType type = vk::ImageViewType::e2D;
|
||||||
vk::Format format{};
|
vk::Format format = vk::Format::eR8G8B8A8Unorm;
|
||||||
SubresourceRange range;
|
SubresourceRange range;
|
||||||
vk::ComponentMapping mapping{};
|
vk::ComponentMapping mapping{};
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,6 @@ class SlotVector {
|
||||||
constexpr static std::size_t InitialCapacity = 1024;
|
constexpr static std::size_t InitialCapacity = 1024;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SlotVector() {
|
|
||||||
Reserve(InitialCapacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
~SlotVector() noexcept {
|
~SlotVector() noexcept {
|
||||||
std::size_t index = 0;
|
std::size_t index = 0;
|
||||||
for (u64 bits : stored_bitset) {
|
for (u64 bits : stored_bitset) {
|
||||||
|
|
|
@ -83,6 +83,15 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler&
|
||||||
ASSERT_MSG(veh_handle, "Failed to register an exception handler");
|
ASSERT_MSG(veh_handle, "Failed to register an exception handler");
|
||||||
#endif
|
#endif
|
||||||
g_texture_cache = this;
|
g_texture_cache = this;
|
||||||
|
|
||||||
|
ImageInfo info;
|
||||||
|
info.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
||||||
|
info.type = vk::ImageType::e2D;
|
||||||
|
const ImageId null_id = slot_images.insert(instance, scheduler, info, 0);
|
||||||
|
ASSERT(null_id.index == 0);
|
||||||
|
|
||||||
|
ImageViewInfo view_info;
|
||||||
|
void(slot_image_views.insert(instance, scheduler, view_info, slot_images[null_id].image));
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureCache::~TextureCache() {
|
TextureCache::~TextureCache() {
|
||||||
|
@ -128,6 +137,29 @@ Image& TextureCache::FindImage(const ImageInfo& info, VAddr cpu_address) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageView& TextureCache::RenderTarget(VAddr cpu_address, u32 pitch) {
|
||||||
|
boost::container::small_vector<ImageId, 2> image_ids;
|
||||||
|
ForEachImageInRegion(cpu_address, pitch * 4, [&](ImageId image_id, Image& image) {
|
||||||
|
if (image.cpu_addr == cpu_address) {
|
||||||
|
image_ids.push_back(image_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_MSG(image_ids.size() <= 1, "Overlapping framebuffers not allowed!");
|
||||||
|
auto* image = &slot_images[image_ids.empty() ? ImageId{0} : image_ids.back()];
|
||||||
|
|
||||||
|
ImageViewInfo info;
|
||||||
|
info.format = vk::Format::eB8G8R8A8Srgb;
|
||||||
|
if (const ImageViewId view_id = image->FindView(info); view_id) {
|
||||||
|
return slot_image_views[view_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageViewId view_id = slot_image_views.insert(instance, scheduler, info, image->image);
|
||||||
|
image->image_view_infos.emplace_back(info);
|
||||||
|
image->image_view_ids.emplace_back(view_id);
|
||||||
|
return slot_image_views[view_id];
|
||||||
|
}
|
||||||
|
|
||||||
void TextureCache::RefreshImage(Image& image) {
|
void TextureCache::RefreshImage(Image& image) {
|
||||||
// Mark image as validated.
|
// Mark image as validated.
|
||||||
image.flags &= ~ImageFlagBits::CpuModified;
|
image.flags &= ~ImageFlagBits::CpuModified;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
||||||
#include "video_core/texture_cache/image.h"
|
#include "video_core/texture_cache/image.h"
|
||||||
|
#include "video_core/texture_cache/image_view.h"
|
||||||
#include "video_core/texture_cache/slot_vector.h"
|
#include "video_core/texture_cache/slot_vector.h"
|
||||||
|
|
||||||
namespace Core::Libraries::VideoOut {
|
namespace Core::Libraries::VideoOut {
|
||||||
|
@ -35,6 +36,9 @@ public:
|
||||||
/// Retrieves the image handle of the image with the provided attributes and address.
|
/// Retrieves the image handle of the image with the provided attributes and address.
|
||||||
Image& FindImage(const ImageInfo& info, VAddr cpu_address);
|
Image& FindImage(const ImageInfo& info, VAddr cpu_address);
|
||||||
|
|
||||||
|
/// Retrieves the render target with specified properties
|
||||||
|
ImageView& RenderTarget(VAddr cpu_address, u32 pitch);
|
||||||
|
|
||||||
/// Reuploads image contents.
|
/// Reuploads image contents.
|
||||||
void RefreshImage(Image& image);
|
void RefreshImage(Image& image);
|
||||||
|
|
||||||
|
@ -116,6 +120,7 @@ private:
|
||||||
Vulkan::Scheduler& scheduler;
|
Vulkan::Scheduler& scheduler;
|
||||||
Vulkan::StreamBuffer staging;
|
Vulkan::StreamBuffer staging;
|
||||||
SlotVector<Image> slot_images;
|
SlotVector<Image> slot_images;
|
||||||
|
SlotVector<ImageView> slot_image_views;
|
||||||
tsl::robin_pg_map<u64, std::vector<ImageId>> page_table;
|
tsl::robin_pg_map<u64, std::vector<ImageId>> page_table;
|
||||||
boost::icl::interval_map<VAddr, s32> cached_pages;
|
boost::icl::interval_map<VAddr, s32> cached_pages;
|
||||||
#ifdef _WIN64
|
#ifdef _WIN64
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue