Compare commits

...
Author SHA1 Message Date
nmzik 3da05eff71 kernel: share directory streams across seeks and reads
Fix PS5 boot failure when an optional empty sound path opens /app0/ and the game seeks and reads that directory. Pack directory records once and share their bytes and position across raw reads, enumeration, seeking, and size reporting. Apply seek and read error semantics with checked offsets.

Add multiblock directory regression coverage using the existing production Vulkan resource lifecycle. Existing CMake build and kernel_file_system test pass. Independent correctness, fixture, runtime, and final compactness audits passed; launch.json run reached Jurassic Park gameplay with 4560 sampled flips, no Vulkan validation errors, and normal window-close exit.
2026-09-10 14:36:15 +04:00
nmzik 489d483362 graphics: simplify texture upload and component mapping paths
Use an explicit selector switch and descriptive channel, mip, and slice names. Share volume/array tile setup, hoist per-mip copy work, consolidate tiled validation, and remove redundant code.

Preserve layout bounds, mip tails, source strides, and cache interfaces. Independent correctness and compactness audits passed; native packed-texture tests and 330 GPU tiler parity cases passed.
2026-09-10 14:36:15 +04:00
nmzik a305a6c1c6 graphics: share packed texture and render-target channel layouts
Resolve PS5 5551 textures with alpha in bit 15 and compose host channel order with guest selectors. Share the existing render-target mapping with sampled textures so packed layouts cannot diverge.

Add native GPU readback coverage for 5551 and 4444, arbitrary selectors, constants, and existing 1555 render-target mapping. Verified PPSA24515 through launch.json with Vulkan validation, advancing scenes and over 12,700 guest flips. Independent correctness and cleanliness audits passed.
2026-09-10 14:36:15 +04:00
nmzik 635fb8882b systemservice: use configured language for PS5 parameter 400 2026-09-10 14:11:46 +04:00
nmzik fe6f49632c font: accept supported OpenType CFF faces 2026-09-10 14:11:46 +04:00
nmzikandBrandon Strong c3546577b6 shader: implement S_WQM_B32
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-10 14:11:46 +04:00
nmzik c913951f7b shader: implement RDNA2 subvector loop mask and branch semantics 2026-09-10 14:11:46 +04:00
nmzik 3902b42096 shader: share direct and conditional branch classification 2026-09-10 14:11:46 +04:00
nmzik ae573f941a graphics: share vertex format mapping and support packed float colors 2026-09-10 14:11:46 +04:00
nmzik 20ea54cf0c Json2: new ABI 2026-09-10 14:11:46 +04:00
nmzik 0b4e78c0b2 graphics: execute DB_RENDER_OVERRIDE depth and stencil copies 2026-09-09 15:26:52 +04:00
nmzik 57844a07e2 buffer cache: grow stream reserves toward incoming requests 2026-09-09 15:26:52 +04:00
nmzik ba18551d6e ces: implement CP932 string conversion 2026-09-09 15:26:52 +04:00
nmzikandAli Almohaya c7b0d77970 loader: port guest fault diagnostics and remove host stack traces
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-09-09 13:55:53 +04:00
nmzik 1af19eabf0 shader: fix incorrect per-lane VCCZ/EXECZ reads 2026-09-09 13:22:09 +04:00
nmzik 32ea0869e5 shader: implement S_ORN2_SAVEEXEC_B32 2026-09-09 13:11:48 +04:00
nmzik e04007a7c6 shader: implement DS_INC_RTN_U32 and DS_DEC_RTN_U32 2026-09-09 12:56:20 +04:00
frangametvandnmzik 1a3a15e4cf Fix mount root opens and empty APR prefixes (#439)
* Fix opening mount root directories

* Allow empty APR path prefixes

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-09 12:31:23 +04:00
nmzik 679adbfa5e graphics: fix linear render-target pitch alignment 2026-09-09 02:45:59 +02:00
nmzikandEttore Sartori d285efe3b5 graphics: allow absent programmable restart indices
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
2026-09-08 21:26:59 +04:00
nmzik a64c8d3205 kernel: implement umtx wait/wake and reject malformed timeouts 2026-09-08 21:26:59 +04:00
nmzik 82c52c3fab window: round FPS in the title 2026-09-08 21:26:59 +04:00
nmzik dd1e90fcd7 video: keep decoding after Flush 2026-09-08 21:26:59 +04:00
nmzik ef0155636a shader: support single and split NGG shaders for PPSA11142 2026-09-08 21:26:59 +04:00
nmzik 9d550fbffa graphics: fix last provoking vertex on PS5 2026-09-08 21:26:59 +04:00
nmzik fcef691e0a shader: drop EmitTrueBool 2026-09-08 21:26:59 +04:00
nmzik 839c172612 shader: reuse operands and remove duplicate helpers 2026-09-08 21:26:59 +04:00
nmzik 9f49ac8249 shader: reuse SPIR-V helpers and memory resources 2026-09-08 21:26:59 +04:00
nmzik fde550ef66 graphics: simplify texture cleanup and swizzle decoding 2026-09-08 21:26:59 +04:00
nmzik 2b3993a595 shader: share emitter helpers 2026-09-08 21:26:59 +04:00
nmzik de3e86791c shader: reuse CFG and validation lookups 2026-09-08 21:26:59 +04:00
nmzik 6f66f77b09 shader: drop unused decoder fields 2026-09-08 21:26:59 +04:00
nmzik d853aba955 graphics: skip temporary cache lookup vectors 2026-09-08 21:26:59 +04:00
nmzik cd62693792 shader: share control flow labels 2026-09-08 21:26:59 +04:00
nmzik cb4c8f3878 shader: decode buffer formats once 2026-09-08 21:26:59 +04:00
nmzik 4d371d5f5f shader: reuse initial SSA types 2026-09-08 21:26:59 +04:00
nmzik 72963b01e1 shader: remove redundant decoder and translator state 2026-09-08 21:26:59 +04:00
nmzik 80a18e82b7 graphics: simplify image allocation 2026-09-08 21:26:58 +04:00
nmzik 574e687734 graphics: drop unused completion overload 2026-09-08 21:26:58 +04:00
nmzik c725ec50ff graphics: clean up draw setup 2026-09-08 21:26:58 +04:00
Brandon Strong c6f6e306e0 shader: raise the dense image resource limit to 64 (#457) 2026-09-08 19:37:41 +04:00
nmzik b847135321 launcher: guard optional Qt Vulkan support on macOS 2026-09-08 04:29:01 +04:00
91 changed files with 3524 additions and 2659 deletions

No files matched your search

+2
View File
@@ -567,6 +567,8 @@ if(BUILD_TESTING)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME host_image_allocation
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --host-image-allocation-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
+2 -18
View File
@@ -1,6 +1,5 @@
#include "common/assert.h"
#include "common/debug.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "kytyGitVersion.h"
@@ -12,25 +11,10 @@
namespace Common {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
KYTY_COMPILER == KYTY_COMPILER_CLANG
constexpr int PRINT_STACK_FROM = 4;
#else
constexpr int PRINT_STACK_FROM = 2;
#endif
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
int line) {
DebugStack stack;
DebugStack::Trace(&stack);
std::string report = "--- Build ---\n" KYTY_BUILD_LABEL "\n--- Stack Trace ---\n";
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
static_cast<uint64_t>(stack.GetAddr(i)));
}
report += fmt::format("{}\n{} in {}:{}\n", title, text, file, line);
return report;
return fmt::format("--- Build ---\n{}\n{}\n{} in {}:{}\n", KYTY_BUILD_LABEL, title, text, file,
line);
}
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
-13
View File
@@ -1,20 +1,7 @@
#include "common/debug.h"
#include "common/platform/sysDbg.h"
namespace Common {
void DebugStack::Print(int from, [[maybe_unused]] bool with_name) const {
for (int i = from; i < depth; i++) {
printf("[%d] %016" PRIx64 "\n", i - from, static_cast<uint64_t>(GetAddr(i)));
}
}
void DebugStack::Trace(DebugStack* stack) {
stack->depth = DEBUG_MAX_STACK_DEPTH;
SysStackWalk(stack->stack.data(), &stack->depth);
}
std::string Debug::GetCompiler() {
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
return "clang";
+1 -26
View File
@@ -2,42 +2,17 @@
#define KYTY_COMMON_DEBUG_H_
#include "common/common.h"
#include "common/stringUtils.h"
#include <array>
#include <cstring>
#include <string>
namespace Common {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
KYTY_COMPILER == KYTY_COMPILER_CLANG
constexpr int DEBUG_MAX_STACK_DEPTH = 20;
#else
constexpr int DEBUG_MAX_STACK_DEPTH = 15;
#endif
namespace Debug {
std::string GetCompiler();
std::string GetLinker();
std::string GetBitness();
} // namespace Debug
struct DebugStack {
int depth {0};
std::array<void*, DEBUG_MAX_STACK_DEPTH> stack {};
[[nodiscard]] uintptr_t GetAddr(int i) const { return reinterpret_cast<uintptr_t>(stack[i]); }
void Print(int from, bool with_name = true) const;
void CopyTo(DebugStack* s) const {
std::memcpy(s->stack.data(), stack.data(), sizeof(void*) * depth);
s->depth = depth;
}
static void Trace(DebugStack* stack);
};
} // namespace Common
#endif /* KYTY_COMMON_DEBUG_H_ */
+1 -3
View File
@@ -30,8 +30,6 @@ struct sys_dbg_stack_info_t {
#endif
};
void SysStackWalk(void** stack, int* depth);
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
void SysStackUsagePrint(sys_dbg_stack_info_t& stack); // NOLINT(google-runtime-references)
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
-51
View File
@@ -8,7 +8,6 @@
#include <cstdlib>
#include <cstring>
#include <execinfo.h>
#include <pthread.h>
#include <sys/param.h>
#include <sys/types.h>
@@ -17,56 +16,6 @@
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
#endif
// Avoid unwinding a guest-owned stack.
static bool OnOwnStack() {
const char* probe = reinterpret_cast<const char*>(&probe);
pthread_attr_t attr {};
#if defined(__APPLE__)
const auto* top = static_cast<const char*>(pthread_get_stackaddr_np(pthread_self()));
const auto size = pthread_get_stacksize_np(pthread_self());
(void)attr;
return top != nullptr && size != 0 && probe < top && probe >= top - size;
#else
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
return false;
}
void* base = nullptr;
size_t size = 0;
const bool ok = pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
pthread_attr_destroy(&attr);
if (!ok) {
return false;
}
const auto* low = static_cast<const char*>(base);
return probe >= low && probe < low + size;
#endif
}
void SysStackWalk(void** stack, int* depth) {
if (stack == nullptr || depth == nullptr || *depth <= 0) {
if (depth != nullptr) {
*depth = 0;
}
return;
}
if (!OnOwnStack()) {
*depth = 0;
return;
}
const int n = ::backtrace(stack, *depth);
*depth = (n < 0 ? 0 : n);
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
printf("stack: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.commited_addr),
static_cast<uint64_t>(stack.commited_size));
printf("code: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.code_addr),
static_cast<uint64_t>(stack.code_size));
}
void SysStackUsage(sys_dbg_stack_info_t& s) {
pid_t pid = getpid();
-12
View File
@@ -15,18 +15,6 @@
#include "common/assert.h"
#include "common/platform/sysDbg.h"
void SysStackWalk(void** stack, int* depth) {
*depth = static_cast<int>(CaptureStackBackTrace(0, static_cast<DWORD>(*depth), stack, nullptr));
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
printf("stack: (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64
", %" PRIu64 ")\n",
static_cast<uint64_t>(stack.reserved_addr), static_cast<uint64_t>(stack.reserved_size),
static_cast<uint64_t>(stack.guard_addr), static_cast<uint64_t>(stack.guard_size),
static_cast<uint64_t>(stack.commited_addr), static_cast<uint64_t>(stack.commited_size));
}
void SysStackUsage(sys_dbg_stack_info_t& s) {
MEMORY_BASIC_INFORMATION mbi {};
[[maybe_unused]] size_t ss = VirtualQuery(&mbi, &mbi, sizeof(mbi));
-1
View File
@@ -1,7 +1,6 @@
#include "common/threads.h"
#include "common/assert.h"
#include "common/debug.h"
#include <algorithm>
#include <atomic>
@@ -315,8 +315,17 @@ static void HwCtxSetDepthBoundsRegister(CommandProcessor& cp, uint32_t cmd_offse
}
}
static void HwCtxIgnoreDepthMetadataRegister([[maybe_unused]] uint32_t cmd_offset,
[[maybe_unused]] uint32_t value) {}
static void HwCtxSetDepthMetadataRegister(CommandProcessor& cp, uint32_t cmd_offset,
uint32_t value) {
if (cmd_offset == Pm4::DB_RENDER_OVERRIDE) {
HW::DepthRenderOverride control;
control.force_z_valid = (value & 0x20000000u) != 0;
control.force_z_dirty = (value & 0x08000000u) != 0;
control.force_stencil_valid = (value & 0x40000000u) != 0;
control.force_stencil_dirty = (value & 0x10000000u) != 0;
cp.GetCtx().SetDepthRenderOverride(control);
}
}
static void HwCtxIgnoreFovWindow([[maybe_unused]] uint32_t value) {}
@@ -389,7 +398,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthMetadataRegisters) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset + i, buffer[i]);
HwCtxSetDepthMetadataRegister(cp, cmd_offset + i, buffer[i]);
}
return num_values;
@@ -3003,28 +3012,28 @@ void GraphicsInitJmpTablesCxIndirect() {
HwCtxIgnoreCbDccControl(value);
};
g_hw_ctx_indirect_func[Pm4::DB_COUNT_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
for (auto cmd_offset = Pm4::DB_SRESULTS_COMPARE_STATE0;
cmd_offset <= Pm4::DB_SRESULTS_COMPARE_STATE1; cmd_offset++) {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
}
g_hw_ctx_indirect_func[Pm4::DB_RENDER_OVERRIDE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_RENDER_OVERRIDE2] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_DFSM_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_RMI_L2_CACHE_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::CB_RMI_GL2_CACHE_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::TA_BC_BASE_ADDR] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreBorderColorTableAddr(cmd_offset, value);
+23 -9
View File
@@ -202,6 +202,13 @@ struct RenderControl {
uint8_t copy_sample = 0;
};
struct DepthRenderOverride {
bool force_z_valid = false;
bool force_z_dirty = false;
bool force_stencil_valid = false;
bool force_stencil_dirty = false;
};
struct GdsOaCounter {
uint32_t counter = 0;
uint32_t address = 0;
@@ -809,6 +816,12 @@ public:
void SetClipControl(const ClipControl& control) { m_clip_control = control; }
[[nodiscard]] const RenderControl& GetRenderControl() const { return m_render_control; }
void SetRenderControl(const RenderControl& control) { m_render_control = control; }
[[nodiscard]] const DepthRenderOverride& GetDepthRenderOverride() const {
return m_depth_render_override;
}
void SetDepthRenderOverride(const DepthRenderOverride& control) {
m_depth_render_override = control;
}
[[nodiscard]] const DepthControl& GetDepthControl() const { return m_depth_control; }
void SetDepthControl(const DepthControl& control) { m_depth_control = control; }
[[nodiscard]] const ModeControl& GetModeControl() const { return m_mode_control; }
@@ -904,15 +917,16 @@ private:
uint32_t m_shader_stages = 0;
DepthRenderTarget m_depth_render_target;
RenderControl m_render_control;
DepthControl m_depth_control;
StencilControl m_stencil_control;
StencilMask m_stencil_mask;
float m_depth_clear_value = 0.0f;
float m_depth_bounds_min = 0.0f;
float m_depth_bounds_max = 1.0f;
uint8_t m_stencil_clear_value = 0;
DepthRenderTarget m_depth_render_target;
RenderControl m_render_control;
DepthRenderOverride m_depth_render_override;
DepthControl m_depth_control;
StencilControl m_stencil_control;
StencilMask m_stencil_mask;
float m_depth_clear_value = 0.0f;
float m_depth_bounds_min = 0.0f;
float m_depth_bounds_max = 1.0f;
uint8_t m_stencil_clear_value = 0;
ModeControl m_mode_control;
PolyOffset m_poly_offset;
+2 -8
View File
@@ -15,7 +15,6 @@
namespace Libs::Graphics {
struct VulkanImage;
struct VulkanMemory;
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
@@ -32,6 +31,7 @@ struct GraphicContext {
bool compute_subgroup_size_control_enabled = false;
bool sample_rate_shading_enabled = false;
bool attachment_feedback_loop_enabled = false;
bool provoking_vertex_last_enabled = false;
bool supports_block_texel_view = false;
bool mesh_shader_enabled = false;
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
@@ -117,12 +117,6 @@ private:
m_image_format_properties;
};
struct VulkanMemory {
vk::MemoryPropertyFlags property = {};
vk::MemoryPropertyFlags preferred_property = {};
VmaAllocation allocation = nullptr;
};
struct VulkanImageState {
vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
@@ -144,7 +138,7 @@ struct VulkanImage {
vk::Image image = nullptr;
VulkanImageState state;
std::vector<VulkanImageState> subresource_states;
Graphics::VulkanMemory memory;
VmaAllocation allocation = nullptr;
};
-19
View File
@@ -1,6 +1,5 @@
#include "graphics/host_gpu/pageManager.h"
#include "common/platform/sysDbg.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "kernel/memory.h"
@@ -21,10 +20,7 @@
#include <windows.h>
#undef min
#undef max
#elif defined(__APPLE__)
#include <unistd.h>
#else
#include <execinfo.h>
#include <unistd.h>
#endif
@@ -53,21 +49,6 @@ constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
std::fputs("PageManager fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "invalid page state", stderr);
std::fputc('\n', stderr);
#if !defined(__APPLE__)
void* frames[16] {};
int frame_count = static_cast<int>(std::size(frames));
SysStackWalk(frames, &frame_count);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const auto image_base = reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
for (int i = 0; i < frame_count; i++) {
const auto address = reinterpret_cast<uintptr_t>(frames[i]);
std::fprintf(stderr, " frame[%d]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
address, address >= image_base ? address - image_base : 0);
}
#else
::backtrace_symbols_fd(frames, frame_count, STDERR_FILENO);
#endif
#endif
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
+6 -4
View File
@@ -277,10 +277,10 @@ void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write)
"memory invalidation");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
m_gpu_modified_ranges.ForEachIntersection(address, bytes, [&](RangeSet::Range range) {
copies.push_back(
{&buffer, buffer.Offset(range.address), range.address, range.size});
}
});
});
if (!copies.empty()) {
DownloadBufferMemory(copies);
@@ -340,10 +340,12 @@ BufferCache::OverlapResult BufferCache::ResolveOverlaps(uint64_t vaddr, uint64_t
end = std::max(end, buffer_end);
if (!has_stream_leap && (stream_score += buffer.StreamScore()) > StreamLeapThreshold) {
has_stream_leap = true;
if (expands_right) {
// Fix the shadPS4 bug that reserves space opposite to the incoming stream's growth.
// The old buffer extending left of the request predicts growth to the right, and vice versa.
if (expands_left) {
end += std::min(StreamLeapSize, PageTable::kAddressSpaceSize - end);
}
if (expands_left) {
if (expands_right) {
const auto minimum = CACHING_PAGESIZE * 2;
if (begin > minimum) {
begin -= std::min(StreamLeapSize, begin - minimum);
+3 -7
View File
@@ -187,15 +187,11 @@ TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler
}
TextureCache::~TextureCache() {
std::vector<ImageId> registered;
m_slot_images.ForEach([&](ImageId id, const Image& image) {
if (image.registered) {
registered.push_back(id);
UnregisterImage(id);
}
});
for (const auto id: registered) {
UnregisterImage(id);
}
}
bool TextureCache::SameBacking(const ImageInfo& cached, const ImageInfo& requested,
@@ -1334,8 +1330,8 @@ ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool e
if (!GuestRange {address, size}.Valid()) {
return {};
}
std::scoped_lock lock {m_lock};
std::vector<ImageId> matches;
std::scoped_lock lock {m_lock};
ImageIds matches;
for (const auto id: FindImagesInRegion(address, size, false)) {
auto owner = m_slot_images.try_get(id);
if (owner == nullptr || owner->info.data.address != address) {
@@ -41,16 +41,13 @@ static bool DccAlphaOnMsb(const HW::ColorInfo& info) {
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& r,
void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
uint32_t rt_slot, bool ignore_target_mask,
bool exact_format) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(buffer)
: render_target_slot);
const auto& rt = hw.GetRenderTarget(rt_slot);
auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot);
if (ignore_target_mask && rt.base.addr != 0 && mask == 0) {
@@ -206,17 +203,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
" layer=%u/%u\n",
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
}
if (tile) {
if (volume || texture_tile) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else {
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
}
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
// PPSA28068: the linear EULA target and its sampled view must share the padded pitch.
if (!tile || volume || texture_tile) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else {
pitch = width;
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
}
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
TileSizeOffset mip_sizes[16] {};
@@ -208,13 +208,7 @@ void CommandScheduler::Wait(uint64_t tick) {
}
void CommandScheduler::PopPendingOperations() {
PopPendingOperations(true);
}
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
if (refresh_gpu_tick) {
m_master.Refresh();
}
m_master.Refresh();
for (;;) {
PendingOperation operation;
{
@@ -81,7 +81,6 @@ private:
};
void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
-1
View File
@@ -478,7 +478,6 @@ static void McCheck(const HW::ModeControl& c) {
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(c.provoking_vtx_last != false);
EXIT_NOT_IMPLEMENTED(c.persp_corr_dis != false);
}
@@ -10,6 +10,7 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
@@ -96,53 +97,17 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
return vk::Format::eUndefined;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
(void)submit_id;
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const bool depth_active = dc.z_enable || dc.depth_bounds_enable ||
rc.depth_clear_enable || rc.copy_depth_to_color;
const bool stencil_active =
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
if (!depth_active && !stencil_active) {
return;
}
static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
const HW::DepthRenderTarget& z,
bool write_buffer = false) {
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const auto depth_address = write_buffer ? z.z_write_base_addr : z.z_read_base_addr;
const auto stencil_address =
write_buffer ? z.stencil_write_base_addr : z.stencil_read_base_addr;
if (!z.z_info.HasValidTextureCompatibility() ||
!z.stencil_info.HasValidTextureCompatibility()) {
DepthFatal("invalid PS5 depth texture-compatibility encoding");
}
const bool attachment_unbound =
z.z_info.format == Prospero::DepthFormat::kInvalid &&
z.stencil_info.format == Prospero::StencilFormat::kInvalid && z.z_info.num_samples == 0 &&
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
!z.depth_view.stencil_write_disable && z.z_read_base_addr == 0 && z.z_write_base_addr == 0 &&
z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0 &&
z.htile_data_base_addr == 0 &&
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
// presence alone must not manufacture a depth attachment.
!z.z_info.htile_acceleration && z.shading_rate_encoding == 0 && z.size.x_max == 0 &&
z.size.y_max == 0;
if (attachment_unbound) {
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
}
return;
}
const bool has_htile = z.z_info.htile_acceleration;
const bool unsupported_shading_rate_encoding =
z.shading_rate_encoding > 1 || (z.shading_rate_encoding != 0 && !has_htile);
@@ -160,23 +125,16 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
rc.copy_sample != 0 || z.z_info.expclear_enabled ||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
if (z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
z.z_info.partially_resident ||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
z.depth_view.current_mip_level != 0 || unsupported_shading_rate_encoding ||
z.z_read_base_addr == 0 ||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
(z.z_read_base_addr & 0xffffu) != 0 ||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) {
depth_address == 0 || (depth_address & 0xffffu) != 0) {
DepthFatal("unsupported depth register state");
}
if (has_stencil) {
if (z.stencil_info.format != Prospero::StencilFormat::k8UInt || !htile_stencil_compat ||
z.stencil_read_base_addr == 0 ||
(!z.depth_view.stencil_write_disable &&
z.stencil_write_base_addr != z.stencil_read_base_addr) ||
(z.stencil_read_base_addr & 0xffffu) != 0) {
stencil_address == 0 || (stencil_address & 0xffffu) != 0) {
DepthFatal("unsupported stencil attachment state");
}
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0) {
@@ -229,13 +187,93 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
const auto depth_backing_size = depth_size.size * view.image_layers;
const auto stencil_backing_size = stencil_size.size * view.image_layers;
const auto htile_backing_size = htile_size.size * view.image_layers;
if (!GuestRange {z.z_read_base_addr, depth_backing_size}.Valid() ||
(has_stencil && !GuestRange {z.stencil_read_base_addr, stencil_backing_size}.Valid()) ||
if (!GuestRange {depth_address, depth_backing_size}.Valid() ||
(has_stencil && !GuestRange {stencil_address, stencil_backing_size}.Valid()) ||
(has_htile && !GuestRange {z.htile_data_base_addr, htile_backing_size}.Valid())) {
DepthFatal("layered depth backing range is invalid");
}
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {depth_address, depth_backing_size};
desc.info.stencil =
has_stencil ? GuestRange {stencil_address, stencil_backing_size} : GuestRange {};
desc.info.pixel_format = format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::TileMode::kDepth;
desc.info.mip_layout[0] = {0, depth_backing_size, pitch, height};
desc.info.metadata.range =
has_htile ? GuestRange {z.htile_data_base_addr, htile_backing_size} : GuestRange {};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
desc.view_info.format = format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
return desc;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const bool depth_active = dc.z_enable || dc.depth_bounds_enable ||
rc.depth_clear_enable || rc.copy_depth_to_color;
const bool stencil_active =
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
if (!depth_active && !stencil_active) {
return;
}
const bool attachment_unbound =
z.z_info.format == Prospero::DepthFormat::kInvalid &&
z.stencil_info.format == Prospero::StencilFormat::kInvalid && z.z_info.num_samples == 0 &&
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
!z.depth_view.stencil_write_disable && z.z_read_base_addr == 0 && z.z_write_base_addr == 0 &&
z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0 &&
z.htile_data_base_addr == 0 &&
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
// presence alone must not manufacture a depth attachment.
!z.z_info.htile_acceleration && z.shading_rate_encoding == 0 && z.size.x_max == 0 &&
z.size.y_max == 0;
if (attachment_unbound) {
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
}
return;
}
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
rc.copy_sample != 0 || dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
(has_stencil && !z.depth_view.stencil_write_disable &&
z.stencil_write_base_addr != z.stencil_read_base_addr)) {
DepthFatal("unsupported depth register state");
}
r.desc = MakeDepthTargetDesc(buffer, z);
r.depth_clear_enable = rc.depth_clear_enable;
r.depth_meta_clear_enable = false;
r.depth_load_clear_enable = r.depth_clear_enable;
r.depth_clear_value = hw.GetDepthClearValue();
r.depth_test_enable = dc.z_enable;
@@ -285,41 +323,73 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
r.stencil_dynamic_back = r.stencil_dynamic_front;
}
}
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {z.z_read_base_addr, depth_backing_size};
desc.info.stencil =
has_stencil ? GuestRange {z.stencil_read_base_addr, stencil_backing_size} : GuestRange {};
desc.info.pixel_format = format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::TileMode::kDepth;
desc.info.mip_layout[0] = {0, depth_backing_size, pitch, height};
desc.info.metadata.range =
has_htile ? GuestRange {z.htile_data_base_addr, htile_backing_size} : GuestRange {};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
desc.view_info.format = format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
r.desc = std::move(desc);
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
BindRenderTarget(r.image_id);
}
bool RenderExecutor::DepthStencilCopy(CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& override = hw.GetDepthRenderOverride();
if (hw.GetColorControl().mode != 0) {
return false;
}
const bool depth_copy = override.force_z_dirty && override.force_z_valid &&
z.z_info.format != Prospero::DepthFormat::kInvalid &&
z.z_read_base_addr != 0 && z.z_write_base_addr != 0 &&
z.z_read_base_addr != z.z_write_base_addr;
const bool stencil_copy = override.force_stencil_dirty && override.force_stencil_valid &&
z.stencil_info.format != Prospero::StencilFormat::kInvalid &&
z.stencil_read_base_addr != 0 && z.stencil_write_base_addr != 0 &&
z.stencil_read_base_addr != z.stencil_write_base_addr;
if (!depth_copy && !stencil_copy) {
return false;
}
auto read_desc = MakeDepthTargetDesc(buffer, z);
auto write_desc = MakeDepthTargetDesc(buffer, z, true);
auto& cache = m_context.GetTextureCache();
const auto read_id = cache.FindImage(read_desc);
BindRenderTarget(read_id);
const auto write_id = cache.FindImage(write_desc);
BindRenderTarget(write_id);
cache.UpdateImage(read_id);
cache.UpdateImage(write_id);
cache.MarkGpuWritten(write_id);
auto& source = cache.GetImage(read_id);
auto& destination = cache.GetImage(write_id);
EXIT_IF(read_id == write_id || source.backing.format != destination.backing.format);
auto& scheduler = m_context.GetCommandScheduler();
scheduler.EndRendering();
const auto command = scheduler.Current().Handle();
const ImageSubresourceRange range {read_desc.view_info.base_level, 1,
read_desc.view_info.base_layer,
read_desc.view_info.layer_count};
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
range, command);
destination.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
range, command);
std::array<vk::ImageCopy, 2> regions {};
uint32_t count = 0;
for (const auto aspect: {vk::ImageAspectFlagBits::eDepth, vk::ImageAspectFlagBits::eStencil}) {
if ((aspect == vk::ImageAspectFlagBits::eDepth && !depth_copy) ||
(aspect == vk::ImageAspectFlagBits::eStencil && !stencil_copy)) {
continue;
}
auto& region = regions[count++];
region.srcSubresource = {aspect, range.base_level, range.base_layer,
range.layer_count};
region.dstSubresource = region.srcSubresource;
region.extent = write_desc.info.extent;
}
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
destination.backing.image, vk::ImageLayout::eTransferDstOptimal,
count, regions.data());
return true;
}
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
const auto format = desc.view_info.format;
if (format == vk::Format::eUndefined) {
@@ -23,7 +23,6 @@ struct RenderDepthInfo {
TextureCache::ImageDesc desc;
bool depth_clear_enable = false;
bool depth_load_clear_enable = false;
bool depth_meta_clear_enable = false;
float depth_clear_value = 0.0f;
bool depth_test_enable = false;
// Effective draw writes; discovery applies test, target-write and clear controls.
+10 -20
View File
@@ -661,26 +661,17 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
return;
}
backing.format = info.pixel_format;
backing.image_type = HostImageType(info.type);
backing.extent = info.extent;
backing.layers = info.IsVolume() ? 1u : info.resources.layers;
backing.mip_levels = info.resources.levels;
backing.samples = info.samples;
backing.flags = ImageCreateFlags(graphics, info);
backing.usage = ImageUsageFlags(graphics, info);
vk::ImageCreateInfo create {};
create.flags = backing.flags;
create.imageType = backing.image_type;
create.extent = backing.extent;
create.mipLevels = backing.mip_levels;
create.arrayLayers = backing.layers;
create.format = backing.format;
create.flags = ImageCreateFlags(graphics, info);
create.imageType = HostImageType(info.type);
create.extent = info.extent;
create.mipLevels = info.resources.levels;
create.arrayLayers = info.IsVolume() ? 1u : info.resources.layers;
create.format = info.pixel_format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = backing.state.layout;
create.usage = backing.usage;
create.samples = vulkan_sample_count(backing.samples);
create.initialLayout = vk::ImageLayout::eUndefined;
create.usage = ImageUsageFlags(graphics, info);
create.samples = vulkan_sample_count(info.samples);
vk::ImageFormatProperties properties {};
if (graphics.GetImageFormatProperties(create.format, create.imageType, create.tiling,
@@ -691,10 +682,9 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
"flags=0x%x samples=%u\n",
static_cast<int>(create.format), static_cast<int>(create.imageType),
static_cast<vk::ImageUsageFlags::MaskType>(create.usage),
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), backing.samples);
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), info.samples);
}
backing.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
if (!graphics.CreateImage(create, backing)) {
EXIT("failed to create image: extent=%ux%ux%u format=%d layers=%u levels=%u\n",
create.extent.width, create.extent.height, create.extent.depth,
@@ -5,11 +5,11 @@
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shader.h"
#include <algorithm>
#include <bit>
#include <array>
#include <cinttypes>
#include <cstring>
namespace Libs::Graphics {
namespace {
@@ -27,13 +27,13 @@ constexpr Prospero::ColorComponentMapping kRenderTargetColorMappings[4][4] = {
Prospero::ColorMappingArgb},
};
struct RenderTargetHostFormatInfo {
struct HostFormatInfo {
vk::Format format = vk::Format::eUndefined;
Prospero::ColorComponentMapping host_to_storage;
};
RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) {
case Prospero::BufferFormat::k8_8_8_8UNorm:
@@ -47,17 +47,77 @@ RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat
default: break;
}
}
const auto format = VulkanFormat(guest_format);
switch (guest_format) {
case Prospero::BufferFormat::k5_5_5_1UNorm:
return {vk::Format::eA1R5G5B5UnormPack16, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k5_5_5_1UNorm: return {format, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k1_5_5_5UNorm:
return {vk::Format::eR5G5B5A1UnormPack16, Prospero::ColorMappingAbgr};
case Prospero::BufferFormat::k4_4_4_4UNorm:
return {vk::Format::eR4G4B4A4UnormPack16, Prospero::ColorMappingAbgr};
default: return {VulkanFormat(guest_format), {}};
case Prospero::BufferFormat::k4_4_4_4UNorm: return {format, Prospero::ColorMappingAbgr};
default: return {format, {}};
}
}
uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
size_t GetTextureRegionCount(uint32_t depth, uint32_t levels, bool volume_texture) {
size_t count = 0;
for (uint32_t level = 0; level < levels; level++) {
count += GetTextureLevelDepth(depth, level, volume_texture);
}
return count;
}
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint32_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
stride = std::max(stride, mips[mip_level].offset + mips[mip_level].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
stride = std::max(stride, total_size / depth);
}
return stride;
}
uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t element_columns =
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
const uint32_t element_rows =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
return static_cast<uint64_t>(element_columns) * element_rows * element.bytes;
}
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, uint32_t height,
uint32_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto mip_pitch = base_pitch;
auto mip_height = height;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto size = CalcLinearUploadLevelSize(element, mip_pitch, mip_height);
mips[mip_level] = {offset, size};
offset += size;
if (mip_pitch > 1) {
mip_pitch /= 2;
}
if (mip_height > 1) {
mip_height /= 2;
}
}
return offset;
}
bool FitsBufferRange(uint64_t offset, uint64_t size, uint64_t capacity) {
return offset <= capacity && size <= capacity - offset;
}
} // namespace
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
@@ -65,135 +125,61 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host = ResolveRenderTargetHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host.format != vk::Format::eUndefined && bytes != 0) {
const auto host_format = ResolveHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host_format.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
kRenderTargetColorMappings[static_cast<size_t>(order)][encoding.components - 1u];
return {host.format, bytes, host.host_to_storage.Then(order_mapping)};
return {host_format.format, bytes, host_format.host_to_storage.Then(order_mapping)};
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n",
static_cast<uint32_t>(layout), static_cast<uint32_t>(type), static_cast<uint32_t>(order));
}
namespace {
static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume_texture) {
size_t count = 0;
for (uint32_t level = 0; level < levels; level++) {
count += GetTextureLevelDepth(depth, level, volume_texture);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage) {
constexpr std::array host_components {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG,
vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA};
std::array<vk::ComponentSwizzle, 4> storage_components {};
for (uint32_t host_component = 0; host_component < host_components.size(); ++host_component) {
storage_components[host_to_storage.Map(host_component)] = host_components[host_component];
}
return count;
}
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return vk::ComponentSwizzle::eR;
case Prospero::CompSwizzle::kGreen: return vk::ComponentSwizzle::eG;
case Prospero::CompSwizzle::kBlue: return vk::ComponentSwizzle::eB;
case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA;
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
}
return vk::ComponentSwizzle::eIdentity;
}
static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
return (swizzle >> (channel * 3u)) & 0x7u;
}
} // namespace
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
vk::ComponentMapping components {};
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
components.g = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 1)));
components.b = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 2)));
components.a = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 3)));
return components;
const auto resolve = [&](uint32_t component) {
const auto selector = static_cast<Prospero::CompSwizzle>(GetDstSel(swizzle, component));
switch (selector) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return storage_components[0];
case Prospero::CompSwizzle::kGreen: return storage_components[1];
case Prospero::CompSwizzle::kBlue: return storage_components[2];
case Prospero::CompSwizzle::kAlpha: return storage_components[3];
default: EXIT("unknown swizzle: %u\n", static_cast<uint32_t>(selector));
}
};
return {resolve(0), resolve(1), resolve(2), resolve(3)};
}
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto vk_format = VulkanFormat(backing_format);
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto host_format = ResolveHostFormat(backing_format, Prospero::ChannelOrder::kStandard);
const auto conversion_format =
backing_format != format ? format : Prospero::BufferFormat::kInvalid;
if (vk_format != vk::Format::eUndefined) {
return SurfaceFormatInfo(vk_format, conversion_format);
if (host_format.format != vk::Format::eUndefined) {
return {host_format.format, conversion_format, host_format.host_to_storage};
}
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
return SurfaceFormatInfo(vk::Format::eUndefined, Prospero::BufferFormat::kInvalid);
}
namespace {
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint64_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t i = 0; i < levels; i++) {
stride = std::max(stride, mips[i].offset + mips[i].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
const auto guest_stride = total_size / depth;
if (guest_stride >= stride) {
stride = guest_stride;
}
}
return stride;
}
uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t elements_w =
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
const uint32_t elements_h =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
return static_cast<uint64_t>(elements_w) * elements_h * element.bytes;
}
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, uint64_t height,
uint64_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height);
for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(element, pitch, h);
mips[i].size = size;
mips[i].offset = offset;
offset += size;
if (pitch > 1) {
pitch /= 2;
}
if (h > 1) {
h /= 2;
}
}
return offset;
}
} // namespace
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
Prospero::TileMode tile_mode, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.surface.description = {
format,
tile,
tile_mode,
volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width,
height,
@@ -202,7 +188,6 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
volume_texture ? 1u : depth,
};
const auto& description = layout.surface.description;
const auto tile_mode = description.tile_mode;
TileTextureElementLayout element {};
if (format == Prospero::BufferFormat::kInvalid) {
@@ -212,34 +197,19 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
levels);
}
switch (tile_mode) {
case Prospero::TileMode::kLinear:
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
break;
case Prospero::TileMode::kDepth:
if (!allow_depth_tile || !TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
default:
if (!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
}
if (tile_mode != Prospero::TileMode::kLinear) {
if (tile_mode == Prospero::TileMode::kLinear) {
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
} else {
if ((tile_mode == Prospero::TileMode::kDepth && !allow_depth_tile) ||
!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format), static_cast<uint32_t>(tile_mode),
upload_size, width, height, levels);
}
element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
}
@@ -272,40 +242,35 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const bool linear_texture = description.tile_mode == Prospero::TileMode::kLinear;
const auto depth = volume_texture ? description.depth : description.layers;
uint32_t mip_width = description.width;
uint32_t mip_height = description.height;
uint32_t mip_pitch = volume_texture && description.tile_mode != Prospero::TileMode::kLinear
? description.width
: layout.pitch;
uint32_t mip_pitch = volume_texture && !linear_texture ? description.width : layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
for (uint32_t i = 0; i < description.levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.mips[i].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {};
region.bufferOffset = layout.mips[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, volume_texture ? 0u : z,
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear = description.tile_mode == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.mips[i].row_length;
region.bufferImageHeight = layout.mips[i].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block;
};
const auto pitch = align(mip_pitch, texel_width);
region.bufferRowLength = pitch > align(mip_width, texel_width) ? pitch : 0;
}
for (uint32_t mip_level = 0; mip_level < description.levels; ++mip_level) {
EXIT_NOT_IMPLEMENTED(layout.mips[mip_level].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
vk::BufferImageCopy region {};
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, mip_level, 0, 1};
region.imageExtent = {mip_width, mip_height, 1};
if (linear_texture) {
region.bufferRowLength = layout.mips[mip_level].row_length;
region.bufferImageHeight = layout.mips[mip_level].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto align_to_texel = [texel_width](uint32_t dimension) {
return ((dimension + texel_width - 1u) / texel_width) * texel_width;
};
const auto aligned_pitch = align_to_texel(mip_pitch);
region.bufferRowLength = aligned_pitch > align_to_texel(mip_width) ? aligned_pitch : 0;
}
for (uint32_t slice_index = 0; slice_index < mip_depth; ++slice_index) {
region.bufferOffset = layout.mips[mip_level].offset + slice_index * layout.slice_stride;
region.imageSubresource.baseArrayLayer = volume_texture ? 0u : slice_index;
region.imageOffset.z = volume_texture ? static_cast<int>(slice_index) : 0;
regions.push_back(region);
}
@@ -323,17 +288,9 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
return regions;
}
static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) {
if (offset > capacity || length > capacity - offset) {
return false;
}
size = length;
return true;
}
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& out_infos) {
std::vector<GpuTileInfo>& out_tile_infos) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const auto depth = volume_texture ? description.depth : description.layers;
@@ -351,111 +308,69 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
return false;
}
std::vector<GpuTileInfo> infos;
infos.reserve(regions.size());
if (volume_texture) {
size_t region_base = 0;
for (uint32_t level = 0; level < levels; ++level) {
const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true);
const bool tail = level >= surface.first_tail_level;
const uint64_t linear_stride = layout.slice_stride;
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
const auto& region = regions[region_base + z];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * surface.block_slice_size +
surface.mips[level].offset;
const uint64_t linear_span =
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
layout.mips[level].size;
if (!SetGpuTileSize(info.linear_offset, linear_span, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, surface.mips[level].size, tiled_size,
info.tiled_size)) {
return false;
}
info.linear_slice_stride = linear_stride;
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.depth = copy_depth;
info.surface_z =
std::vector<GpuTileInfo> tile_infos;
tile_infos.reserve(regions.size());
const auto slices_per_copy = volume_texture ? block.block_depth : 1u;
size_t region_base = 0;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto& mip = surface.mips[mip_level];
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
for (uint32_t slice_index = 0; slice_index < mip_depth; slice_index += slices_per_copy) {
const auto& region = regions[region_base + slice_index];
GpuTileInfo tile_info {};
tile_info.family = block.family;
tile_info.bytes_per_element = block.bytes_per_element;
tile_info.linear_offset = region.bufferOffset;
tile_info.linear_size = layout.mips[mip_level].size;
tile_info.tiled_size = mip.size;
if (volume_texture) {
tile_info.depth = std::min(slices_per_copy, mip_depth - slice_index);
tile_info.linear_slice_stride = layout.slice_stride;
tile_info.linear_size += (tile_info.depth - 1u) * layout.slice_stride;
tile_info.tiled_offset =
mip.offset +
static_cast<uint64_t>(slice_index / slices_per_copy) * surface.block_slice_size;
tile_info.surface_z =
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail_x = tail ? surface.mips[level].tail_x : 0;
info.tail_y = tail ? surface.mips[level].tail_y : 0;
info.tail = tail;
info.tiled_width = surface.mips[level].padded_width;
info.tiled_height = surface.mips[level].padded_height;
infos.push_back(info);
}
region_base += mip_depth;
}
} else {
size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.mips[level];
const auto& mip = surface.mips[level];
const bool tail = level >= surface.first_tail_level;
const auto level_depth = GetTextureLevelDepth(depth, level, false);
for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
} else {
const auto source_stride = layout.source_slice_stride != 0
? layout.source_slice_stride
: surface.block_slice_size;
if (z > (UINT64_MAX - mip.offset) / source_stride) {
if (slice_index > (UINT64_MAX - mip.offset) / source_stride) {
return false;
}
info.tiled_offset = mip.offset + static_cast<uint64_t>(z) * source_stride;
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, mip.size, tiled_size, info.tiled_size)) {
return false;
}
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail = tail;
info.tail_x = tail ? mip.tail_x : 0;
info.tail_y = tail ? mip.tail_y : 0;
info.tiled_width = mip.padded_width;
info.tiled_height = mip.padded_height;
infos.push_back(info);
tile_info.tiled_offset =
mip.offset + static_cast<uint64_t>(slice_index) * source_stride;
tile_info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
}
if (!FitsBufferRange(tile_info.linear_offset, tile_info.linear_size, UINT64_MAX) ||
!FitsBufferRange(tile_info.tiled_offset, tile_info.tiled_size, tiled_size)) {
return false;
}
const auto row_length =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto image_height = region.bufferImageHeight != 0 ? region.bufferImageHeight
: region.imageExtent.height;
tile_info.width = std::max(
(region.imageExtent.width + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.height =
std::max((image_height + texture.texel_height - 1u) / texture.texel_height, 1u);
tile_info.pitch =
std::max((row_length + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.tail = mip_level >= surface.first_tail_level;
tile_info.tail_x = tile_info.tail ? mip.tail_x : 0;
tile_info.tail_y = tile_info.tail ? mip.tail_y : 0;
tile_info.tiled_width = mip.padded_width;
tile_info.tiled_height = mip.padded_height;
tile_infos.push_back(tile_info);
}
region_base += mip_depth;
}
if (infos.empty()) {
return false;
}
out_infos = std::move(infos);
out_tile_infos = std::move(tile_infos);
return true;
}
@@ -1,9 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -20,12 +18,9 @@ struct RenderTargetFormatInfo {
};
struct SurfaceFormatInfo {
explicit constexpr SurfaceFormatInfo(vk::Format format,
Prospero::BufferFormat conversion_format)
: vk_format(format), conversion_format(conversion_format) {}
vk::Format vk_format;
Prospero::BufferFormat conversion_format;
vk::Format vk_format;
Prospero::BufferFormat conversion_format;
Prospero::ColorComponentMapping host_to_storage;
};
struct TextureUploadMipLayout {
@@ -43,20 +38,21 @@ struct TextureUploadLayout {
TextureUploadMipLayout mips[16] = {};
};
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage);
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format);
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
Prospero::ChannelOrder order);
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
Prospero::TileMode tile_mode, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& infos);
std::vector<GpuTileInfo>& out_tile_infos);
} // namespace Libs::Graphics
@@ -404,30 +404,22 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
struct NullImageSpec {
vk::Format format;
Prospero::BufferFormat guest_format;
};
static NullImageSpec NullTextureSpec(const ShaderRecompiler::IR::ImageResource& resource) {
switch (resource.numeric_class) {
case Prospero::TextureNumericClass::Float:
return {vk::Format::eR32Sfloat, Prospero::BufferFormat::k32Float};
case Prospero::TextureNumericClass::Uint:
return {vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt};
case Prospero::TextureNumericClass::Sint:
return {vk::Format::eR32Sint, Prospero::BufferFormat::k32SInt};
case Prospero::TextureNumericClass::Unsupported: break;
}
EXIT("null image has unsupported numeric class\n");
}
static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::ImageResource& resource,
TextureCache::BindingType binding) {
const auto spec = NullTextureSpec(resource);
TextureCache::ImageDesc desc {};
desc.info.pixel_format = spec.format;
desc.info.guest_format = spec.guest_format;
switch (resource.numeric_class) {
case Prospero::TextureNumericClass::Float:
desc.info.guest_format = Prospero::BufferFormat::k32Float;
break;
case Prospero::TextureNumericClass::Uint:
desc.info.guest_format = Prospero::BufferFormat::k32UInt;
break;
case Prospero::TextureNumericClass::Sint:
desc.info.guest_format = Prospero::BufferFormat::k32SInt;
break;
default: EXIT("null image has unsupported numeric class\n");
}
desc.info.pixel_format = VulkanFormat(desc.info.guest_format);
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {1, 1, 1};
desc.info.resources = {1, 1};
@@ -491,17 +483,18 @@ static void PopulateTextureMipLayout(ImageInfo& info) {
static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, vk::Format format,
bool shader_conversion, bool storage, uint32_t view_levels,
uint32_t image_layers) {
const SurfaceFormatInfo& surface_format, bool storage,
uint32_t view_levels, uint32_t image_layers) {
ImageViewInfo view {};
view.format = format;
view.aspect = vk::ImageAspectFlagBits::eColor;
view.base_level = descriptor.BaseLevel();
view.level_count = view_levels;
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
view.mapping = storage || shader_conversion
? vk::ComponentMapping {}
: TextureGetComponentMapping(descriptor.DstSelXYZW());
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
view.mapping =
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
? vk::ComponentMapping {}
: TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage);
switch (resource.dimension) {
case ShaderRecompiler::Decoder::ImageDimension::Dim1D:
view.type = vk::ImageViewType::e1D;
@@ -680,7 +673,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
} else {
PopulateTextureMipLayout(desc.info);
}
desc.view_info = TextureViewInfo(resource, descriptor, view_format, shader_conversion, storage,
desc.view_info = TextureViewInfo(resource, descriptor, view_format, surface_format, storage,
view_levels, desc.info.resources.layers);
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
@@ -769,8 +762,7 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
const auto& program = *runtime.program;
const auto& snapshot = runtime.resources;
PreparedBindings prepared;
prepared.program = runtime.program;
prepared.snapshot = &runtime.resources;
prepared.runtime = &runtime;
prepared.images.reserve(program.info.images.size());
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
@@ -795,9 +787,9 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
const auto& snapshot = *prepared.snapshot;
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
const auto& program = *prepared.runtime->program;
const auto& snapshot = prepared.runtime->resources;
auto& cache = m_context.GetBufferCache();
prepared.buffer_sources.clear();
@@ -820,9 +812,9 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
const auto& snapshot = *prepared.snapshot;
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
const auto& program = *prepared.runtime->program;
const auto& snapshot = prepared.runtime->resources;
const auto& layout = program.bindings;
EXIT_IF(prepared.buffer_sources.size() != program.info.buffers.size());
@@ -855,9 +847,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
void RenderExecutor::RebindImages(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
const auto& snapshot = *prepared.snapshot;
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
const auto& program = *prepared.runtime->program;
const auto& snapshot = prepared.runtime->resources;
auto& images = prepared.images;
EXIT_IF(images.size() != program.info.images.size());
auto& texture_cache = m_context.GetTextureCache();
@@ -915,8 +907,8 @@ RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
if (bindings.pixel) {
FindBuffers(*bindings.pixel);
}
if (bindings.vertex.program->info.uses_dma ||
(bindings.pixel && bindings.pixel->program->info.uses_dma)) {
if (bindings.vertex.runtime->program->info.uses_dma ||
(bindings.pixel && bindings.pixel->runtime->program->info.uses_dma)) {
m_context.GetGpuResources().PrepareBda();
}
RebindBuffers(bindings.vertex);
@@ -947,13 +939,13 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
? vk::ShaderStageFlagBits::eFragment
: vk::ShaderStageFlags {};
for (const auto* prepared: prepared_bindings) {
EXIT_IF(prepared == nullptr || prepared->program == nullptr ||
prepared->snapshot == nullptr);
write_count += prepared->program->bindings.descriptors.size();
for (const auto& binding: prepared->program->bindings.descriptors) {
EXIT_IF(prepared == nullptr || prepared->runtime == nullptr || !*prepared->runtime);
const auto& program = *prepared->runtime->program;
write_count += program.bindings.descriptors.size();
for (const auto& binding: program.bindings.descriptors) {
descriptor_count += NativeDescriptorCount(binding);
}
const auto shader_stage = NativeShaderStage(prepared->program->stage);
const auto shader_stage = NativeShaderStage(program.stage);
push_stages |= shader_stage;
EXIT_IF((pipeline_bind_point == vk::PipelineBindPoint::eGraphics &&
(shader_stage & GraphicsStages) == vk::ShaderStageFlags {}) ||
@@ -968,7 +960,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
m_descriptor_writes.reserve(write_count);
for (auto* prepared: prepared_bindings) {
const auto& program = *prepared->program;
const auto& program = *prepared->runtime->program;
auto& descriptors = *prepared;
const auto shader_stage = NativeShaderStage(program.stage);
const auto shader_stages = ShaderPipelineStages(shader_stage);
@@ -16,9 +16,7 @@
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct ResourceSnapshot;
}
struct ShaderStageRuntime;
struct TextureBinding {
ImageId image_id;
@@ -35,8 +33,8 @@ struct PreparedBindings {
BufferId id;
};
const ShaderRecompiler::IR::CompiledShaderInfo* program = nullptr;
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
// The draw owns the immutable compiled-program/runtime-snapshot association through commit.
const ShaderStageRuntime* runtime = nullptr;
// Keep the resolved guest range through cache preparation; only the host buffer ID may
// become stale and need resolving again when bindings are rebound.
std::vector<BufferSource> buffer_sources;
@@ -728,6 +728,7 @@ PipelineCache::Pipeline& PipelineCache::CreateGraphicsPipeline(
static_params.cull_back = !rect_list && mc.cull_back;
static_params.cull_front = !rect_list && mc.cull_front;
static_params.face = mc.face;
static_params.provoking_vtx_last = mc.provoking_vtx_last;
static_params.polygon_mode =
ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back);
@@ -49,6 +49,7 @@ struct PipelineStaticParameters {
bool cull_front = false;
bool cull_back = false;
bool face = false;
bool provoking_vtx_last = false;
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t color_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {};
@@ -68,7 +69,7 @@ struct PipelineStaticParameters {
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) == 165);
static_assert(sizeof(PipelineStaticParameters) == 166);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
@@ -12,6 +12,7 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/BufferFormat.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/rectListShader.h"
#include "graphics/shader/shader.h"
@@ -122,220 +123,10 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
return;
}
switch (fmt) {
case Prospero::BufferFormat::k32_32_32_32Float:
format = vk::Format::eR32G32B32A32Sfloat;
size = 4;
break;
case Prospero::BufferFormat::k32_32_32_32SInt:
format = vk::Format::eR32G32B32A32Sint;
size = 4;
break;
case Prospero::BufferFormat::k32_32_32_32UInt:
format = vk::Format::eR32G32B32A32Uint;
size = 4;
break;
case Prospero::BufferFormat::k32_32_32Float:
format = vk::Format::eR32G32B32Sfloat;
size = 3;
break;
case Prospero::BufferFormat::k32_32_32SInt:
format = vk::Format::eR32G32B32Sint;
size = 3;
break;
case Prospero::BufferFormat::k32_32_32UInt:
format = vk::Format::eR32G32B32Uint;
size = 3;
break;
case Prospero::BufferFormat::k16_16_16_16Float:
format = vk::Format::eR16G16B16A16Sfloat;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16SInt:
format = vk::Format::eR16G16B16A16Sint;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16UInt:
format = vk::Format::eR16G16B16A16Uint;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16SScaled:
format = vk::Format::eR16G16B16A16Sscaled;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16UScaled:
format = vk::Format::eR16G16B16A16Uscaled;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16SNorm:
format = vk::Format::eR16G16B16A16Snorm;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16UNorm:
format = vk::Format::eR16G16B16A16Unorm;
size = 4;
break;
case Prospero::BufferFormat::k32_32Float:
format = vk::Format::eR32G32Sfloat;
size = 2;
break;
case Prospero::BufferFormat::k32_32SInt:
format = vk::Format::eR32G32Sint;
size = 2;
break;
case Prospero::BufferFormat::k32_32UInt:
format = vk::Format::eR32G32Uint;
size = 2;
break;
case Prospero::BufferFormat::k8_8_8_8UInt:
format = vk::Format::eR8G8B8A8Uint;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8SScaled:
format = vk::Format::eR8G8B8A8Sscaled;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8UScaled:
format = vk::Format::eR8G8B8A8Uscaled;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8SNorm:
format = vk::Format::eR8G8B8A8Snorm;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8UNorm:
format = vk::Format::eR8G8B8A8Unorm;
size = 4;
break;
case Prospero::BufferFormat::k10_10_10_2UNorm:
format = vk::Format::eA2B10G10R10UnormPack32;
size = 4;
break;
case Prospero::BufferFormat::k10_10_10_2SNorm:
format = vk::Format::eA2B10G10R10SnormPack32;
size = 4;
break;
case Prospero::BufferFormat::k16_16Float:
format = vk::Format::eR16G16Sfloat;
size = 2;
break;
case Prospero::BufferFormat::k16_16SInt:
format = vk::Format::eR16G16Sint;
size = 2;
break;
case Prospero::BufferFormat::k16_16UInt:
format = vk::Format::eR16G16Uint;
size = 2;
break;
case Prospero::BufferFormat::k16_16SScaled:
format = vk::Format::eR16G16Sscaled;
size = 2;
break;
case Prospero::BufferFormat::k16_16UScaled:
format = vk::Format::eR16G16Uscaled;
size = 2;
break;
case Prospero::BufferFormat::k16_16SNorm:
format = vk::Format::eR16G16Snorm;
size = 2;
break;
case Prospero::BufferFormat::k16_16UNorm:
format = vk::Format::eR16G16Unorm;
size = 2;
break;
case Prospero::BufferFormat::k32Float:
format = vk::Format::eR32Sfloat;
size = 1;
break;
case Prospero::BufferFormat::k32SInt:
format = vk::Format::eR32Sint;
size = 1;
break;
case Prospero::BufferFormat::k32UInt:
format = vk::Format::eR32Uint;
size = 1;
break;
case Prospero::BufferFormat::k8_8SInt:
format = vk::Format::eR8G8Sint;
size = 2;
break;
case Prospero::BufferFormat::k8_8UInt:
format = vk::Format::eR8G8Uint;
size = 2;
break;
case Prospero::BufferFormat::k8_8SScaled:
format = vk::Format::eR8G8Sscaled;
size = 2;
break;
case Prospero::BufferFormat::k8_8UScaled:
format = vk::Format::eR8G8Uscaled;
size = 2;
break;
case Prospero::BufferFormat::k8_8SNorm:
format = vk::Format::eR8G8Snorm;
size = 2;
break;
case Prospero::BufferFormat::k8_8UNorm:
format = vk::Format::eR8G8Unorm;
size = 2;
break;
case Prospero::BufferFormat::k16Float:
format = vk::Format::eR16Sfloat;
size = 1;
break;
case Prospero::BufferFormat::k16SInt:
format = vk::Format::eR16Sint;
size = 1;
break;
case Prospero::BufferFormat::k16UInt:
format = vk::Format::eR16Uint;
size = 1;
break;
case Prospero::BufferFormat::k16SScaled:
format = vk::Format::eR16Sscaled;
size = 1;
break;
case Prospero::BufferFormat::k16UScaled:
format = vk::Format::eR16Uscaled;
size = 1;
break;
case Prospero::BufferFormat::k16SNorm:
format = vk::Format::eR16Snorm;
size = 1;
break;
case Prospero::BufferFormat::k16UNorm:
format = vk::Format::eR16Unorm;
size = 1;
break;
case Prospero::BufferFormat::k8SInt:
format = vk::Format::eR8Sint;
size = 1;
break;
case Prospero::BufferFormat::k8UInt:
format = vk::Format::eR8Uint;
size = 1;
break;
case Prospero::BufferFormat::k8SScaled:
format = vk::Format::eR8Sscaled;
size = 1;
break;
case Prospero::BufferFormat::k8UScaled:
format = vk::Format::eR8Uscaled;
size = 1;
break;
case Prospero::BufferFormat::k8SNorm:
format = vk::Format::eR8Snorm;
size = 1;
break;
case Prospero::BufferFormat::k8UNorm:
format = vk::Format::eR8Unorm;
size = 1;
break;
default:
EXIT("unknown format: fmt = %u\n", raw_format);
format = vk::Format::eUndefined;
size = 4;
break;
format = VulkanFormat(fmt);
size = ShaderRecompiler::Format::GetFormatInfo(fmt).component_count;
if (format == vk::Format::eUndefined || size == 0) {
EXIT("unknown vertex format: fmt = %u\n", raw_format);
}
if (NarrowInputFormat(format, size, used_components)) {
@@ -595,6 +386,15 @@ void CreatePipelineInternal(
#if !defined(__APPLE__)
rasterizer.pNext = &clip_ext;
#endif
vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex {};
EXIT_NOT_IMPLEMENTED(static_params.provoking_vtx_last &&
!graphics.provoking_vertex_last_enabled);
if (graphics.provoking_vertex_last_enabled) {
provoking_vertex.provokingVertexMode = static_params.provoking_vtx_last
? vk::ProvokingVertexModeEXT::eLastVertex : vk::ProvokingVertexModeEXT::eFirstVertex;
provoking_vertex.pNext = rasterizer.pNext;
rasterizer.pNext = &provoking_vertex;
}
rasterizer.cullMode = cull_mode;
rasterizer.frontFace = front_face;
rasterizer.polygonMode = static_params.polygon_mode;
+6 -7
View File
@@ -179,13 +179,12 @@ private:
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
uint32_t render_target_slice_offset, uint32_t render_target_slot,
bool ignore_target_mask = false, bool exact_format = false);
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& target);
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
void ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& target);
[[nodiscard]] bool DepthStencilCopy(CommandBuffer& buffer);
[[nodiscard]] bool PrepareDrawRenderState(CommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state);
@@ -197,7 +196,7 @@ private:
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth,
const std::optional<PreparedBindings>& pixel = std::nullopt);
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
+50 -60
View File
@@ -544,11 +544,11 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
!cache.ClearMeta(metadata.range.address)) {
EXIT("failed to acquire HTile metadata for a depth clear\n");
}
depth.depth_meta_clear_enable =
const bool meta_clear =
metadata.kind == ImageMetadataKind::Htile &&
cache.IsMetaCleared(metadata.range.address, depth.desc.view_info.base_layer);
depth.depth_load_clear_enable = depth.depth_clear_enable || depth.depth_meta_clear_enable;
if (depth.depth_meta_clear_enable &&
depth.depth_load_clear_enable = depth.depth_clear_enable || meta_clear;
if (meta_clear &&
!cache.TouchMeta(metadata.range.address, depth.desc.view_info.base_layer, false)) {
EXIT("failed to consume HTile clear state\n");
}
@@ -863,8 +863,8 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
return true;
}
static bool ResolvePrimitiveRestart(const CommandBuffer& buffer, vk::PrimitiveTopology topology,
uint32_t index_type_and_size) {
static bool ResolvePrimitiveRestart(const CommandBuffer& buffer,
const DrawIndexBufferSource& source) {
const auto control = buffer.GetUserConfig().GetPrimitiveResetControl();
EXIT_NOT_IMPLEMENTED((control & ~0x3u) != 0);
if ((control & 0x1u) == 0) {
@@ -876,35 +876,38 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer, vk::PrimitiveTo
case Prospero::PrimitiveType::kTriStrip: break;
default: return false;
}
if (topology != vk::PrimitiveTopology::eLineStrip &&
topology != vk::PrimitiveTopology::eTriangleStrip &&
topology != vk::PrimitiveTopology::eTriangleFan) {
return false;
}
uint32_t index_mask = 0;
switch (static_cast<Prospero::IndexType>(index_type_and_size)) {
case Prospero::IndexType::kIndex8: index_mask = 0xffu; break;
case Prospero::IndexType::kIndex16: index_mask = 0xffffu; break;
case Prospero::IndexType::kIndex32: index_mask = 0xffffffffu; break;
default: EXIT("unknown index_type_and_size: %u\n", index_type_and_size);
}
const auto reset_index = buffer.GetRegisters().GetPrimitiveResetIndex();
const auto element_size = source.guest_element_size;
const auto index_mask = UINT32_MAX >> ((4 - element_size) * 8);
const auto reset_index = buffer.GetRegisters().GetPrimitiveResetIndex();
if ((control & 0x2u) != 0 && (reset_index & ~index_mask) != 0) {
return false;
}
EXIT_NOT_IMPLEMENTED((reset_index & index_mask) != index_mask);
return true;
const auto restart_index = reset_index & index_mask;
if (restart_index == index_mask) {
// Use native restart; the 8-bit path widens its marker to 0xffff.
return true;
}
// A game can set a custom reset value without using it in the index buffer.
// Keep restart off in that case; fail if we actually find the value.
// Scan before preparing draw resources: readback can restart the command buffer.
EXIT_NOT_IMPLEMENTED(source.address == 0);
const auto* indices = reinterpret_cast<const uint8_t*>(source.address);
for (uint64_t offset = 0; offset < source.size; offset += element_size) {
uint32_t index = 0;
std::memcpy(&index, indices + offset, element_size);
EXIT_NOT_IMPLEMENTED(index == restart_index);
}
return false;
}
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw,
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state) {
auto& ctx = buffer.GetRegisters();
if (ResolveColorTargets(submit_id, buffer, render_target_slice_offset)) {
if (ResolveColorTargets(buffer, render_target_slice_offset)) {
return false;
}
if (log_setup_phases) {
@@ -913,7 +916,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& b
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
if (slot == 0 || (render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0 &&
ctx.GetRenderTarget(slot).base.addr != 0)) {
ResolveRenderColorTarget(submit_id, buffer, state.color_info[state.color_count],
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
render_target_slice_offset, slot);
if (state.color_info[state.color_count].image_id) {
state.color_count++;
@@ -923,7 +926,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& b
if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
ResolveRenderDepthTarget(buffer, state.depth_info);
state.ps_active = DrawHasActivePixelShader(buffer);
if (state.color_count == 0 && !state.depth_info.image_id && !state.ps_active) {
@@ -1235,7 +1238,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
if (ConsumeMetadataColorOperation(buffer)) {
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
ResetBindings();
return;
}
@@ -1270,53 +1273,41 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
vk::IndexType index_type = vk::IndexType::eUint16;
uint64_t index_size = 0;
bool expand_index8_to_u16 = false;
const bool primitive_restart =
ResolvePrimitiveRestart(buffer, topology, args.index_type_and_size);
DrawIndexBufferSource index_source {};
index_source.address = reinterpret_cast<uint64_t>(args.index_addr);
switch (static_cast<Prospero::IndexType>(args.index_type_and_size)) {
case Prospero::IndexType::kIndex16:
index_type = vk::IndexType::eUint16;
index_size = 2 * static_cast<uint64_t>(args.index_count);
index_source.type = vk::IndexType::eUint16;
index_source.guest_element_size = 2;
break;
case Prospero::IndexType::kIndex32:
index_type = vk::IndexType::eUint32;
index_size = 4 * static_cast<uint64_t>(args.index_count);
index_source.type = vk::IndexType::eUint32;
index_source.guest_element_size = 4;
break;
// Some games use it - need vulkan extension
case Prospero::IndexType::kIndex8:
index_type = vk::IndexType::eUint16;
index_size = static_cast<uint64_t>(args.index_count);
expand_index8_to_u16 = true;
index_source.guest_element_size = 1;
break;
default: EXIT("unknown index_type_and_size: %u\n", args.index_type_and_size);
}
index_source.size = static_cast<uint64_t>(args.index_count) * index_source.guest_element_size;
const bool primitive_restart = ResolvePrimitiveRestart(buffer, index_source);
const DrawCallInfo draw {"DrawIndex", CommandBufferDebugOp::DrawIndex, args.index_count,
args.instance_count, args.first_instance};
std::vector<uint16_t> expanded_indices;
if (expand_index8_to_u16) {
if (index_source.guest_element_size == 1) {
EXIT_NOT_IMPLEMENTED(args.index_addr == nullptr);
const auto* src = static_cast<const uint8_t*>(args.index_addr);
expanded_indices.resize(args.index_count);
for (uint32_t i = 0; i < args.index_count; i++) {
expanded_indices[i] = primitive_restart && src[i] == 0xffu ? 0xffffu : src[i];
}
index_source.host_data = expanded_indices.data();
index_source.size = expanded_indices.size() * sizeof(uint16_t);
}
DrawIndexBufferSource index_source {};
index_source.address = reinterpret_cast<uint64_t>(args.index_addr);
index_source.host_data =
expanded_indices.empty() ? nullptr : static_cast<const void*>(expanded_indices.data());
index_source.size =
expanded_indices.empty() ? index_size : expanded_indices.size() * sizeof(uint16_t);
index_source.type = index_type;
index_source.guest_element_size = static_cast<uint32_t>(index_size / args.index_count);
const DrawCallInfo draw {"DrawIndex", CommandBufferDebugOp::DrawIndex, args.index_count,
args.instance_count, args.first_instance};
DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, args.render_target_slice_offset, true,
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, true,
state)) {
ResetBindings();
return;
@@ -1363,7 +1354,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
return;
}
if (ConsumeMetadataColorOperation(buffer)) {
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
ResetBindings();
return;
}
@@ -1393,7 +1384,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
args.vertex_count, args.instance_count, args.first_instance};
DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, args.render_target_slice_offset, false,
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, false,
state)) {
ResetBindings();
return;
@@ -1440,8 +1431,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
ResetBindings();
}
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
uint32_t render_target_slice_offset) {
bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_target_slice_offset) {
const auto& hw = buffer.GetRegisters();
if (hw.GetColorControl().mode != 3) {
return false;
@@ -1455,8 +1445,8 @@ bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, CommandBuffer& buff
RenderColorInfo src {};
RenderColorInfo dst {};
ResolveRenderColorTarget(submit_id, buffer, src, render_target_slice_offset, 0, true, true);
ResolveRenderColorTarget(submit_id, buffer, dst, render_target_slice_offset, 1, true, true);
ResolveRenderColorTarget(buffer, src, render_target_slice_offset, 0, true, true);
ResolveRenderColorTarget(buffer, dst, render_target_slice_offset, 1, true, true);
if (!src.image_id || !dst.image_id) {
return false;
}
+18 -11
View File
@@ -130,35 +130,42 @@ uint64_t GraphicContext::GetTotalMemoryBudget() const {
bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr);
EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr);
auto& memory = image.memory;
VmaAllocationCreateInfo alloc_info {};
alloc_info.requiredFlags = static_cast<vk::MemoryPropertyFlags::MaskType>(memory.property);
alloc_info.preferredFlags =
static_cast<vk::MemoryPropertyFlags::MaskType>(memory.preferred_property);
alloc_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
vk::Image::CType native_image = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>(
vmaCreateImage(allocator, static_cast<const vk::ImageCreateInfo::NativeType*>(image_info),
&alloc_info, &native_image, &memory.allocation, nullptr));
&alloc_info, &native_image, &image.allocation, nullptr));
image.image = native_image;
if (result != vk::Result::eSuccess) {
LogMemoryBudget();
return false;
}
image.format = image_info.format;
image.image_type = image_info.imageType;
image.extent = image_info.extent;
image.layers = image_info.arrayLayers;
image.mip_levels = image_info.mipLevels;
image.samples = static_cast<uint32_t>(image_info.samples);
image.usage = image_info.usage;
image.flags = image_info.flags;
image.state = {.layout = image_info.initialLayout};
image.subresource_states.clear();
return true;
}
void GraphicContext::DeleteImage(VulkanImage& image) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || image.image == nullptr || image.memory.allocation == nullptr);
EXIT_IF(allocator == nullptr || image.image == nullptr || image.allocation == nullptr);
auto& memory = image.memory;
vmaDestroyImage(allocator, image.image, memory.allocation);
image.image = nullptr;
memory.allocation = nullptr;
vmaDestroyImage(allocator, image.image, image.allocation);
image.image = nullptr;
image.allocation = nullptr;
}
} // namespace Libs::Graphics
+17 -1
View File
@@ -17,14 +17,22 @@ struct FormatMapping {
constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k8UNorm, vk::Format::eR8Unorm},
{Prospero::BufferFormat::k8SNorm, vk::Format::eR8Snorm},
{Prospero::BufferFormat::k8UScaled, vk::Format::eR8Uscaled},
{Prospero::BufferFormat::k8SScaled, vk::Format::eR8Sscaled},
{Prospero::BufferFormat::k8UInt, vk::Format::eR8Uint},
{Prospero::BufferFormat::k8SInt, vk::Format::eR8Sint},
{Prospero::BufferFormat::k16UNorm, vk::Format::eR16Unorm},
{Prospero::BufferFormat::k16SNorm, vk::Format::eR16Snorm},
{Prospero::BufferFormat::k16UScaled, vk::Format::eR16Uscaled},
{Prospero::BufferFormat::k16SScaled, vk::Format::eR16Sscaled},
{Prospero::BufferFormat::k16UInt, vk::Format::eR16Uint},
{Prospero::BufferFormat::k16SInt, vk::Format::eR16Sint},
{Prospero::BufferFormat::k16Float, vk::Format::eR16Sfloat},
{Prospero::BufferFormat::k8_8UNorm, vk::Format::eR8G8Unorm},
{Prospero::BufferFormat::k8_8SNorm, vk::Format::eR8G8Snorm},
{Prospero::BufferFormat::k8_8UScaled, vk::Format::eR8G8Uscaled},
{Prospero::BufferFormat::k8_8SScaled, vk::Format::eR8G8Sscaled},
{Prospero::BufferFormat::k8_8UInt, vk::Format::eR8G8Uint},
{Prospero::BufferFormat::k8_8SInt, vk::Format::eR8G8Sint},
{Prospero::BufferFormat::k32UInt, vk::Format::eR32Uint},
@@ -32,14 +40,19 @@ constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k32Float, vk::Format::eR32Sfloat},
{Prospero::BufferFormat::k16_16UNorm, vk::Format::eR16G16Unorm},
{Prospero::BufferFormat::k16_16SNorm, vk::Format::eR16G16Snorm},
{Prospero::BufferFormat::k16_16UScaled, vk::Format::eR16G16Uscaled},
{Prospero::BufferFormat::k16_16SScaled, vk::Format::eR16G16Sscaled},
{Prospero::BufferFormat::k16_16UInt, vk::Format::eR16G16Uint},
{Prospero::BufferFormat::k16_16SInt, vk::Format::eR16G16Sint},
{Prospero::BufferFormat::k16_16Float, vk::Format::eR16G16Sfloat},
{Prospero::BufferFormat::k11_11_10Float, vk::Format::eB10G11R11UfloatPack32},
{Prospero::BufferFormat::k10_10_10_2UNorm, vk::Format::eA2B10G10R10UnormPack32},
{Prospero::BufferFormat::k10_10_10_2SNorm, vk::Format::eA2B10G10R10SnormPack32},
{Prospero::BufferFormat::k10_10_10_2UInt, vk::Format::eA2B10G10R10UintPack32},
{Prospero::BufferFormat::k8_8_8_8UNorm, vk::Format::eR8G8B8A8Unorm},
{Prospero::BufferFormat::k8_8_8_8SNorm, vk::Format::eR8G8B8A8Snorm},
{Prospero::BufferFormat::k8_8_8_8UScaled, vk::Format::eR8G8B8A8Uscaled},
{Prospero::BufferFormat::k8_8_8_8SScaled, vk::Format::eR8G8B8A8Sscaled},
{Prospero::BufferFormat::k8_8_8_8UInt, vk::Format::eR8G8B8A8Uint},
{Prospero::BufferFormat::k8_8_8_8SInt, vk::Format::eR8G8B8A8Sint},
{Prospero::BufferFormat::k32_32UInt, vk::Format::eR32G32Uint},
@@ -47,6 +60,8 @@ constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k32_32Float, vk::Format::eR32G32Sfloat},
{Prospero::BufferFormat::k16_16_16_16UNorm, vk::Format::eR16G16B16A16Unorm},
{Prospero::BufferFormat::k16_16_16_16SNorm, vk::Format::eR16G16B16A16Snorm},
{Prospero::BufferFormat::k16_16_16_16UScaled, vk::Format::eR16G16B16A16Uscaled},
{Prospero::BufferFormat::k16_16_16_16SScaled, vk::Format::eR16G16B16A16Sscaled},
{Prospero::BufferFormat::k16_16_16_16UInt, vk::Format::eR16G16B16A16Uint},
{Prospero::BufferFormat::k16_16_16_16SInt, vk::Format::eR16G16B16A16Sint},
{Prospero::BufferFormat::k16_16_16_16Float, vk::Format::eR16G16B16A16Sfloat},
@@ -63,7 +78,8 @@ constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k8_8_8_8Srgb, vk::Format::eR8G8B8A8Srgb},
{Prospero::BufferFormat::k9_9_9_5Float, vk::Format::eE5B9G9R9UfloatPack32},
{Prospero::BufferFormat::k5_6_5UNorm, vk::Format::eB5G6R5UnormPack16},
{Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eR5G5B5A1UnormPack16},
{Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eA1R5G5B5UnormPack16},
{Prospero::BufferFormat::k1_5_5_5UNorm, vk::Format::eR5G5B5A1UnormPack16},
{Prospero::BufferFormat::k4_4_4_4UNorm, vk::Format::eR4G4B4A4UnormPack16},
{Prospero::BufferFormat::kBc1UNorm, vk::Format::eBc1RgbaUnormBlock},
{Prospero::BufferFormat::kBc1Srgb, vk::Format::eBc1RgbaSrgbBlock},
+3 -12
View File
@@ -191,24 +191,15 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
}
if (dst.image != nullptr) {
graphics.DeleteImage(dst);
dst.memory = {};
}
dst.extent = {extent.width, extent.height, 1};
dst.format = format;
dst.layers = 1;
dst.mip_levels = 1;
dst.state = {};
dst.subresource_states.clear();
dst.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
vk::ImageCreateInfo create {};
create.sType = vk::StructureType::eImageCreateInfo;
create.imageType = vk::ImageType::e2D;
create.extent = {dst.extent.width, dst.extent.height, 1};
create.extent = {extent.width, extent.height, 1};
create.mipLevels = 1;
create.arrayLayers = 1;
create.format = dst.format;
create.format = format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = vk::ImageLayout::eUndefined;
create.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
@@ -216,7 +207,7 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
create.samples = vk::SampleCountFlagBits::e1;
if (!graphics.CreateImage(create, dst)) {
EXIT("failed to allocate prepared presentation image, extent=%ux%u format=%d\n",
dst.extent.width, dst.extent.height, static_cast<int>(dst.format));
extent.width, extent.height, static_cast<int>(format));
}
}
@@ -637,7 +637,15 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
feedback_layout.pNext = &feedback_dynamic;
supported_features2.pNext = &feedback_layout;
}
const bool provoking_extension =
HasExtension(device_extensions, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
vk::PhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex {};
if (provoking_extension) {
provoking_vertex.pNext = supported_features2.pNext;
supported_features2.pNext = &provoking_vertex;
}
physical_device.getFeatures2(&supported_features2);
graphics.provoking_vertex_last_enabled = provoking_extension && provoking_vertex.provokingVertexLast;
graphics.attachment_feedback_loop_enabled =
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
feedback_dynamic.attachmentFeedbackLoopDynamicState;
@@ -753,6 +761,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
create_info.pNext = graphics.attachment_feedback_loop_enabled
? static_cast<void*>(&feedback_layout)
: feedback_dynamic.pNext;
if (graphics.provoking_vertex_last_enabled) {
provoking_vertex.pNext = const_cast<void*>(create_info.pNext);
provoking_vertex.transformFeedbackPreservesProvokingVertex = VK_FALSE;
create_info.pNext = &provoking_vertex;
}
create_info.flags = {};
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
@@ -1145,6 +1158,7 @@ void WindowContext::CreateVulkan() {
graphic_ctx.memory_budget_ext_enabled = true;
}
for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
VK_EXT_MESH_SHADER_EXTENSION_NAME,
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME}) {
if (HasExtension(available_extensions, extension)) {
+1 -1
View File
@@ -1016,7 +1016,7 @@ void WindowContext::UpdateTitle() {
const auto* device_name = graphic_ctx.GetPhysicalDeviceProperties().deviceName.data();
auto text = fmt::format(
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", KYTY_BUILD_LABEL, build_type,
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:.0f}", KYTY_BUILD_LABEL, build_type,
(has_title ? title : ""), (has_title ? ", " : ""), (has_title_id ? title_id : ""),
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
device_name, processor_name, frame_num, current_fps);
+1 -16
View File
@@ -238,28 +238,13 @@ constexpr BufferFormatInfo GetFormatInfo(Prospero::BufferFormat format) {
}
}
constexpr bool IsKnownFormat(Prospero::BufferFormat format) {
return GetFormatInfo(format).type != ComponentType::Unknown;
}
constexpr uint32_t GetFormatComponentCount(Prospero::BufferFormat format) {
return GetFormatInfo(format).component_count;
}
constexpr uint32_t GetFormatComponentByteOffset(Prospero::BufferFormat format, uint32_t component) {
const auto info = GetFormatInfo(format);
constexpr uint32_t GetFormatComponentByteOffset(const BufferFormatInfo& info, uint32_t component) {
if (component >= info.component_count) {
return 0;
}
return info.packed_bitfield ? 0u : info.component_bit_offset[component] / 8u;
}
constexpr bool CanUseTypedBufferLoad(Prospero::BufferFormat format) {
const auto info = GetFormatInfo(format);
return info.type != ComponentType::Unknown && !info.packed_bitfield &&
info.component_bits[0] == 32u;
}
} // namespace Libs::Graphics::ShaderRecompiler::Format
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BUFFERFORMAT_H_ */
@@ -77,8 +77,7 @@ enum class EmbeddedFetchValueType {
AttribTable,
Attrib,
BufferTable,
Buffer,
Index
Buffer
};
struct EmbeddedFetchSgprInfo {
@@ -88,10 +87,6 @@ struct EmbeddedFetchSgprInfo {
std::vector<uint32_t> prolog_loads;
};
struct EmbeddedFetchVgprInfo {
EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown;
};
using EmbeddedFetchVectorLanes = std::map<uint64_t, EmbeddedFetchSgprInfo>;
uint64_t EmbeddedFetchVectorLaneKey(uint32_t reg, uint32_t lane) {
@@ -108,7 +103,6 @@ void ClearEmbeddedFetchVectorLanes(EmbeddedFetchVectorLanes* lanes, uint32_t reg
lanes->erase(first, last);
}
using EmbeddedFetchLoad = Frontend::EmbeddedFetchLoad;
using EmbeddedFetchData = Frontend::EmbeddedFetchPlan;
bool IsDecodedSgpr(const Decoder::Operand& op) {
@@ -136,20 +130,6 @@ uint32_t EmbeddedFetchDstSize(const Decoder::Instruction& inst) {
return inst.opcode == Decoder::Opcode::V_MAD_U64_U32 ? 2u : DecodedDstSize(inst);
}
bool EmbeddedFetchHasBranch(Decoder::Opcode opcode) {
switch (opcode) {
case Decoder::Opcode::S_SETPC_B64:
case Decoder::Opcode::S_BRANCH:
case Decoder::Opcode::S_CBRANCH_SCC0:
case Decoder::Opcode::S_CBRANCH_SCC1:
case Decoder::Opcode::S_CBRANCH_VCCZ:
case Decoder::Opcode::S_CBRANCH_VCCNZ:
case Decoder::Opcode::S_CBRANCH_EXECZ:
case Decoder::Opcode::S_CBRANCH_EXECNZ: return true;
default: return false;
}
}
void ClearEmbeddedFetchSgprs(std::array<EmbeddedFetchSgprInfo, 108>& sgprs,
const Decoder::Operand& dst, uint32_t size) {
if (!IsDecodedSgpr(dst)) {
@@ -244,11 +224,14 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
const int buffer_reg = input_info->fetch_buffer_reg + shift_regs;
std::array<EmbeddedFetchSgprInfo, 108> sgprs {};
std::array<EmbeddedFetchVgprInfo, 256> vgprs {};
std::array<bool, 256> vgpr_is_index {};
EmbeddedFetchVectorLanes vector_lanes;
const bool track_vector_lanes =
std::none_of(decoded.instructions.begin(), decoded.instructions.end(),
[](const auto& inst) { return EmbeddedFetchHasBranch(inst.opcode); });
[](const auto& inst) {
return Decoder::IsDirectBranch(inst.opcode) ||
inst.opcode == Decoder::Opcode::S_SETPC_B64;
});
if (attrib_reg >= 0 && attrib_reg < static_cast<int>(sgprs.size())) {
sgprs[attrib_reg].type = EmbeddedFetchValueType::AttribTable;
@@ -299,8 +282,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
switch (inst.opcode) {
case Decoder::Opcode::V_WRITELANE_B32: {
uint32_t lane = 0;
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
vgprs[inst.dst.reg] = {};
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size()) {
vgpr_is_index[inst.dst.reg] = false;
}
if (track_vector_lanes && IsDecodedVgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
DecodedSgprReg(inst.src0) < sgprs.size() &&
@@ -405,13 +388,13 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
}
} else if (inst.opcode == Decoder::Opcode::V_CNDMASK_B32) {
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size()) {
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
}
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size() &&
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size() &&
IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 &&
IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) {
vgprs[inst.dst.reg].type = EmbeddedFetchValueType::Index;
vgpr_is_index[inst.dst.reg] = true;
}
} else if (IsEmbeddedFetchAttribPropagationAlu(inst)) {
if (IsDecodedSgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
@@ -441,16 +424,11 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
}
}
} else if (IsEmbeddedFetchBufferLoad(inst)) {
if (IsDecodedVgpr(inst.src0) && inst.src0.reg < vgprs.size() &&
vgprs[inst.src0.reg].type == EmbeddedFetchValueType::Index &&
if (IsDecodedVgpr(inst.src0) && inst.src0.reg < vgpr_is_index.size() &&
vgpr_is_index[inst.src0.reg] &&
IsDecodedSgpr(inst.src1) && DecodedSgprReg(inst.src1) < sgprs.size() &&
sgprs[DecodedSgprReg(inst.src1)].type == EmbeddedFetchValueType::Buffer) {
const auto& buffer = sgprs[DecodedSgprReg(inst.src1)];
EmbeddedFetchLoad load;
load.pc = inst.pc;
load.attrib_id = buffer.attrib_id;
load.components = DecodedDstSize(inst);
load.prolog_loads = buffer.prolog_loads;
const auto& buffer = sgprs[DecodedSgprReg(inst.src1)];
if (data.loads.empty()) {
if (!vertex_offset_conflict) {
data.vertex_offset_sgpr = vertex_offset_candidate;
@@ -459,7 +437,11 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
data.instance_offset_sgpr = instance_offset_candidate;
}
}
data.loads.push_back(load);
auto& load = data.loads.emplace_back();
load.pc = inst.pc;
load.attrib_id = buffer.attrib_id;
load.components = DecodedDstSize(inst);
load.prolog_loads = buffer.prolog_loads;
}
}
break;
@@ -467,7 +449,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
if (inst.opcode == Decoder::Opcode::V_MOVRELD_B32) {
vector_lanes.clear();
} else if (inst.opcode != Decoder::Opcode::V_WRITELANE_B32 && IsDecodedVgpr(inst.dst)) {
for (uint32_t i = 0; i < EmbeddedFetchDstSize(inst) && inst.dst.reg + i < vgprs.size();
for (uint32_t i = 0;
i < EmbeddedFetchDstSize(inst) && inst.dst.reg + i < vgpr_is_index.size();
i++) {
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg + i);
}
@@ -541,7 +524,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
Decoder::Program decoded;
std::vector<uint32_t> joined_code;
if (options.stage == ShaderType::Mesh) {
if (!options.back_code.empty()) {
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
} else {
Decoder::DecodeProgram(code, decoded);
@@ -664,11 +647,6 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
IR::EliminateDeadCode(ir.blocks);
IR::TrackResources(ir);
IR::EliminateDeadCode(ir.blocks);
if (options.stage == ShaderType::Vertex) {
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
ir.info.instance_offset_sgpr = embedded_fetch.instance_offset_sgpr;
}
TranslateResult result;
result.program = std::move(ir);
if (options.dump_ir) {
@@ -1,6 +1,6 @@
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
#include "common/debug.h"
#include "common/assert.h"
#include <algorithm>
#include <cstring>
@@ -319,8 +319,7 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
ValidateNativeProgram(program);
IR::ValidateProgram(program, true);
EmitterState state(program, input_info);
state.stage = program.stage;
state.wave_size = program.wave_size;
state.stage = program.stage;
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
state.lane_count =
workgroup != nullptr && program.wave_size == 64u && workgroup->host_subgroup_size == 32u
@@ -333,7 +332,7 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
AllocateInputVariables(state);
AllocateOutputVariables(state);
DefineModule(state);
EmitProgram(state, program);
EmitProgram(state);
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
state.interface_variables);
@@ -10,26 +10,6 @@ struct Pair {
uint32_t high = 0;
};
uint32_t NewUnary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
return result;
}
uint32_t NewBinary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs,
uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
return result;
}
uint32_t NewSelect(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
return result;
}
Pair ExtractPair(EmitterState& state, uint32_t value) {
Pair result {state.builder.AllocateId(), state.builder.AllocateId()};
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), result.low, value, 0});
@@ -45,20 +25,20 @@ uint32_t MakePair(EmitterState& state, uint32_t low, uint32_t high) {
uint32_t CompareEqual64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
bool not_equal) {
const auto compare = NewBinary(state, not_equal ? OpINotEqual : OpIEqual,
const auto compare = Binary(state, not_equal ? OpINotEqual : OpIEqual,
TypeBoolVector(state, 2), lhs_value, rhs_value);
return NewUnary(state, not_equal ? OpAny : OpAll, TypeBool(state), compare);
return Unary(state, not_equal ? OpAny : OpAll, TypeBool(state), compare);
}
uint32_t CompareOrdered64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
uint32_t high_compare, uint32_t low_compare) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto high_equal = NewBinary(state, OpIEqual, TypeBool(state), lhs.high, rhs.high);
const auto high_result = NewBinary(state, high_compare, TypeBool(state), lhs.high, rhs.high);
const auto low_result = NewBinary(state, low_compare, TypeBool(state), lhs.low, rhs.low);
const auto low_path = NewBinary(state, OpLogicalAnd, TypeBool(state), high_equal, low_result);
return NewBinary(state, OpLogicalOr, TypeBool(state), high_result, low_path);
const auto high_equal = Binary(state, OpIEqual, TypeBool(state), lhs.high, rhs.high);
const auto high_result = Binary(state, high_compare, TypeBool(state), lhs.high, rhs.high);
const auto low_result = Binary(state, low_compare, TypeBool(state), lhs.low, rhs.low);
const auto low_path = Binary(state, OpLogicalAnd, TypeBool(state), high_equal, low_result);
return Binary(state, OpLogicalOr, TypeBool(state), high_result, low_path);
}
uint32_t EmitAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
@@ -70,20 +50,20 @@ uint32_t EmitAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value)
state.builder.AddFunction({OpIAddCarry, TypeU32Pair(state), low_pair, lhs.low, rhs.low});
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), low, low_pair, 0});
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), carry, low_pair, 1});
const auto high0 = NewBinary(state, OpIAdd, TypeU32(state), lhs.high, rhs.high);
const auto high = NewBinary(state, OpIAdd, TypeU32(state), high0, carry);
const auto high0 = Binary(state, OpIAdd, TypeU32(state), lhs.high, rhs.high);
const auto high = Binary(state, OpIAdd, TypeU32(state), high0, carry);
return MakePair(state, low, high);
}
uint32_t EmitSub64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto low = NewBinary(state, OpISub, TypeU32(state), lhs.low, rhs.low);
const auto borrow = NewBinary(state, OpULessThan, TypeBool(state), lhs.low, rhs.low);
const auto low = Binary(state, OpISub, TypeU32(state), lhs.low, rhs.low);
const auto borrow = Binary(state, OpULessThan, TypeBool(state), lhs.low, rhs.low);
const auto borrow_u32 =
NewSelect(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0));
const auto high0 = NewBinary(state, OpISub, TypeU32(state), lhs.high, rhs.high);
const auto high = NewBinary(state, OpISub, TypeU32(state), high0, borrow_u32);
Select(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0));
const auto high0 = Binary(state, OpISub, TypeU32(state), lhs.high, rhs.high);
const auto high = Binary(state, OpISub, TypeU32(state), high0, borrow_u32);
return MakePair(state, low, high);
}
@@ -93,27 +73,27 @@ uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signe
uint32_t lhs_operand = lhs;
uint32_t rhs_operand = rhs;
if (signed_value) {
lhs_operand = NewUnary(state, OpBitcast, TypeI32(state), lhs);
rhs_operand = NewUnary(state, OpBitcast, TypeI32(state), rhs);
lhs_operand = Unary(state, OpBitcast, TypeI32(state), lhs);
rhs_operand = Unary(state, OpBitcast, TypeI32(state), rhs);
}
const auto extended = state.builder.AllocateId();
state.builder.AddFunction({signed_value ? OpSMulExtended : OpUMulExtended, pair_type, extended,
lhs_operand, rhs_operand});
const auto high = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, operand_type, high, extended, 1});
return signed_value ? NewUnary(state, OpBitcast, TypeU32(state), high) : high;
return signed_value ? Unary(state, OpBitcast, TypeU32(state), high) : high;
}
uint32_t EmitMul64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto low = NewBinary(state, OpIMul, TypeU32(state), lhs.low, rhs.low);
const auto low = Binary(state, OpIMul, TypeU32(state), lhs.low, rhs.low);
const auto high0 = EmitMulHigh(state, lhs.low, rhs.low, false);
const auto high1 = NewBinary(state, OpIMul, TypeU32(state), lhs.low, rhs.high);
const auto high2 = NewBinary(state, OpIMul, TypeU32(state), lhs.high, rhs.low);
const auto high1 = Binary(state, OpIMul, TypeU32(state), lhs.low, rhs.high);
const auto high2 = Binary(state, OpIMul, TypeU32(state), lhs.high, rhs.low);
return MakePair(state, low,
NewBinary(state, OpIAdd, TypeU32(state),
NewBinary(state, OpIAdd, TypeU32(state), high0, high1), high2));
Binary(state, OpIAdd, TypeU32(state),
Binary(state, OpIAdd, TypeU32(state), high0, high1), high2));
}
uint32_t EmitShift64(EmitterState& state, uint32_t opcode, uint32_t value, uint32_t shift) {
@@ -126,33 +106,33 @@ uint32_t EmitShift64(EmitterState& state, uint32_t opcode, uint32_t value, uint3
EmitShiftRightLogicalU64Values(state, pair.low, pair.high, shift, low, high);
} else {
const auto amount =
NewBinary(state, OpBitwiseAnd, TypeU32(state), shift, ConstantU32(state, 63));
Binary(state, OpBitwiseAnd, TypeU32(state), shift, ConstantU32(state, 63));
const auto word_shift =
NewBinary(state, OpBitwiseAnd, TypeU32(state), amount, ConstantU32(state, 31));
Binary(state, OpBitwiseAnd, TypeU32(state), amount, ConstantU32(state, 31));
const auto at_least_32 =
NewBinary(state, OpUGreaterThanEqual, TypeBool(state), amount, ConstantU32(state, 32));
Binary(state, OpUGreaterThanEqual, TypeBool(state), amount, ConstantU32(state, 32));
const auto nonzero =
NewBinary(state, OpINotEqual, TypeBool(state), amount, ConstantU32(state, 0));
Binary(state, OpINotEqual, TypeBool(state), amount, ConstantU32(state, 0));
const auto high_shifted =
NewBinary(state, OpShiftRightArithmetic, TypeU32(state), pair.high, word_shift);
Binary(state, OpShiftRightArithmetic, TypeU32(state), pair.high, word_shift);
const auto low_shifted =
NewBinary(state, OpShiftRightLogical, TypeU32(state), pair.low, word_shift);
Binary(state, OpShiftRightLogical, TypeU32(state), pair.low, word_shift);
const auto carry_count =
NewBinary(state, OpBitwiseAnd, TypeU32(state),
NewBinary(state, OpISub, TypeU32(state), ConstantU32(state, 32), word_shift),
Binary(state, OpBitwiseAnd, TypeU32(state),
Binary(state, OpISub, TypeU32(state), ConstantU32(state, 32), word_shift),
ConstantU32(state, 31));
const auto carry =
NewSelect(state, TypeU32(state), nonzero,
NewBinary(state, OpShiftLeftLogical, TypeU32(state), pair.high, carry_count),
Select(state, TypeU32(state), nonzero,
Binary(state, OpShiftLeftLogical, TypeU32(state), pair.high, carry_count),
ConstantU32(state, 0));
const auto low_below_32 = NewBinary(state, OpBitwiseOr, TypeU32(state), low_shifted, carry);
const auto low_below_32 = Binary(state, OpBitwiseOr, TypeU32(state), low_shifted, carry);
const auto negative =
NewBinary(state, OpSLessThan, TypeBool(state), pair.high, ConstantU32(state, 0));
const auto sign_fill = NewSelect(state, TypeU32(state), negative,
Binary(state, OpSLessThan, TypeBool(state), pair.high, ConstantU32(state, 0));
const auto sign_fill = Select(state, TypeU32(state), negative,
ConstantU32(state, 0xffffffffu), ConstantU32(state, 0));
return MakePair(state,
NewSelect(state, TypeU32(state), at_least_32, high_shifted, low_below_32),
NewSelect(state, TypeU32(state), at_least_32, sign_fill, high_shifted));
Select(state, TypeU32(state), at_least_32, high_shifted, low_below_32),
Select(state, TypeU32(state), at_least_32, sign_fill, high_shifted));
}
return MakePair(state, low, high);
}
@@ -165,36 +145,36 @@ uint32_t EmitConstantShift64(EmitterState& state, uint32_t opcode, uint32_t valu
const auto pair = ExtractPair(state, value);
if (opcode == OpShiftLeftLogical) {
if (shift < 32u) {
const auto low = NewBinary(state, OpShiftLeftLogical, TypeU32(state), pair.low,
const auto low = Binary(state, OpShiftLeftLogical, TypeU32(state), pair.low,
ConstantU32(state, shift));
const auto high = NewBinary(state, OpBitwiseOr, TypeU32(state),
NewBinary(state, OpShiftLeftLogical, TypeU32(state),
const auto high = Binary(state, OpBitwiseOr, TypeU32(state),
Binary(state, OpShiftLeftLogical, TypeU32(state),
pair.high, ConstantU32(state, shift)),
NewBinary(state, OpShiftRightLogical, TypeU32(state),
Binary(state, OpShiftRightLogical, TypeU32(state),
pair.low, ConstantU32(state, 32u - shift)));
return MakePair(state, low, high);
}
return MakePair(state, ConstantU32(state, 0),
shift == 32u ? pair.low
: NewBinary(state, OpShiftLeftLogical, TypeU32(state),
: Binary(state, OpShiftLeftLogical, TypeU32(state),
pair.low, ConstantU32(state, shift - 32u)));
}
if (shift < 32u) {
const auto low = NewBinary(state, OpBitwiseOr, TypeU32(state),
NewBinary(state, OpShiftRightLogical, TypeU32(state), pair.low,
const auto low = Binary(state, OpBitwiseOr, TypeU32(state),
Binary(state, OpShiftRightLogical, TypeU32(state), pair.low,
ConstantU32(state, shift)),
NewBinary(state, OpShiftLeftLogical, TypeU32(state), pair.high,
Binary(state, OpShiftLeftLogical, TypeU32(state), pair.high,
ConstantU32(state, 32u - shift)));
const auto high =
NewBinary(state, opcode, TypeU32(state), pair.high, ConstantU32(state, shift));
Binary(state, opcode, TypeU32(state), pair.high, ConstantU32(state, shift));
return MakePair(state, low, high);
}
const auto high = opcode == OpShiftRightArithmetic
? NewBinary(state, OpShiftRightArithmetic, TypeU32(state), pair.high,
? Binary(state, OpShiftRightArithmetic, TypeU32(state), pair.high,
ConstantU32(state, 31u))
: ConstantU32(state, 0);
const auto low = shift == 32u ? pair.high
: NewBinary(state, opcode, TypeU32(state), pair.high,
: Binary(state, opcode, TypeU32(state), pair.high,
ConstantU32(state, shift - 32u));
return MakePair(state, low, high);
}
@@ -207,12 +187,12 @@ uint32_t EmitFindMsb64(EmitterState& state, uint32_t value) {
{OpExtInst, TypeI32(state), high_i, GlslStd450(state), GlslFindUMsb, pair.high});
state.builder.AddFunction(
{OpExtInst, TypeI32(state), low_i, GlslStd450(state), GlslFindUMsb, pair.low});
const auto high = NewUnary(state, OpBitcast, TypeU32(state), high_i);
const auto low = NewUnary(state, OpBitcast, TypeU32(state), low_i);
const auto high = Unary(state, OpBitcast, TypeU32(state), high_i);
const auto low = Unary(state, OpBitcast, TypeU32(state), low_i);
const auto high_nonzero =
NewBinary(state, OpINotEqual, TypeBool(state), pair.high, ConstantU32(state, 0));
return NewSelect(state, TypeU32(state), high_nonzero,
NewBinary(state, OpIAdd, TypeU32(state), high, ConstantU32(state, 32)), low);
Binary(state, OpINotEqual, TypeBool(state), pair.high, ConstantU32(state, 0));
return Select(state, TypeU32(state), high_nonzero,
Binary(state, OpIAdd, TypeU32(state), high, ConstantU32(state, 32)), low);
}
uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value,
@@ -226,10 +206,10 @@ uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bo
uint32_t EmitMed3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value) {
const auto minimum = EmitMinMax3(state, a, b, c, signed_value, false);
const auto maximum = EmitMinMax3(state, a, b, c, signed_value, true);
const auto ab = NewBinary(state, OpIAdd, TypeU32(state), a, b);
const auto abc = NewBinary(state, OpIAdd, TypeU32(state), ab, c);
return NewBinary(state, OpISub, TypeU32(state),
NewBinary(state, OpISub, TypeU32(state), abc, minimum), maximum);
const auto ab = Binary(state, OpIAdd, TypeU32(state), a, b);
const auto abc = Binary(state, OpIAdd, TypeU32(state), ab, c);
return Binary(state, OpISub, TypeU32(state),
Binary(state, OpISub, TypeU32(state), abc, minimum), maximum);
}
uint32_t EmitFMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool max_value) {
@@ -242,11 +222,11 @@ uint32_t EmitFMed3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
const auto max_ab = EmitMinMaxF32Value(state, a, b, true);
const auto high_min = EmitMinMaxF32Value(state, max_ab, c, false);
const auto median = EmitMinMaxF32Value(state, min_ab, high_min, true);
const auto nan_ab = NewBinary(state, OpLogicalOr, TypeBool(state),
const auto nan_ab = Binary(state, OpLogicalOr, TypeBool(state),
EmitClassifyF32(state, a).nan, EmitClassifyF32(state, b).nan);
const auto any_nan =
NewBinary(state, OpLogicalOr, TypeBool(state), nan_ab, EmitClassifyF32(state, c).nan);
return NewSelect(state, TypeF32(state), any_nan, min3, median);
Binary(state, OpLogicalOr, TypeBool(state), nan_ab, EmitClassifyF32(state, c).nan);
return Select(state, TypeF32(state), any_nan, min3, median);
}
uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
@@ -270,37 +250,30 @@ uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
}
const auto nan = EmitClassifyF32(state, src).nan;
if (signed_value) {
const auto below = NewBinary(state, OpFOrdLessThanEqual, TypeBool(state), src,
const auto below = Binary(state, OpFOrdLessThanEqual, TypeBool(state), src,
ConstantF32(state, 0xcf000000u));
const auto above = NewBinary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
const auto above = Binary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
ConstantF32(state, 0x4f000000u));
const auto high =
NewSelect(state, TypeU32(state), above, ConstantU32(state, 0x7fffffffu), converted_raw);
Select(state, TypeU32(state), above, ConstantU32(state, 0x7fffffffu), converted_raw);
const auto low =
NewSelect(state, TypeU32(state), below, ConstantU32(state, 0x80000000u), high);
return NewSelect(state, TypeU32(state), nan, ConstantU32(state, 0), low);
Select(state, TypeU32(state), below, ConstantU32(state, 0x80000000u), high);
return Select(state, TypeU32(state), nan, ConstantU32(state, 0), low);
}
const auto below =
NewBinary(state, OpFOrdLessThanEqual, TypeBool(state), src, ConstantF32(state, 0));
const auto above = NewBinary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
Binary(state, OpFOrdLessThanEqual, TypeBool(state), src, ConstantF32(state, 0));
const auto above = Binary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
ConstantF32(state, 0x4f800000u));
const auto zero = NewBinary(state, OpLogicalOr, TypeBool(state), nan, below);
const auto zero = Binary(state, OpLogicalOr, TypeBool(state), nan, below);
const auto high =
NewSelect(state, TypeU32(state), above, ConstantU32(state, 0xffffffffu), converted_raw);
return NewSelect(state, TypeU32(state), zero, ConstantU32(state, 0), high);
Select(state, TypeU32(state), above, ConstantU32(state, 0xffffffffu), converted_raw);
return Select(state, TypeU32(state), zero, ConstantU32(state, 0), high);
}
uint32_t EmitPackHalf(EmitterState& state, uint32_t src) {
return EmitExt(state, TypeU32(state), GlslPackHalf2x16, {src});
}
uint32_t EmitUnpackHalf(EmitterState& state, uint32_t bits) {
const auto pair = EmitExt(state, TypeF32Vector(state, 2), GlslUnpackHalf2x16, {bits});
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, TypeF32(state), result, pair, 0});
return result;
}
} // namespace
bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -341,7 +314,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
}
case IR::ValueOpcode::ConvertF32F16:
ctx.Define(inst, EmitUnpackHalf(state, ctx.Arg(inst, 0)));
ctx.Define(inst, EmitF16BitsToF32(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::ConvertS32F32:
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), true));
@@ -350,7 +323,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), false));
return true;
case IR::ValueOpcode::ConvertF32S32: {
const auto signed_value = NewUnary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 0));
const auto signed_value = Unary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 0));
ctx.Emit(inst, OpConvertSToF, IR::Type::F32, {signed_value});
return true;
}
@@ -394,9 +367,9 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
case IR::ValueOpcode::PackFloat2x16Rtz: {
const auto low = EmitF32ToF16RtzBits(state, ctx.Arg(inst, 0));
const auto high =
NewBinary(state, OpShiftLeftLogical, TypeU32(state),
Binary(state, OpShiftLeftLogical, TypeU32(state),
EmitF32ToF16RtzBits(state, ctx.Arg(inst, 1)), ConstantU32(state, 16));
ctx.Define(inst, NewBinary(state, OpBitwiseOr, TypeU32(state), low, high));
ctx.Define(inst, Binary(state, OpBitwiseOr, TypeU32(state), low, high));
return true;
}
case IR::ValueOpcode::FPAbs32:
@@ -458,10 +431,10 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
case IR::ValueOpcode::IAbs32: {
const auto value = ctx.Arg(inst, 0);
const auto neg = NewUnary(state, OpSNegate, TypeU32(state), value);
const auto neg = Unary(state, OpSNegate, TypeU32(state), value);
const auto negative =
NewBinary(state, OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
ctx.Define(inst, NewSelect(state, TypeU32(state), negative, neg, value));
Binary(state, OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
ctx.Define(inst, Select(state, TypeU32(state), negative, neg, value));
return true;
}
case IR::ValueOpcode::ShiftLeftLogical32: return binary(OpShiftLeftLogical, IR::Type::U32);
@@ -496,25 +469,25 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
case IR::ValueOpcode::BitwiseXor32: return binary(OpBitwiseXor, IR::Type::U32);
case IR::ValueOpcode::BitwiseNot32: return unary(OpNot, IR::Type::U32);
case IR::ValueOpcode::BitwiseAnd64:
ctx.Define(inst, NewBinary(state, OpBitwiseAnd, TypeU64(state), ctx.Arg(inst, 0),
ctx.Define(inst, Binary(state, OpBitwiseAnd, TypeU64(state), ctx.Arg(inst, 0),
ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::BitReverse32: return unary(OpBitReverse, IR::Type::U32);
case IR::ValueOpcode::BitCount32: return unary(OpBitCount, IR::Type::U32);
case IR::ValueOpcode::BitCount64: {
const auto pair =
ExtractPair(state, NewUnary(state, OpBitCount, TypeU64(state), ctx.Arg(inst, 0)));
ctx.Define(inst, NewBinary(state, OpIAdd, TypeU32(state), pair.low, pair.high));
ExtractPair(state, Unary(state, OpBitCount, TypeU64(state), ctx.Arg(inst, 0)));
ctx.Define(inst, Binary(state, OpIAdd, TypeU32(state), pair.low, pair.high));
return true;
}
case IR::ValueOpcode::FindILsb32: {
const auto value = EmitExt(state, TypeI32(state), GlslFindILsb, {ctx.Arg(inst, 0)});
ctx.Define(inst, NewUnary(state, OpBitcast, TypeU32(state), value));
ctx.Define(inst, Unary(state, OpBitcast, TypeU32(state), value));
return true;
}
case IR::ValueOpcode::FindUMsb32: {
const auto value = EmitExt(state, TypeI32(state), GlslFindUMsb, {ctx.Arg(inst, 0)});
ctx.Define(inst, NewUnary(state, OpBitcast, TypeU32(state), value));
ctx.Define(inst, Unary(state, OpBitcast, TypeU32(state), value));
return true;
}
case IR::ValueOpcode::FindUMsb64:
@@ -638,13 +611,13 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
case IR::ValueOpcode::FPRecip32: {
const auto source = EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0));
ctx.Define(inst, NewBinary(state, OpFDiv, TypeF32(state),
ctx.Define(inst, Binary(state, OpFDiv, TypeF32(state),
ConstantF32(state, 0x3f800000u), source));
return true;
}
case IR::ValueOpcode::FPRecipIFlag32:
// Integer-to-float inputs used by IFLAG cannot be denormal.
ctx.Define(inst, NewBinary(state, OpFDiv, TypeF32(state),
ctx.Define(inst, Binary(state, OpFDiv, TypeF32(state),
ConstantF32(state, 0x3f800000u), ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FPRecipSqrt32:
@@ -659,7 +632,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
case IR::ValueOpcode::FPCos: {
auto source = EmitTrigCycleF32(state, ctx.Arg(inst, 0), op == IR::ValueOpcode::FPSin);
source =
NewBinary(state, OpFMul, TypeF32(state), source, ConstantF32(state, 0x40c90fdbu));
Binary(state, OpFMul, TypeF32(state), source, ConstantF32(state, 0x40c90fdbu));
ctx.Define(inst, EmitExt(state, TypeF32(state),
op == IR::ValueOpcode::FPSin ? GlslSin : GlslCos, {source}));
return true;
@@ -673,7 +646,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPLdexp: {
const auto exponent = NewUnary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 1));
const auto exponent = Unary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 1));
ctx.Define(inst,
EmitExt(state, TypeF32(state), GlslLdexp, {ctx.Arg(inst, 0), exponent}));
return true;
@@ -215,9 +215,9 @@ uint32_t EmitLogicalNotBool(EmitterState& state, uint32_t value) {
return ret;
}
F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits) {
F32Class cls;
cls.bits = EmitBitcastF32ToU32(state, value);
cls.bits = bits;
const auto abs_bits = EmitAndConstant(state, cls.bits, 0x7fffffffu);
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
@@ -228,6 +228,10 @@ F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
return cls;
}
F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
return EmitClassifyF32Bits(state, EmitBitcastF32ToU32(state, value));
}
uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
uint32_t class_match) {
const auto selected =
@@ -6,13 +6,6 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr) {
if (state.stage != ShaderType::Pixel) {
return attr;
}
return ShaderPixelParameterMappedLocation(*state.input_info.pixel, attr);
}
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) {
std::array<uint32_t, 32> active_inputs {};
uint32_t active_count = 0;
@@ -2,10 +2,6 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitTrueBool(EmitterState& state) {
return ConstantBool(state, true);
}
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
const auto quad_base = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
@@ -23,7 +19,7 @@ DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uin
state.builder.AddFunction(
{OpBitwiseAnd, TypeU32(state), selected, selected0, ConstantU32(state, 3)});
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), target, quad_base, selected});
return {target, EmitTrueBool(state)};
return {target, ConstantBool(state, true)};
}
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
@@ -70,7 +66,7 @@ DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subi
{OpIAdd, TypeU32(state), plus, lane, ConstantU32(state, 16u - amount)});
state.builder.AddFunction({OpSelect, TypeU32(state), selected, in_high, minus, plus});
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), target, row, selected});
return {target, EmitTrueBool(state)};
return {target, ConstantBool(state, true)};
}
DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row) {
@@ -87,7 +83,7 @@ DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool
state.builder.AddFunction(
{OpISub, TypeU32(state), mirrored, ConstantU32(state, lane_mask), lane});
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), target, base, mirrored});
return {target, EmitTrueBool(state)};
return {target, ConstantBool(state, true)};
}
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
@@ -114,9 +110,9 @@ DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)});
return {target, EmitTrueBool(state)};
return {target, ConstantBool(state, true)};
}
return {subid, EmitTrueBool(state)};
return {subid, ConstantBool(state, true)};
}
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state) {
@@ -184,7 +180,7 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
if (count == 1u) {
state.builder.AddFunction({OpLoad, scalar_type, raw, input.variable_id});
} else {
const auto pointer_type = VertexParameterScalarPointerType(state, kind);
const auto pointer_type = TypePointer(state, StorageClassInput, scalar_type);
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, pointer_type, pointer, input.variable_id,
ConstantU32(state, component)});
@@ -203,14 +199,14 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane) {
const auto active_ballot = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformBallot, TypeU32Vector(state, 4), active_ballot,
ConstantU32(state, ScopeSubgroup), EmitTrueBool(state)});
ConstantU32(state, ScopeSubgroup), ConstantBool(state, true)});
return EmitBallotLaneActiveBool(state, active_ballot, lane);
}
uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t active_ballot, uint32_t lane) {
const auto low = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), low, active_ballot, 0});
uint32_t mask = low;
if (state.wave_size == 64u) {
if (state.program.wave_size == 64u) {
const auto high = state.builder.AllocateId();
const auto in_high = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
@@ -235,7 +231,7 @@ uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t active_ballot, u
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), hit, mask, bit});
state.builder.AddFunction({OpINotEqual, TypeBool(state), active, hit, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpULessThan, TypeBool(state), in_range, lane, ConstantU32(state, state.wave_size)});
{OpULessThan, TypeBool(state), in_range, lane, ConstantU32(state, state.program.wave_size)});
state.builder.AddFunction({OpLogicalAnd, TypeBool(state), ret, active, in_range});
return ret;
}
@@ -32,18 +32,6 @@ uint32_t ImageGatherComponent(uint32_t dmask) {
}
}
uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
return result;
}
uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs, uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
return result;
}
bool HasFlag(const IR::MemoryInfo& mem, uint32_t flag) {
return (mem.image_sample_flags & flag) != 0u;
}
@@ -768,8 +756,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
}
const auto* handle = image_arg.ResolveInstruction();
const auto* source = image.source < ctx.program.descriptor_sources.size()
? &ctx.program.descriptor_sources[image.source]
const auto* source = image.source < state.program.descriptor_sources.size()
? &state.program.descriptor_sources[image.source]
: nullptr;
if (handle == nullptr || source == nullptr || !source->indirect_image.has_value() ||
source->indirect_image->key_arg >= handle->NumArgs()) {
@@ -360,7 +360,6 @@ struct EmitterState {
ShaderStageInputInfo input_info;
const IR::SpirvRequirements& requirements;
ShaderType stage = ShaderType::Unknown;
uint32_t wave_size = 64;
uint32_t lane_count = 1;
uint32_t lane_half = 0;
uint32_t storage_buffer_variable = 0;
@@ -386,6 +385,7 @@ struct EmitterState {
uint32_t mesh_cull = 0;
uint32_t entry_label = 0;
uint32_t current_label = 0;
const IR::Block* current_block = nullptr;
uint32_t pixel_valid_mask_variable = 0;
uint32_t subgroup_local_invocation_id_variable = 0;
uint32_t per_vertex_variable = 0;
@@ -401,6 +401,7 @@ struct EmitterState {
std::vector<InputBinding> inputs;
std::vector<OutputBinding> outputs;
std::vector<uint32_t> interface_variables;
std::unordered_map<const IR::Block*, uint32_t> labels;
};
uint32_t TypeVoid(EmitterState& state);
@@ -436,9 +437,28 @@ inline void EmitLabel(EmitterState& state, uint32_t label) {
state.builder.AddFunction({OpLabel, label});
}
inline uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
return result;
}
inline uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs,
uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
return result;
}
inline uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition,
uint32_t true_value, uint32_t false_value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
return result;
}
struct ValueEmitContext {
ValueEmitContext(EmitterState& state_, const IR::Program& program_)
: state(state_), program(program_) {}
explicit ValueEmitContext(EmitterState& state_): state(state_) {}
uint32_t Def(IR::Value value);
uint32_t Arg(const IR::Inst& inst, size_t index);
@@ -460,12 +480,9 @@ struct ValueEmitContext {
[[noreturn]] void Fail(const IR::Inst& inst, const char* reason) const;
EmitterState& state;
const IR::Program& program;
std::unordered_map<const IR::Inst*, uint32_t> definitions;
std::unordered_map<const IR::Block*, uint32_t> labels;
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
std::unordered_map<const IR::Inst*, std::pair<uint32_t, uint32_t>> dispatcher_block_loads;
const IR::Block* current_block = nullptr;
uint32_t scratch_u32_variable = 0;
ValueEmitContext* other_half = nullptr;
uint32_t half = 0;
@@ -496,8 +513,6 @@ struct F32Class {
uint32_t zero = 0;
};
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr);
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr);
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr);
@@ -510,11 +525,6 @@ uint32_t VertexParameterComponentCount(const InputBinding& input);
uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind);
uint32_t VertexParameterScalarPointerType(EmitterState& state, VertexInputScalarKind kind);
uint32_t VertexParameterVectorOrScalarType(EmitterState& state, VertexInputScalarKind kind,
uint32_t components);
uint32_t VertexParameterInputPointerType(EmitterState& state, VertexInputScalarKind kind,
uint32_t components);
@@ -602,8 +612,6 @@ void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index = 0);
uint32_t MeshPrimitivePointer(EmitterState& state);
uint32_t EmitTrueBool(EmitterState& state);
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
@@ -675,22 +683,13 @@ uint32_t EmitStorageBufferElementPointer(EmitterState& state,
const MemoryResourceAccess& access, uint32_t index,
uint32_t pointer_type);
uint32_t EmitTBufferBitcastF32ToU32(EmitterState& state, uint32_t value);
uint32_t EmitTBufferBitcastU32ToF32(EmitterState& state, uint32_t value);
uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value);
uint32_t EmitTBufferCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
uint32_t constant);
uint32_t EmitTBufferSelectF32(EmitterState& state, uint32_t condition, uint32_t true_value,
uint32_t false_value);
bool IsSignedFormatComponent(Format::ComponentType type);
uint32_t EmitHalfToF32Bits(EmitterState& state, uint32_t raw);
uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits);
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
@@ -741,6 +740,8 @@ uint32_t EmitLogicalOrBool(EmitterState& state, uint32_t lhs, uint32_t rhs);
uint32_t EmitLogicalNotBool(EmitterState& state, uint32_t value);
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits);
F32Class EmitClassifyF32(EmitterState& state, uint32_t value);
uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
@@ -770,7 +771,7 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst);
bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitProgram(EmitterState& state, const IR::Program& program);
void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state);
@@ -7,25 +7,6 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
return result;
}
uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs, uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
return result;
}
uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
return result;
}
uint32_t AndCondition(EmitterState& state, uint32_t lhs, uint32_t rhs) {
return Binary(state, OpLogicalAnd, TypeBool(state), lhs, rhs);
}
@@ -290,12 +271,6 @@ PreparedMemoryElement PrepareMemoryElement(ValueEmitContext& ctx, const IR::Memo
return {.resource = resource, .index = index};
}
PreparedMemoryElement PrepareMemoryElement(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource,
uint32_t raw_index) {
return {.resource = resource, .index = EmitMemoryElementIndex(ctx.state, resource, raw_index)};
}
uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource,
uint32_t index);
@@ -304,10 +279,10 @@ uint32_t LoadSubwordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess&
uint32_t LoadWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource) {
const auto access = PrepareMemoryElement(ctx, mem, resource, DwordIndex(ctx, inst, mem));
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index),
[&]() { return LoadWordInBounds(ctx, access.resource, access.index); });
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index),
[&]() { return LoadWordInBounds(ctx, resource, index); });
}
uint32_t LoadWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
@@ -323,11 +298,10 @@ uint32_t LoadSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const
const auto address = ByteAddress(ctx, inst, mem);
const auto raw_index = Binary(ctx.state, OpShiftRightLogical, TypeU32(ctx.state), address,
ConstantU32(ctx.state, 2));
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
const auto index = EmitMemoryElementIndex(ctx.state, resource, raw_index);
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
return LoadSubwordInBounds(ctx, access.resource, address, access.index, bits,
sign_extend);
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
return LoadSubwordInBounds(ctx, resource, address, index, bits, sign_extend);
});
}
@@ -365,15 +339,14 @@ uint32_t LoadSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo
});
}
Prospero::BufferFormat BufferFormat(const ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem) {
Prospero::BufferFormat BufferFormat(const ValueEmitContext& ctx, const IR::MemoryInfo& mem) {
return mem.typed ? Format::DecodeTBufferFormat(mem.data_format, mem.number_format)
: StorageBufferFormat(ctx.state, mem);
}
IR::MemoryInfo RebaseFormattedComponent(IR::MemoryInfo mem, Prospero::BufferFormat format,
IR::MemoryInfo RebaseFormattedComponent(IR::MemoryInfo mem, const Format::BufferFormatInfo& info,
uint32_t component) {
mem.offset += Format::GetFormatComponentByteOffset(format, component);
mem.offset += Format::GetFormatComponentByteOffset(info, component);
mem.data_dwords = 1u;
mem.component_index = component;
return mem;
@@ -414,7 +387,7 @@ uint32_t FormattedConstant(ValueEmitContext& ctx, const Format::BufferFormatInfo
template <typename LoadWordFn, typename LoadSubwordFn>
uint32_t LoadFormattedComponent(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
Prospero::BufferFormat format, const Format::BufferFormatInfo& info,
const Format::BufferFormatInfo& info,
uint32_t output_component, LoadWordFn&& load_word,
LoadSubwordFn&& load_subword) {
const auto source = ResolveFormattedSource(ctx, mem, info, output_component);
@@ -449,19 +422,18 @@ uint32_t LoadFormattedComponent(ValueEmitContext& ctx, const IR::MemoryInfo& mem
uint32_t FormattedLoadPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem, uint32_t output_component,
const MemoryResourceAccess& resource) {
const auto format = BufferFormat(ctx, inst, mem);
if (!Format::IsKnownFormat(format)) {
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
if (info.type == Format::ComponentType::Unknown) {
return LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, output_component), resource);
}
const auto info = Format::GetFormatInfo(format);
return LoadFormattedComponent(
ctx, mem, format, info, output_component,
ctx, mem, info, output_component,
[&](uint32_t component) {
return LoadWordPrepared(ctx, inst, RebaseFormattedComponent(mem, format, component),
return LoadWordPrepared(ctx, inst, RebaseFormattedComponent(mem, info, component),
resource);
},
[&](uint32_t component, uint32_t bits, bool sign_extend) {
return LoadSubwordPrepared(ctx, inst, RebaseFormattedComponent(mem, format, component),
return LoadSubwordPrepared(ctx, inst, RebaseFormattedComponent(mem, info, component),
resource, bits, sign_extend);
});
}
@@ -507,10 +479,10 @@ void StoreSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR:
const auto address = ByteAddress(ctx, inst, mem);
const auto raw_index = Binary(ctx.state, OpShiftRightLogical, TypeU32(ctx.state), address,
ConstantU32(ctx.state, 2));
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
const auto index = EmitMemoryElementIndex(ctx.state, resource, raw_index);
EmitIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
StoreSubwordInBounds(ctx, mem, access.resource, address, access.index, bits, data);
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
StoreSubwordInBounds(ctx, mem, resource, address, index, bits, data);
});
}
@@ -523,12 +495,11 @@ void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo me
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource, uint32_t data) {
const auto access = PrepareMemoryElement(ctx, mem, resource, DwordIndex(ctx, inst, mem));
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
EmitIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
ctx.state.builder.AddFunction(
{OpStore, EmitMemoryElementPointer(ctx.state, access.resource, access.index),
data});
{OpStore, EmitMemoryElementPointer(ctx.state, resource, index), data});
});
}
@@ -548,15 +519,14 @@ void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem)
void FormattedStorePrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t component, const MemoryResourceAccess& resource,
uint32_t data) {
const auto format = BufferFormat(ctx, inst, mem);
if (!Format::IsKnownFormat(format)) {
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
if (info.type == Format::ComponentType::Unknown) {
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data);
return;
}
const auto info = Format::GetFormatInfo(format);
if (component >= info.component_count) return;
const auto bits = info.component_bits[component];
const auto component_mem = RebaseFormattedComponent(mem, format, component);
const auto component_mem = RebaseFormattedComponent(mem, info, component);
if (bits == 8u || bits == 16u) {
StoreSubwordPrepared(ctx, inst, component_mem, resource, bits, data);
} else {
@@ -619,31 +589,58 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
return old;
}
template <typename Fn>
uint32_t EmitAtomicAccess(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem, Fn&& operation) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
return operation(EmitMemoryElementPointer(ctx.state, access.resource, access.index));
});
});
}
uint32_t EmitAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
const auto result =
EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index),
[&]() {
const auto scope =
mem.kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
const auto pointer =
EmitMemoryElementPointer(ctx.state, access.resource, access.index);
const auto old = EmitAtomicOperation(ctx, inst, pointer, scope);
if (mem.kind == IR::ResourceKind::Lds) {
const auto semantics =
MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
ctx.state.builder.AddFunction({OpMemoryBarrier,
ConstantU32(ctx.state, ScopeWorkgroup),
ConstantU32(ctx.state, semantics)});
} else {
EmitDeviceAtomicMemoryBarrier(ctx.state);
}
return old;
});
});
return result;
return EmitAtomicAccess(ctx, inst, mem, [&](uint32_t pointer) {
const auto scope = mem.kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
const auto old = EmitAtomicOperation(ctx, inst, pointer, scope);
if (mem.kind == IR::ResourceKind::Lds) {
const auto semantics = MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
ctx.state.builder.AddFunction({OpMemoryBarrier, ConstantU32(ctx.state, scope),
ConstantU32(ctx.state, semantics)});
} else {
EmitDeviceAtomicMemoryBarrier(ctx.state);
}
return old;
});
}
template <typename Fn>
uint32_t EmitAtomicUpdate(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem, Fn&& replacement) {
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
return EmitAtomicAccess(ctx, inst, mem, [&](uint32_t pointer) {
return AtomicUpdate(ctx.state, pointer, mem.kind, [&](uint32_t old) {
return replacement(ctx.state, old, value);
});
});
}
uint32_t AtomicIncrement(EmitterState& state, uint32_t old, uint32_t limit) {
// old >= limit ? 0 : old + 1 (unsigned).
const auto wrap = Binary(state, OpUGreaterThanEqual, TypeBool(state), old, limit);
const auto next = Binary(state, OpIAdd, TypeU32(state), old, ConstantU32(state, 1));
return Select(state, TypeU32(state), wrap, ConstantU32(state, 0), next);
}
uint32_t AtomicDecrement(EmitterState& state, uint32_t old, uint32_t limit) {
// old == 0 || old > limit ? limit : old - 1 (unsigned).
const auto zero = Binary(state, OpIEqual, TypeBool(state), old, ConstantU32(state, 0));
const auto above = Binary(state, OpUGreaterThan, TypeBool(state), old, limit);
const auto wrap = Binary(state, OpLogicalOr, TypeBool(state), zero, above);
const auto next = Binary(state, OpISub, TypeU32(state), old, ConstantU32(state, 1));
return Select(state, TypeU32(state), wrap, limit, next);
}
uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst,
@@ -677,17 +674,9 @@ uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst,
uint32_t FloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
bool max_value) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
const auto pointer =
EmitMemoryElementPointer(ctx.state, access.resource, access.index);
const auto source = ctx.Arg(inst, inst.NumArgs() - 2);
return AtomicUpdate(ctx.state, pointer, mem.kind, [&](uint32_t old) {
return EmitFloatAtomicReplacement(ctx.state, old, source, max_value);
});
});
return EmitAtomicUpdate(ctx, inst, mem, [max_value](EmitterState& state, uint32_t old,
uint32_t value) {
return EmitFloatAtomicReplacement(state, old, value, max_value);
});
}
@@ -781,7 +770,6 @@ uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool append)
}
struct PreparedFormattedMemory {
Prospero::BufferFormat format = Prospero::BufferFormat::kInvalid;
Format::BufferFormatInfo info;
MemoryResourceAccess resource;
std::array<uint32_t, 4> addresses {};
@@ -794,10 +782,10 @@ enum class FormattedAccess { Load, Store };
PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource,
const Format::BufferFormatInfo& info,
uint32_t components, FormattedAccess access) {
PreparedFormattedMemory plan;
plan.format = BufferFormat(ctx, inst, mem);
plan.info = Format::GetFormatInfo(plan.format);
plan.info = info;
plan.resource = resource;
std::array<bool, 4> required_components {};
if (access == FormattedAccess::Load) {
@@ -816,11 +804,11 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
bool first_bound = true;
for (uint32_t component = 0; component < plan.info.component_count; component++) {
if (!required_components[component]) continue;
const auto byte_offset = Format::GetFormatComponentByteOffset(plan.format, component);
const auto byte_offset = Format::GetFormatComponentByteOffset(plan.info, component);
bool reused = false;
for (uint32_t previous = 0; previous < component; previous++) {
if (required_components[previous] &&
Format::GetFormatComponentByteOffset(plan.format, previous) == byte_offset) {
Format::GetFormatComponentByteOffset(plan.info, previous) == byte_offset) {
plan.addresses[component] = plan.addresses[previous];
plan.indices[component] = plan.indices[previous];
reused = true;
@@ -828,7 +816,7 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
}
}
if (reused) continue;
const auto component_mem = RebaseFormattedComponent(mem, plan.format, component);
const auto component_mem = RebaseFormattedComponent(mem, plan.info, component);
plan.addresses[component] = ByteAddress(ctx, inst, component_mem);
const auto raw_index = Binary(ctx.state, OpShiftRightLogical, TypeU32(ctx.state),
plan.addresses[component], ConstantU32(ctx.state, 2));
@@ -849,7 +837,7 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
uint32_t LoadFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
const PreparedFormattedMemory& plan, uint32_t output_component) {
return LoadFormattedComponent(
ctx, mem, plan.format, plan.info, output_component,
ctx, mem, plan.info, output_component,
[&](uint32_t component) {
return LoadWordInBounds(ctx, plan.resource, plan.indices[component]);
},
@@ -898,10 +886,10 @@ uint32_t LoadWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t co
ConstantU32CompositeZero(state, components), [&]() {
const auto mem = ctx.Memory(inst);
const auto resource = PrepareMemoryResourceAccess(state, mem);
const auto format =
mem.formatted ? BufferFormat(ctx, inst, mem) : Prospero::BufferFormat::kInvalid;
if (Format::IsKnownFormat(format)) {
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, components,
const auto info = Format::GetFormatInfo(
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
if (info.type != Format::ComponentType::Unknown) {
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, components,
FormattedAccess::Load);
return EmitValueOrDefaultIfCondition(
state, plan.in_bounds, TypeU32Composite(state, components),
@@ -928,10 +916,10 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
const auto mem = ctx.Memory(inst);
const auto resource = PrepareMemoryResourceAccess(state, mem);
const auto composite = ctx.Arg(inst, inst.NumArgs() - 2);
const auto format =
mem.formatted ? BufferFormat(ctx, inst, mem) : Prospero::BufferFormat::kInvalid;
if (Format::IsKnownFormat(format)) {
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, components,
const auto info = Format::GetFormatInfo(
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
if (info.type != Format::ComponentType::Unknown) {
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, components,
FormattedAccess::Store);
EmitIfCondition(state, plan.in_bounds, [&]() {
for (uint32_t component = 0; component < components; component++) {
@@ -967,10 +955,10 @@ uint32_t LoadWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t co
ConstantU32(state, component * 4u));
const auto raw_index = Binary(state, OpShiftRightLogical, TypeU32(state), address,
ConstantU32(state, 2));
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
const auto index = EmitMemoryElementIndex(state, resource, raw_index);
values[component] = EmitValueOrZeroIfCondition(
state, EmitMemoryElementInBounds(state, access.resource, access.index),
[&]() { return LoadWordInBounds(ctx, access.resource, access.index); });
state, EmitMemoryElementInBounds(state, resource, index),
[&]() { return LoadWordInBounds(ctx, resource, index); });
}
return ConstructU32Composite(state, components, values);
});
@@ -988,11 +976,10 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
ConstantU32(state, component * 4u));
const auto raw_index =
Binary(state, OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
EmitIfCondition(state, EmitMemoryElementInBounds(state, access.resource, access.index),
const auto index = EmitMemoryElementIndex(state, resource, raw_index);
EmitIfCondition(state, EmitMemoryElementInBounds(state, resource, index),
[&]() {
StoreWordInBounds(ctx, access.resource, access.index,
ctx.Arg(inst, component + 1u));
StoreWordInBounds(ctx, resource, index, ctx.Arg(inst, component + 1u));
});
}
});
@@ -1170,6 +1157,12 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
op == IR::ValueOpcode::BufferAtomicFMax32));
return true;
}
if (op == IR::ValueOpcode::SharedAtomicInc32 || op == IR::ValueOpcode::SharedAtomicDec32) {
const auto replacement = op == IR::ValueOpcode::SharedAtomicInc32
? AtomicIncrement : AtomicDecrement;
ctx.Define(inst, EmitAtomicUpdate(ctx, inst, ctx.Memory(inst), replacement));
return true;
}
if (op == IR::ValueOpcode::SharedAtomicFMin32 || op == IR::ValueOpcode::SharedAtomicFMax32) {
SharedFloatAtomic(ctx, inst, ctx.Memory(inst), op == IR::ValueOpcode::SharedAtomicFMax32);
return true;
@@ -188,31 +188,12 @@ uint32_t EmitStorageBufferElementPointer(EmitterState& state,
return pointer;
}
uint32_t EmitTBufferBitcastF32ToU32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, TypeU32(state), ret, value});
return ret;
}
uint32_t EmitTBufferBitcastU32ToF32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, TypeF32(state), ret, value});
return ret;
}
uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, TypeI32(state), ret, value});
return ret;
}
uint32_t EmitTBufferCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
uint32_t constant) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({opcode, TypeBool(state), ret, value, ConstantU32(state, constant)});
return ret;
}
uint32_t EmitTBufferSelectF32(EmitterState& state, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto ret = state.builder.AllocateId();
@@ -225,15 +206,6 @@ bool IsSignedFormatComponent(Format::ComponentType type) {
type == Format::ComponentType::Sscaled;
}
uint32_t EmitHalfToF32Bits(EmitterState& state, uint32_t raw) {
const auto unpacked = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, TypeF32Vector(state, 2), unpacked, GlslStd450(state), GlslUnpackHalf2x16, raw});
state.builder.AddFunction({OpCompositeExtract, TypeF32(state), value, unpacked, 0});
return EmitTBufferBitcastF32ToU32(state, value);
}
uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
const auto mantissa_bits = bits == 11u ? 6u : 5u;
const auto mantissa_mask = (1u << mantissa_bits) - 1u;
@@ -250,11 +222,11 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
const auto mantissa_bits_32 =
EmitBinaryU32(state, OpShiftLeftLogical, mantissa, ConstantU32(state, 23u - mantissa_bits));
const auto normal_bits = EmitBinaryU32(state, OpBitwiseOr, exponent_bits, mantissa_bits_32);
const auto normal = EmitTBufferBitcastU32ToF32(state, normal_bits);
const auto normal = EmitBitcastU32ToF32(state, normal_bits);
const auto special_bits =
EmitBinaryU32(state, OpBitwiseOr, ConstantU32(state, 0x7f800000u), mantissa_bits_32);
const auto special = EmitTBufferBitcastU32ToF32(state, special_bits);
const auto special = EmitBitcastU32ToF32(state, special_bits);
const auto mantissa_f32 = state.builder.AllocateId();
const auto subnormal = state.builder.AllocateId();
@@ -263,11 +235,11 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
{OpFMul, TypeF32(state), subnormal, mantissa_f32,
ConstantF32Value(state, std::ldexp(1.0f, 1 - 15 - static_cast<int>(mantissa_bits)))});
const auto zero_exp = EmitTBufferCompareU32Constant(state, OpIEqual, exponent, 0);
const auto special_exp = EmitTBufferCompareU32Constant(state, OpIEqual, exponent, 31);
const auto zero_exp = EmitCompareU32Constant(state, OpIEqual, exponent, 0);
const auto special_exp = EmitCompareU32Constant(state, OpIEqual, exponent, 31);
const auto finite = EmitTBufferSelectF32(state, zero_exp, subnormal, normal);
const auto result = EmitTBufferSelectF32(state, special_exp, special, finite);
return EmitTBufferBitcastF32ToU32(state, result);
return EmitBitcastF32ToU32(state, result);
}
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
@@ -279,13 +251,13 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
case Format::ComponentType::Uscaled: {
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpConvertUToF, TypeF32(state), value, raw});
return EmitTBufferBitcastF32ToU32(state, value);
return EmitBitcastF32ToU32(state, value);
}
case Format::ComponentType::Sscaled: {
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpConvertSToF, TypeF32(state), value, signed_raw});
return EmitTBufferBitcastF32ToU32(state, value);
return EmitBitcastF32ToU32(state, value);
}
case Format::ComponentType::Unorm: {
const auto value = state.builder.AllocateId();
@@ -294,7 +266,7 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
state.builder.AddFunction({OpConvertUToF, TypeF32(state), value, raw});
state.builder.AddFunction(
{OpFDiv, TypeF32(state), normalized, value, ConstantF32Value(state, max_value)});
return EmitTBufferBitcastF32ToU32(state, normalized);
return EmitBitcastF32ToU32(state, normalized);
}
case Format::ComponentType::Snorm: {
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
@@ -307,14 +279,14 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
{OpFDiv, TypeF32(state), normalized, value, ConstantF32Value(state, max_value)});
state.builder.AddFunction({OpExtInst, TypeF32(state), clamped, GlslStd450(state),
GlslFMax, normalized, ConstantF32Value(state, -1.0f)});
return EmitTBufferBitcastF32ToU32(state, clamped);
return EmitBitcastF32ToU32(state, clamped);
}
case Format::ComponentType::Float:
if (bits == 32u) {
return raw;
}
if (bits == 16u) {
return EmitHalfToF32Bits(state, raw);
return EmitBitcastF32ToU32(state, EmitF16BitsToF32(state, raw));
}
return EmitUFloatToF32Bits(state, raw, bits);
default: return raw;
@@ -335,8 +307,7 @@ uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t
uint32_t key;
};
const auto classify = [&](uint32_t bits) {
const auto value = EmitBitcastU32ToF32(state, bits);
const auto cls = EmitClassifyF32(state, value);
const auto cls = EmitClassifyF32Bits(state, bits);
const auto negative = EmitCompareU32Constant(
state, OpINotEqual, EmitAndConstant(state, bits, 0x80000000u), 0u);
const auto negative_key = state.builder.AllocateId();
@@ -349,45 +349,6 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
}
}
uint32_t VertexParameterScalarPointerType(EmitterState& state, VertexInputScalarKind kind) {
switch (kind) {
case VertexInputScalarKind::Sint:
return TypePointer(state, StorageClassInput, TypeI32(state));
case VertexInputScalarKind::Uint:
return TypePointer(state, StorageClassInput, TypeU32(state));
case VertexInputScalarKind::Float:
default: return TypePointer(state, StorageClassInput, TypeF32(state));
}
}
uint32_t VertexParameterVectorOrScalarType(EmitterState& state, VertexInputScalarKind kind,
uint32_t components) {
switch (kind) {
case VertexInputScalarKind::Sint:
switch (components) {
case 1: return TypeI32(state);
case 2: return TypeI32Vector(state, 2);
case 3: return TypeI32Vector(state, 3);
default: return TypeI32Vector(state, 4);
}
case VertexInputScalarKind::Uint:
switch (components) {
case 1: return TypeU32(state);
case 2: return TypeU32Vector(state, 2);
case 3: return TypeU32Vector(state, 3);
default: return TypeU32Vector(state, 4);
}
case VertexInputScalarKind::Float:
default:
switch (components) {
case 1: return TypeF32(state);
case 2: return TypeF32Vector(state, 2);
case 3: return TypeF32Vector(state, 3);
default: return TypeF32Vector(state, 4);
}
}
}
uint32_t VertexParameterInputPointerType(EmitterState& state, VertexInputScalarKind kind,
uint32_t components) {
switch (kind) {
@@ -96,7 +96,9 @@ void EmitReturn(ValueEmitContext& ctx) {
}
uint32_t BranchCondition(ValueEmitContext& ctx, const IR::BlockInfo& info) {
// Scalar-instruction conditions already test the full wave's raw register values.
if (ctx.other_half == nullptr ||
info.terminator.condition == CFG::BranchCondition::ScalarInstruction ||
info.terminator.condition == CFG::BranchCondition::GotoVariable) {
return ctx.Def(info.condition);
}
@@ -118,18 +120,19 @@ uint32_t BranchCondition(ValueEmitContext& ctx, const IR::BlockInfo& info) {
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
const IR::BlockInfo& info) {
const auto& program = ctx.state.program;
const auto& term = info.terminator;
const auto emit_merge = [&]() {
if (term.loop_header) {
const auto* merge = TargetBlock(ctx.program, term.merge_block);
const auto* cont = TargetBlock(ctx.program, term.continue_block);
const auto* merge = TargetBlock(program, term.merge_block);
const auto* cont = TargetBlock(program, term.continue_block);
if (merge != nullptr && cont != nullptr) {
ctx.state.builder.AddFunction(
{OpLoopMerge, ctx.Label(merge), ctx.Label(cont), LoopControlNone});
}
} else if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
term.merge_block != UINT32_MAX) {
if (const auto* merge = TargetBlock(ctx.program, term.merge_block); merge != nullptr) {
if (const auto* merge = TargetBlock(program, term.merge_block); merge != nullptr) {
ctx.state.builder.AddFunction(
{OpSelectionMerge, ctx.Label(merge), SelectionControlNone});
}
@@ -138,7 +141,7 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
switch (term.kind) {
case CFG::TerminatorKind::Branch: {
const auto* target = TargetBlock(ctx.program, term.true_block);
const auto* target = TargetBlock(program, term.true_block);
if (target == nullptr) {
EmitReturn(ctx);
return;
@@ -148,8 +151,8 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
return;
}
case CFG::TerminatorKind::ConditionalBranch: {
const auto* true_block = TargetBlock(ctx.program, term.true_block);
const auto* false_block = TargetBlock(ctx.program, term.false_block);
const auto* true_block = TargetBlock(program, term.true_block);
const auto* false_block = TargetBlock(program, term.false_block);
if (true_block == nullptr || false_block == nullptr || info.condition.IsEmpty()) {
EmitReturn(ctx);
return;
@@ -166,7 +169,7 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
const IR::Block* from, uint32_t target) {
const auto* block = TargetBlock(ctx.program, target);
const auto* block = TargetBlock(ctx.state.program, target);
if (block != nullptr) {
StoreDispatcherPhiEdge(ctx, dispatcher, from, block);
if (ctx.other_half != nullptr) {
@@ -239,7 +242,7 @@ void EmitStructuredInstruction(ValueEmitContext& ctx, StructuredFunctionState& s
}
for (size_t index = 0; index < inst.NumArgs(); index++) {
const auto* predecessor = inst.PhiBlock(index);
if (predecessor == nullptr || !ctx.labels.contains(predecessor)) {
if (predecessor == nullptr || !ctx.state.labels.contains(predecessor)) {
ctx.Fail(inst, "has a predecessor outside the structured function");
}
}
@@ -272,10 +275,7 @@ void EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionSt
template <typename EmitInstruction>
void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&& emit_instruction) {
ctx.current_block = block;
if (ctx.other_half != nullptr) {
ctx.other_half->current_block = block;
}
ctx.state.current_block = block;
EmitLabel(ctx.state, ctx.Label(block));
bool emitted_non_phi = false;
for (const auto& inst: *block) {
@@ -288,7 +288,6 @@ void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&&
}
for (uint32_t half = 0; half < ctx.state.lane_count; half++) {
auto& lane = half == 0 ? ctx : *ctx.other_half;
lane.current_block = block;
ctx.state.lane_half = half;
if (half == 0 || (inst.GetOpcode() != IR::ValueOpcode::Barrier &&
inst.GetOpcode() != IR::ValueOpcode::MeshAllocate)) {
@@ -315,28 +314,29 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu
}
void EmitStructuredFunction(ValueEmitContext& ctx) {
const auto& program = ctx.state.program;
StructuredFunctionState structured;
ctx.state.builder.AddFunction({OpBranch, ctx.Label(ctx.program.blocks.front())});
for (size_t index = 0; index < ctx.program.blocks.size(); index++) {
const auto* block = ctx.program.blocks[index];
ctx.state.builder.AddFunction({OpBranch, ctx.Label(program.blocks.front())});
for (size_t index = 0; index < program.blocks.size(); index++) {
const auto* block = program.blocks[index];
EmitBlock(ctx, block, [&](ValueEmitContext& lane, const IR::Inst& inst) {
EmitStructuredInstruction(lane, structured, inst);
});
structured.block_exit_labels.emplace(block, ctx.state.current_label);
EmitStructuredTerminator(ctx, block, ctx.program.block_info[index]);
EmitStructuredTerminator(ctx, block, program.block_info[index]);
}
PatchStructuredPhis(ctx, structured);
}
void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher) {
auto& state = ctx.state;
const auto* entry = ctx.program.blocks.front();
const auto* entry = state.program.blocks.front();
state.builder.AddFunction({OpBranch, ctx.Label(entry)});
EmitBlock(ctx, entry, [&](ValueEmitContext& lane, const IR::Inst& inst) {
EmitDispatcherInstruction(lane, dispatcher, inst);
});
const auto initial_pc =
EmitDispatcherNextPc(ctx, dispatcher, entry, ctx.program.block_info.front());
EmitDispatcherNextPc(ctx, dispatcher, entry, state.program.block_info.front());
const auto initial_parent = state.current_label;
state.builder.AddFunction({OpBranch, dispatcher.header_label});
@@ -357,21 +357,21 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
state.builder.AddFunction(
{OpSelectionMerge, dispatcher.after_switch_label, SelectionControlNone});
std::vector<uint32_t> words {OpSwitch, pc, dispatcher.after_switch_label};
for (size_t index = 1; index < ctx.program.blocks.size(); index++) {
words.push_back(ctx.program.block_info[index].id);
words.push_back(ctx.Label(ctx.program.blocks[index]));
for (size_t index = 1; index < state.program.blocks.size(); index++) {
words.push_back(state.program.block_info[index].id);
words.push_back(ctx.Label(state.program.blocks[index]));
}
state.builder.AddFunction(words);
std::vector<uint32_t> next_pc_words {OpPhi, TypeU32(state), next_pc,
ConstantU32(state, UINT32_MAX), dispatcher.select_label};
for (size_t index = 1; index < ctx.program.blocks.size(); index++) {
EmitBlock(ctx, ctx.program.blocks[index],
for (size_t index = 1; index < state.program.blocks.size(); index++) {
EmitBlock(ctx, state.program.blocks[index],
[&](ValueEmitContext& lane, const IR::Inst& inst) {
EmitDispatcherInstruction(lane, dispatcher, inst);
});
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, ctx.program.blocks[index],
ctx.program.block_info[index]);
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, state.program.blocks[index],
state.program.block_info[index]);
next_pc_words.push_back(selected);
next_pc_words.push_back(state.current_label);
state.builder.AddFunction({OpBranch, dispatcher.after_switch_label});
@@ -423,8 +423,8 @@ uint32_t ValueEmitContext::Def(IR::Value value) {
if (inst == nullptr) {
Fail("direct SPIR-V emitter received a non-value argument");
}
if (dispatcher_spills != nullptr && current_block != nullptr &&
inst->Parent() != current_block) {
if (dispatcher_spills != nullptr && state.current_block != nullptr &&
inst->Parent() != state.current_block) {
if (const auto found = dispatcher_spills->find(inst); found != dispatcher_spills->end()) {
if (const auto loaded = dispatcher_block_loads.find(inst);
loaded != dispatcher_block_loads.end() &&
@@ -437,12 +437,7 @@ uint32_t ValueEmitContext::Def(IR::Value value) {
return id;
}
}
if (const auto found = definitions.find(inst); found != definitions.end()) {
return found->second;
}
const auto id = state.builder.AllocateId();
definitions.emplace(inst, id);
return id;
return Result(*inst);
}
uint32_t ValueEmitContext::Arg(const IR::Inst& inst, size_t index) {
@@ -537,10 +532,12 @@ uint32_t ValueEmitContext::Result(const IR::Inst& inst) {
uint32_t ValueEmitContext::Emit(const IR::Inst& inst, uint32_t opcode, IR::Type type,
std::initializer_list<uint32_t> args) {
std::vector<uint32_t> words {opcode, TypeId(type), Result(inst)};
const auto type_id = TypeId(type);
const auto result = Result(inst);
std::vector<uint32_t> words {opcode, type_id, result};
words.insert(words.end(), args.begin(), args.end());
state.builder.AddFunction(words);
return Result(inst);
return result;
}
uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) {
@@ -571,33 +568,34 @@ const IR::Inst* ValueEmitContext::ImageAddress(IR::Value value) {
}
const IR::MemoryInfo& ValueEmitContext::Memory(const IR::Inst& inst) const {
return program.memory_info.at(inst.Flags<IR::MemoryFlags>().index);
return state.program.memory_info.at(inst.Flags<IR::MemoryFlags>().index);
}
const IR::ExportInfo& ValueEmitContext::Export(const IR::Inst& inst) const {
return program.export_info.at(inst.Flags<IR::ExportFlags>().index);
return state.program.export_info.at(inst.Flags<IR::ExportFlags>().index);
}
uint32_t ValueEmitContext::Label(const IR::Block* block) const {
return labels.at(block);
return state.labels.at(block);
}
[[noreturn]] void ValueEmitContext::Fail(const char* reason) const {
EXIT("SPIR-V emission failed: hash=0x%016" PRIx64 " stage=%u reason=%s\n",
program.shader_hash, static_cast<unsigned>(program.stage), reason);
state.program.shader_hash, static_cast<unsigned>(state.program.stage), reason);
std::abort();
}
[[noreturn]] void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) const {
EXIT("SPIR-V emission failed: hash=0x%016" PRIx64 " stage=%u opcode=%s reason=%s\n",
program.shader_hash, static_cast<unsigned>(program.stage),
state.program.shader_hash, static_cast<unsigned>(state.program.stage),
IR::ValueOpcodeName(inst.GetOpcode()), reason);
std::abort();
}
void EmitProgram(EmitterState& state, const IR::Program& program) {
ValueEmitContext ctx(state, program);
ValueEmitContext high(state, program);
void EmitProgram(EmitterState& state) {
const auto& program = state.program;
ValueEmitContext ctx(state);
ValueEmitContext high(state);
if (state.lane_count == 2) {
ctx.other_half = &high;
high.other_half = &ctx;
@@ -610,8 +608,7 @@ void EmitProgram(EmitterState& state, const IR::Program& program) {
}
for (const auto* block: program.blocks) {
const auto label = state.builder.AllocateId();
ctx.labels.emplace(block, label);
high.labels.emplace(block, label);
state.labels.emplace(block, label);
}
if (state.program.dispatcher_fallback) {
auto& dispatch = dispatcher.emplace();
@@ -7,6 +7,7 @@
#include <iterator>
#include <map>
#include <set>
#include <span>
#include <stack>
namespace Libs::Graphics::ShaderRecompiler::CFG {
@@ -51,26 +52,6 @@ uint32_t ProgramEndPc(const Decoder::Program& program) {
return InstructionEndPc(program.instructions.back());
}
bool IsUnconditionalBranch(Opcode opcode) {
return opcode == Opcode::S_BRANCH;
}
bool IsConditionalBranch(Opcode opcode) {
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;
default: return false;
}
}
bool IsBranch(Opcode opcode) {
return IsUnconditionalBranch(opcode) || IsConditionalBranch(opcode);
}
BranchCondition ConditionForOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::S_BRANCH: return BranchCondition::Always;
@@ -80,6 +61,8 @@ BranchCondition ConditionForOpcode(Opcode opcode) {
case Opcode::S_CBRANCH_VCCNZ: return BranchCondition::VccNonZero;
case Opcode::S_CBRANCH_EXECZ: return BranchCondition::ExecZero;
case Opcode::S_CBRANCH_EXECNZ: return BranchCondition::ExecNonZero;
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END: return BranchCondition::ScalarInstruction;
default: return BranchCondition::Unknown;
}
}
@@ -323,11 +306,10 @@ bool ResolveJumpTableEntryCount(const Decoder::Program& program, uint32_t before
return false;
}
bool AddUniqueTargetPc(std::vector<uint32_t>& targets, uint32_t target) {
if (std::find(targets.begin(), targets.end(), target) == targets.end()) {
targets.push_back(target);
void AddUnique(std::vector<uint32_t>& values, uint32_t value) {
if (std::find(values.begin(), values.end(), value) == values.end()) {
values.push_back(value);
}
return true;
}
bool ScalarCodeWrittenInRange(const Decoder::Program& program, uint32_t begin_index,
@@ -421,7 +403,7 @@ bool ResolveSetpcJumpTable(const Decoder::Program& program, uint32_t setpc_index
return false;
}
const auto target_pc = (target_base + low) & ~3u;
AddUniqueTargetPc(targets, target_pc);
AddUnique(targets, target_pc);
selector_values.push_back(i * 8u);
selector_target_pcs.push_back(target_pc);
}
@@ -507,7 +489,7 @@ bool ResolveSetpcDwordJumpTable(const Decoder::Program& program, uint32_t setpc_
return false;
}
const auto target_pc = (target_base - offset) & ~3u;
AddUniqueTargetPc(targets, target_pc);
AddUnique(targets, target_pc);
selector_values.push_back(i * 4u);
selector_target_pcs.push_back(target_pc);
}
@@ -590,12 +572,6 @@ bool IsValidTarget(uint32_t target, const std::set<uint32_t>& instruction_pcs, u
return target == end_pc || (target >= first_pc && instruction_pcs.contains(target));
}
void AddUnique(std::vector<uint32_t>& values, uint32_t value) {
if (std::find(values.begin(), values.end(), value) == values.end()) {
values.push_back(value);
}
}
std::vector<uint32_t> AllBlockIds(uint32_t count) {
std::vector<uint32_t> ids;
ids.reserve(count);
@@ -868,15 +844,15 @@ void ComputeNaturalLoops(Graph& graph) {
loop.header = edge.to;
loop.latch = edge.from;
loop.continue_block = edge.from;
loop.body_blocks = body;
loop.body_blocks = std::move(body);
for (auto block_id: body) {
for (auto block_id: loop.body_blocks) {
const auto* block = graph.FindBlock(block_id);
if (block == nullptr) {
continue;
}
for (auto succ: block->successors) {
if (!Contains(body, succ)) {
if (!Contains(loop.body_blocks, succ)) {
AddUnique(loop.exit_blocks, succ);
}
}
@@ -995,6 +971,29 @@ void RecomputeAnalyses(Graph& graph) {
ComputeComponents(graph);
}
std::vector<uint32_t> ApplyBlockOrder(Graph& graph, std::vector<BasicBlock> blocks) {
std::vector<uint32_t> id_map(blocks.size(), UINT32_MAX);
for (uint32_t i = 0; i < blocks.size(); i++) {
id_map[blocks[i].id] = i;
}
graph.blocks = std::move(blocks);
graph.entry_block = RemapId(graph.entry_block, id_map);
for (auto& block: graph.blocks) {
block.id = RemapId(block.id, id_map);
RemapIds(block.predecessors, id_map);
RemapIds(block.successors, id_map);
RemapIds(block.dominators, id_map);
RemapIds(block.post_dominators, id_map);
block.terminator.true_block = RemapId(block.terminator.true_block, id_map);
block.terminator.false_block = RemapId(block.terminator.false_block, id_map);
block.terminator.merge_block = RemapId(block.terminator.merge_block, id_map);
block.terminator.continue_block = RemapId(block.terminator.continue_block, id_map);
}
return id_map;
}
uint32_t MoveBlockBefore(Graph& graph, uint32_t block_id, uint32_t before_id) {
if (block_id == before_id || block_id >= graph.blocks.size() ||
before_id >= graph.blocks.size()) {
@@ -1016,24 +1015,7 @@ uint32_t MoveBlockBefore(Graph& graph, uint32_t block_id, uint32_t before_id) {
}
}
std::vector<uint32_t> id_map(new_blocks.size(), UINT32_MAX);
for (uint32_t i = 0; i < new_blocks.size(); i++) {
id_map[new_blocks[i].id] = i;
}
graph.blocks = std::move(new_blocks);
graph.entry_block = RemapId(graph.entry_block, id_map);
for (auto& block: graph.blocks) {
block.id = RemapId(block.id, id_map);
RemapIds(block.predecessors, id_map);
RemapIds(block.successors, id_map);
RemapIds(block.dominators, id_map);
RemapIds(block.post_dominators, id_map);
block.terminator.true_block = RemapId(block.terminator.true_block, id_map);
block.terminator.false_block = RemapId(block.terminator.false_block, id_map);
block.terminator.merge_block = RemapId(block.terminator.merge_block, id_map);
block.terminator.continue_block = RemapId(block.terminator.continue_block, id_map);
}
const auto id_map = ApplyBlockOrder(graph, std::move(new_blocks));
return RemapId(block_id, id_map);
}
@@ -1396,16 +1378,16 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
}
std::vector<uint32_t> construct_predecessors;
std::vector<uint32_t> external_predecessors;
bool has_external_predecessor = false;
for (auto pred: merge_block->predecessors) {
if (Contains(construct_blocks, pred)) {
AddUnique(construct_predecessors, pred);
} else {
AddUnique(external_predecessors, pred);
has_external_predecessor = true;
}
}
if (construct_predecessors.empty() || (!force_split && external_predecessors.empty())) {
if (construct_predecessors.empty() || (!force_split && !has_external_predecessor)) {
return false;
}
const auto synthetic_merge = AppendSyntheticBranchBlock(graph, merge);
@@ -1656,24 +1638,7 @@ void PlaceGotoRouteBlocks(Graph& graph, uint32_t original_block_count,
new_blocks.push_back(std::move(old_blocks[block_id]));
}
std::vector<uint32_t> id_map(new_blocks.size(), UINT32_MAX);
for (uint32_t block_id = 0; block_id < new_blocks.size(); block_id++) {
id_map[new_blocks[block_id].id] = block_id;
}
graph.blocks = std::move(new_blocks);
graph.entry_block = RemapId(graph.entry_block, id_map);
for (auto& block: graph.blocks) {
block.id = RemapId(block.id, id_map);
RemapIds(block.predecessors, id_map);
RemapIds(block.successors, id_map);
RemapIds(block.dominators, id_map);
RemapIds(block.post_dominators, id_map);
block.terminator.true_block = RemapId(block.terminator.true_block, id_map);
block.terminator.false_block = RemapId(block.terminator.false_block, id_map);
block.terminator.merge_block = RemapId(block.terminator.merge_block, id_map);
block.terminator.continue_block = RemapId(block.terminator.continue_block, id_map);
}
ApplyBlockOrder(graph, std::move(new_blocks));
}
uint32_t AppendGotoSelectBlock(Graph& graph, uint32_t route_variable, uint32_t true_target,
@@ -1767,7 +1732,7 @@ bool RouteOneSharedArm(Graph& graph, uint32_t original_block_count, uint32_t out
inner_is_true ? outer->terminator.false_block : outer->terminator.true_block;
const auto* inner = graph.FindBlock(inner_id);
if (inner == nullptr || inner->inst_begin == inner->inst_end ||
inner->predecessors != std::vector<uint32_t> {outer_id} ||
inner->predecessors.size() != 1u || inner->predecessors.front() != outer_id ||
inner->terminator.kind != TerminatorKind::ConditionalBranch ||
IsInnermostLoopControlConditional(graph, *inner)) {
continue;
@@ -1942,7 +1907,7 @@ Graph BuildGraph(const Decoder::Program& program) {
for (uint32_t i = 0; i < program.instructions.size(); i++) {
const auto& inst = program.instructions[i];
const auto next_pc = InstructionEndPc(inst);
if (IsBranch(inst.opcode)) {
if (Decoder::IsDirectBranch(inst.opcode)) {
if (!IsValidTarget(inst.branch_target, instruction_pcs, first_pc, end_pc)) {
ExitBuildFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}",
@@ -1959,9 +1924,9 @@ Graph BuildGraph(const Decoder::Program& program) {
graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
fmt::format("unsupported dynamic S_SETPC_B64 at pc 0x{:08x}", inst.pc));
}
const auto& target_pcs = target_info.indirect
? target_info.target_pcs
: std::vector<uint32_t> {target_info.target};
const auto target_pcs = target_info.indirect
? std::span<const uint32_t>(target_info.target_pcs)
: std::span<const uint32_t>(&target_info.target, 1);
for (const auto target: target_pcs) {
if (!IsValidTarget(target, instruction_pcs, first_pc, end_pc)) {
ExitBuildFailure(
@@ -1981,7 +1946,6 @@ Graph BuildGraph(const Decoder::Program& program) {
}
std::vector<uint32_t> sorted_labels(labels.begin(), labels.end());
std::sort(sorted_labels.begin(), sorted_labels.end());
for (uint32_t i = 0; i < sorted_labels.size(); i++) {
const auto start = sorted_labels[i];
if (start > end_pc) {
@@ -2050,11 +2014,11 @@ Graph BuildGraph(const Decoder::Program& program) {
block.terminator.condition = BranchCondition::Always;
block.terminator.true_block = pc_to_block.at(target_info.target);
}
} else if (IsUnconditionalBranch(last.opcode)) {
} else if (last.opcode == Opcode::S_BRANCH) {
block.terminator.kind = TerminatorKind::Branch;
block.terminator.condition = BranchCondition::Always;
block.terminator.true_block = pc_to_block.at(last.branch_target);
} else if (IsConditionalBranch(last.opcode)) {
} else if (Decoder::IsConditionalBranch(last.opcode)) {
block.terminator.kind = TerminatorKind::ConditionalBranch;
block.terminator.condition = ConditionForOpcode(last.opcode);
block.terminator.true_block = pc_to_block.at(last.branch_target);
@@ -2122,11 +2086,7 @@ Graph BuildGraph(const Decoder::Program& program) {
}
SortUnique(graph.code_table_load_pcs);
ComputeDominators(graph);
ComputePostDominators(graph);
ComputeBackEdges(graph);
ComputeNaturalLoops(graph);
ComputeComponents(graph);
RecomputeAnalyses(graph);
if (indirect_setpc) {
graph.irreducible = true;
@@ -2266,6 +2226,7 @@ std::string BranchConditionToString(BranchCondition condition) {
case BranchCondition::VccNonZero: return "vccnz";
case BranchCondition::ExecZero: return "execz";
case BranchCondition::ExecNonZero: return "execnz";
case BranchCondition::ScalarInstruction: return "scalar_instruction";
case BranchCondition::GotoVariable: return "goto_variable";
default: return "unknown";
}
@@ -17,6 +17,7 @@ enum class BranchCondition {
VccNonZero,
ExecZero,
ExecNonZero,
ScalarInstruction,
GotoVariable,
Unknown
};
@@ -10,7 +10,6 @@ void DecodeExp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
const uint32_t en = word0 & 0xfu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.family = Family::EXP;
inst.opcode_id = target;
@@ -8,24 +8,15 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace {
struct MimgSampleInfo {
uint32_t encoding = 0;
const char* name = nullptr;
uint32_t flags = 0;
uint32_t address_components = 3;
uint32_t encoding = 0;
const char* name = nullptr;
uint32_t flags = 0;
};
struct MimgGatherInfo {
uint32_t encoding = 0;
const char* name = nullptr;
Opcode decoded = Opcode::UNSUPPORTED;
uint32_t flags = 0;
uint32_t address_components = 3;
};
struct MimgAtomicInfo {
uint32_t encoding = 0;
const char* name = nullptr;
Opcode decoded = Opcode::UNSUPPORTED;
uint32_t encoding = 0;
Opcode decoded = Opcode::UNSUPPORTED;
uint32_t flags = 0;
};
constexpr ImageDimension DecodeImageDimension(uint32_t dim) {
@@ -84,131 +75,127 @@ constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension d
return components;
}
constexpr MimgSampleInfo SampleInfo(uint32_t opcode, const char* name, uint32_t flags) {
return {opcode, name, flags, ImageSampleAddressComponents(flags, ImageDimension::Dim2D)};
}
constexpr MimgSampleInfo MIMG_SAMPLE_OPCODE_LIST[] = {
SampleInfo(0x20u, "image_sample", 0),
SampleInfo(0x21u, "image_sample_cl", ImageSampleFlagLodClamp),
SampleInfo(0x22u, "image_sample_d", ImageSampleFlagDerivative),
SampleInfo(0x23u, "image_sample_d_cl", ImageSampleFlagDerivative | ImageSampleFlagLodClamp),
SampleInfo(0x24u, "image_sample_l", ImageSampleFlagLod),
SampleInfo(0x25u, "image_sample_b", ImageSampleFlagBias),
SampleInfo(0x26u, "image_sample_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp),
SampleInfo(0x27u, "image_sample_lz", ImageSampleFlagLevelZero),
SampleInfo(0x28u, "image_sample_c", ImageSampleFlagCompare),
SampleInfo(0x29u, "image_sample_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp),
SampleInfo(0x2au, "image_sample_c_d", ImageSampleFlagCompare | ImageSampleFlagDerivative),
SampleInfo(0x2bu, "image_sample_c_d_cl",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp),
SampleInfo(0x2cu, "image_sample_c_l", ImageSampleFlagCompare | ImageSampleFlagLod),
SampleInfo(0x2du, "image_sample_c_b", ImageSampleFlagCompare | ImageSampleFlagBias),
SampleInfo(0x2eu, "image_sample_c_b_cl",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp),
SampleInfo(0x2fu, "image_sample_c_lz", ImageSampleFlagCompare | ImageSampleFlagLevelZero),
SampleInfo(0x30u, "image_sample_o", ImageSampleFlagOffset),
SampleInfo(0x31u, "image_sample_cl_o", ImageSampleFlagLodClamp | ImageSampleFlagOffset),
SampleInfo(0x32u, "image_sample_d_o", ImageSampleFlagDerivative | ImageSampleFlagOffset),
SampleInfo(0x33u, "image_sample_d_cl_o",
ImageSampleFlagDerivative | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
SampleInfo(0x34u, "image_sample_l_o", ImageSampleFlagLod | ImageSampleFlagOffset),
SampleInfo(0x35u, "image_sample_b_o", ImageSampleFlagBias | ImageSampleFlagOffset),
SampleInfo(0x36u, "image_sample_b_cl_o",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
SampleInfo(0x37u, "image_sample_lz_o", ImageSampleFlagLevelZero | ImageSampleFlagOffset),
SampleInfo(0x38u, "image_sample_c_o", ImageSampleFlagCompare | ImageSampleFlagOffset),
SampleInfo(0x39u, "image_sample_c_cl_o",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
SampleInfo(0x3au, "image_sample_c_d_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagOffset),
SampleInfo(0x3bu, "image_sample_c_d_cl_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp |
ImageSampleFlagOffset),
SampleInfo(0x3cu, "image_sample_c_l_o",
ImageSampleFlagCompare | ImageSampleFlagLod | ImageSampleFlagOffset),
SampleInfo(0x3du, "image_sample_c_b_o",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset),
SampleInfo(0x3eu, "image_sample_c_b_cl_o",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagOffset),
SampleInfo(0x3fu, "image_sample_c_lz_o",
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset),
SampleInfo(0x68u, "image_sample_cd", ImageSampleFlagDerivative | ImageSampleFlagCd),
SampleInfo(0x69u, "image_sample_cd_cl",
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp),
SampleInfo(0x6au, "image_sample_c_cd",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd),
SampleInfo(0x6bu, "image_sample_c_cd_cl",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
ImageSampleFlagLodClamp),
SampleInfo(0x6cu, "image_sample_cd_o",
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagOffset),
SampleInfo(0x6du, "image_sample_cd_cl_o",
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp |
ImageSampleFlagOffset),
SampleInfo(0x6eu, "image_sample_c_cd_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
ImageSampleFlagOffset),
SampleInfo(0x6fu, "image_sample_c_cd_cl_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
ImageSampleFlagLodClamp | ImageSampleFlagOffset),
SampleInfo(0xa0u, "image_sample_a", ImageSampleFlagAdjust),
SampleInfo(0xa1u, "image_sample_cl_a", ImageSampleFlagLodClamp | ImageSampleFlagAdjust),
SampleInfo(0xa5u, "image_sample_b_a", ImageSampleFlagBias | ImageSampleFlagAdjust),
SampleInfo(0xa6u, "image_sample_b_cl_a",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagAdjust),
SampleInfo(0xa8u, "image_sample_c_a", ImageSampleFlagCompare | ImageSampleFlagAdjust),
SampleInfo(0xa9u, "image_sample_c_cl_a",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagAdjust),
SampleInfo(0xadu, "image_sample_c_b_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagAdjust),
SampleInfo(0xaeu, "image_sample_c_b_cl_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagAdjust),
SampleInfo(0xb0u, "image_sample_o_a", ImageSampleFlagOffset | ImageSampleFlagAdjust),
SampleInfo(0xb1u, "image_sample_cl_o_a",
ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust),
SampleInfo(0xb5u, "image_sample_b_o_a",
ImageSampleFlagBias | ImageSampleFlagOffset | ImageSampleFlagAdjust),
SampleInfo(0xb6u, "image_sample_b_cl_o_a",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
ImageSampleFlagAdjust),
SampleInfo(0xb8u, "image_sample_c_o_a",
ImageSampleFlagCompare | ImageSampleFlagOffset | ImageSampleFlagAdjust),
SampleInfo(0xb9u, "image_sample_c_cl_o_a",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
ImageSampleFlagAdjust),
SampleInfo(0xbdu, "image_sample_c_b_o_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset |
ImageSampleFlagAdjust),
SampleInfo(0xbeu, "image_sample_c_b_cl_o_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagOffset | ImageSampleFlagAdjust),
{0x20u, "image_sample", 0},
{0x21u, "image_sample_cl", ImageSampleFlagLodClamp},
{0x22u, "image_sample_d", ImageSampleFlagDerivative},
{0x23u, "image_sample_d_cl", ImageSampleFlagDerivative | ImageSampleFlagLodClamp},
{0x24u, "image_sample_l", ImageSampleFlagLod},
{0x25u, "image_sample_b", ImageSampleFlagBias},
{0x26u, "image_sample_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp},
{0x27u, "image_sample_lz", ImageSampleFlagLevelZero},
{0x28u, "image_sample_c", ImageSampleFlagCompare},
{0x29u, "image_sample_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp},
{0x2au, "image_sample_c_d", ImageSampleFlagCompare | ImageSampleFlagDerivative},
{0x2bu, "image_sample_c_d_cl",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp},
{0x2cu, "image_sample_c_l", ImageSampleFlagCompare | ImageSampleFlagLod},
{0x2du, "image_sample_c_b", ImageSampleFlagCompare | ImageSampleFlagBias},
{0x2eu, "image_sample_c_b_cl",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp},
{0x2fu, "image_sample_c_lz", ImageSampleFlagCompare | ImageSampleFlagLevelZero},
{0x30u, "image_sample_o", ImageSampleFlagOffset},
{0x31u, "image_sample_cl_o", ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0x32u, "image_sample_d_o", ImageSampleFlagDerivative | ImageSampleFlagOffset},
{0x33u, "image_sample_d_cl_o",
ImageSampleFlagDerivative | ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0x34u, "image_sample_l_o", ImageSampleFlagLod | ImageSampleFlagOffset},
{0x35u, "image_sample_b_o", ImageSampleFlagBias | ImageSampleFlagOffset},
{0x36u, "image_sample_b_cl_o",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0x37u, "image_sample_lz_o", ImageSampleFlagLevelZero | ImageSampleFlagOffset},
{0x38u, "image_sample_c_o", ImageSampleFlagCompare | ImageSampleFlagOffset},
{0x39u, "image_sample_c_cl_o",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0x3au, "image_sample_c_d_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagOffset},
{0x3bu, "image_sample_c_d_cl_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp |
ImageSampleFlagOffset},
{0x3cu, "image_sample_c_l_o",
ImageSampleFlagCompare | ImageSampleFlagLod | ImageSampleFlagOffset},
{0x3du, "image_sample_c_b_o",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset},
{0x3eu, "image_sample_c_b_cl_o",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagOffset},
{0x3fu, "image_sample_c_lz_o",
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset},
{0x68u, "image_sample_cd", ImageSampleFlagDerivative | ImageSampleFlagCd},
{0x69u, "image_sample_cd_cl",
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp},
{0x6au, "image_sample_c_cd",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd},
{0x6bu, "image_sample_c_cd_cl",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
ImageSampleFlagLodClamp},
{0x6cu, "image_sample_cd_o",
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagOffset},
{0x6du, "image_sample_cd_cl_o",
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp |
ImageSampleFlagOffset},
{0x6eu, "image_sample_c_cd_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
ImageSampleFlagOffset},
{0x6fu, "image_sample_c_cd_cl_o",
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
ImageSampleFlagLodClamp | ImageSampleFlagOffset},
{0xa0u, "image_sample_a", ImageSampleFlagAdjust},
{0xa1u, "image_sample_cl_a", ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
{0xa5u, "image_sample_b_a", ImageSampleFlagBias | ImageSampleFlagAdjust},
{0xa6u, "image_sample_b_cl_a",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
{0xa8u, "image_sample_c_a", ImageSampleFlagCompare | ImageSampleFlagAdjust},
{0xa9u, "image_sample_c_cl_a",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
{0xadu, "image_sample_c_b_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagAdjust},
{0xaeu, "image_sample_c_b_cl_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagAdjust},
{0xb0u, "image_sample_o_a", ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xb1u, "image_sample_cl_o_a",
ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xb5u, "image_sample_b_o_a",
ImageSampleFlagBias | ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xb6u, "image_sample_b_cl_o_a",
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
ImageSampleFlagAdjust},
{0xb8u, "image_sample_c_o_a",
ImageSampleFlagCompare | ImageSampleFlagOffset | ImageSampleFlagAdjust},
{0xb9u, "image_sample_c_cl_o_a",
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
ImageSampleFlagAdjust},
{0xbdu, "image_sample_c_b_o_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset |
ImageSampleFlagAdjust},
{0xbeu, "image_sample_c_b_cl_o_a",
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
ImageSampleFlagOffset | ImageSampleFlagAdjust},
};
constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
{0x47u, "image_gather4_lz", Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero, 2u},
{0x48u, "image_gather4_c", Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare, 3u},
{0x4fu, "image_gather4_c_lz", Opcode::IMAGE_GATHER4_C_LZ,
ImageSampleFlagCompare | ImageSampleFlagLevelZero, 3u},
{0x57u, "image_gather4_lz_o", Opcode::IMAGE_GATHER4_LZ_O,
ImageSampleFlagLevelZero | ImageSampleFlagOffset, 3u},
{0x58u, "image_gather4_c_o", Opcode::IMAGE_GATHER4_C_O,
ImageSampleFlagCompare | ImageSampleFlagOffset, 4u},
{0x5fu, "image_gather4_c_lz_o", Opcode::IMAGE_GATHER4_C_LZ_O,
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset, 4u},
{0x61u, "image_gather4h", Opcode::IMAGE_GATHER4H, ImageSampleFlagGatherHorizontal, 2u},
{0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero},
{0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare},
{0x4fu, Opcode::IMAGE_GATHER4_C_LZ,
ImageSampleFlagCompare | ImageSampleFlagLevelZero},
{0x57u, Opcode::IMAGE_GATHER4_LZ_O,
ImageSampleFlagLevelZero | ImageSampleFlagOffset},
{0x58u, Opcode::IMAGE_GATHER4_C_O,
ImageSampleFlagCompare | ImageSampleFlagOffset},
{0x5fu, Opcode::IMAGE_GATHER4_C_LZ_O,
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset},
{0x61u, Opcode::IMAGE_GATHER4H, ImageSampleFlagGatherHorizontal},
};
constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = {
{0x0fu, "image_atomic_swap", Opcode::IMAGE_ATOMIC_SWAP},
{0x11u, "image_atomic_add", Opcode::IMAGE_ATOMIC_ADD},
{0x15u, "image_atomic_umin", Opcode::IMAGE_ATOMIC_UMIN},
{0x17u, "image_atomic_umax", Opcode::IMAGE_ATOMIC_UMAX},
{0x18u, "image_atomic_and", Opcode::IMAGE_ATOMIC_AND},
{0x19u, "image_atomic_or", Opcode::IMAGE_ATOMIC_OR},
{0x1au, "image_atomic_xor", Opcode::IMAGE_ATOMIC_XOR},
constexpr Detail::OpcodeMap MIMG_ATOMIC_OPCODE_LIST[] = {
{0x0fu, Opcode::IMAGE_ATOMIC_SWAP},
{0x11u, Opcode::IMAGE_ATOMIC_ADD},
{0x15u, Opcode::IMAGE_ATOMIC_UMIN},
{0x17u, Opcode::IMAGE_ATOMIC_UMAX},
{0x18u, Opcode::IMAGE_ATOMIC_AND},
{0x19u, Opcode::IMAGE_ATOMIC_OR},
{0x1au, Opcode::IMAGE_ATOMIC_XOR},
};
constexpr auto MIMG_SAMPLE_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_SAMPLE_OPCODE_LIST);
@@ -223,12 +210,12 @@ const MimgGatherInfo* LookupGather(uint32_t opcode) {
return Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
}
const MimgAtomicInfo* LookupAtomic(uint32_t opcode) {
const Detail::OpcodeMap* LookupAtomic(uint32_t opcode) {
return Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode);
}
Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgGatherInfo* gather,
const MimgAtomicInfo* atomic) {
const Detail::OpcodeMap* atomic) {
if (sample != nullptr) {
return Opcode::IMAGE_SAMPLE;
}
@@ -262,7 +249,7 @@ uint32_t DecodeMimgSampleFlags(const MimgSampleInfo* sample, const MimgGatherInf
uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
const MimgSampleInfo* sample, const MimgGatherInfo* gather,
const MimgAtomicInfo* atomic) {
const Detail::OpcodeMap* atomic) {
if (sample != nullptr) {
return ImageSampleAddressComponents(sample->flags, dimension);
}
@@ -349,7 +336,6 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const auto* atomic = LookupAtomic(opcode);
inst.pc = pc;
inst.word = word0;
inst.word_count = word_count;
inst.family = Family::MIMG;
inst.opcode_id = opcode;
@@ -94,6 +94,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x12u, Opcode::DS_MIN_F32, 1, 32}, {0x13u, Opcode::DS_MAX_F32, 1, 32},
{0x1eu, Opcode::DS_WRITE_B8, 1, 8}, {0x1fu, Opcode::DS_WRITE_B16, 1, 16},
{0x20u, Opcode::DS_ADD_RTN_U32, 1, 32}, {0x21u, Opcode::DS_SUB_RTN_U32, 1, 32},
{0x23u, Opcode::DS_INC_RTN_U32, 1, 32}, {0x24u, Opcode::DS_DEC_RTN_U32, 1, 32},
{0x25u, Opcode::DS_MIN_RTN_I32, 1, 32}, {0x26u, Opcode::DS_MAX_RTN_I32, 1, 32},
{0x27u, Opcode::DS_MIN_RTN_U32, 1, 32}, {0x28u, Opcode::DS_MAX_RTN_U32, 1, 32},
{0x29u, Opcode::DS_AND_RTN_B32, 1, 32}, {0x2au, Opcode::DS_OR_RTN_B32, 1, 32},
@@ -129,13 +130,6 @@ uint32_t SignExtendU32(uint32_t value, uint32_t bits) {
return (value ^ sign) - sign;
}
void MarkMemoryUnsupported(Instruction& inst, Family family, uint32_t opcode, const char* reason) {
inst.family = family;
inst.opcode_id = opcode;
inst.opcode = Opcode::UNSUPPORTED;
SetUnsupported(inst, family, opcode, reason);
}
void ApplyMemoryInfo(Instruction& inst, const MemoryOpcodeInfo* info) {
if (info == nullptr) {
inst.opcode = Opcode::UNSUPPORTED;
@@ -172,6 +166,8 @@ bool IsDsAtomicOpcode(Opcode opcode) {
case Opcode::DS_ADD_RTN_U32:
case Opcode::DS_SUB_U32:
case Opcode::DS_SUB_RTN_U32:
case Opcode::DS_INC_RTN_U32:
case Opcode::DS_DEC_RTN_U32:
case Opcode::DS_MIN_I32:
case Opcode::DS_MIN_RTN_I32:
case Opcode::DS_MAX_I32:
@@ -231,7 +227,6 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t soffset = (word1 >> 25u) & 0x7fu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = SignExtendU32(word1 & 0x1fffffu, 21u);
inst.glc = ((word0 >> 16u) & 1u) != 0;
@@ -241,7 +236,7 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::UNSUPPORTED) {
MarkMemoryUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
SetUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
}
DecodeScalarDestination(sdst, pc, inst.dst);
@@ -263,7 +258,6 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
const uint32_t soffset = (word1 >> 24u) & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = word0 & 0xfffu;
inst.idxen = ((word0 >> 13u) & 1u) != 0;
@@ -276,7 +270,7 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::UNSUPPORTED) {
MarkMemoryUnsupported(inst, Family::MUBUF, opcode, "MUBUF opcode is not implemented");
SetUnsupported(inst, Family::MUBUF, opcode, "MUBUF opcode is not implemented");
}
DecodeVectorGpr(vdata, inst.dst);
@@ -299,7 +293,6 @@ void DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
const uint32_t soffset = (word1 >> 24u) & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = word0 & 0xfffu;
inst.idxen = ((word0 >> 13u) & 1u) != 0;
@@ -314,7 +307,7 @@ void DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::UNSUPPORTED) {
MarkMemoryUnsupported(inst, Family::MTBUF, opcode, "MTBUF opcode is not implemented");
SetUnsupported(inst, Family::MTBUF, opcode, "MTBUF opcode is not implemented");
}
DecodeVectorGpr(vdata, inst.dst);
@@ -339,7 +332,6 @@ void DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t addr = word1 & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = seg == 0u ? (offset & 0x7ffu) : SignExtendU32(offset, 12u);
inst.glc = ((word0 >> 16u) & 1u) != 0;
@@ -356,7 +348,7 @@ void DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
return;
}
if (inst.opcode == Opcode::UNSUPPORTED) {
MarkMemoryUnsupported(inst, Family::FLAT, opcode, "FLAT opcode is not implemented");
SetUnsupported(inst, Family::FLAT, opcode, "FLAT opcode is not implemented");
return;
}
@@ -384,7 +376,6 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
const uint32_t addr = word1 & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = offset0 | (offset1 << 8u);
inst.gds = ((word0 >> 17u) & 1u) != 0u;
@@ -394,7 +385,7 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::UNSUPPORTED) {
MarkMemoryUnsupported(inst, Family::DS, opcode, "DS opcode is not implemented");
SetUnsupported(inst, Family::DS, opcode, "DS opcode is not implemented");
}
if (inst.opcode == Opcode::DS_SWIZZLE_B32 && inst.offset >= 0xe000u) {
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
@@ -41,6 +41,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
{0x06u, Opcode::S_CMOV_B64},
{0x07u, Opcode::S_NOT_B32},
{0x08u, Opcode::S_NOT_B64},
{0x09u, Opcode::S_WQM_B32},
{0x0au, Opcode::S_WQM_B64},
{0x0bu, Opcode::S_BREV_B32},
{0x0fu, Opcode::S_BCNT1_I32_B32},
@@ -62,6 +63,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
{0x3bu, Opcode::S_BITREPLICATE_B64_B32},
{0x3cu, Opcode::S_AND_SAVEEXEC_B32},
{0x40u, Opcode::S_ORN2_SAVEEXEC_B32},
{0x44u, Opcode::S_ANDN1_SAVEEXEC_B32},
};
@@ -82,6 +84,7 @@ constexpr OpcodeMap SOPK_OPCODE_LIST[] = {
{0x0eu, Opcode::S_CMP_LE_U32}, {0x0fu, Opcode::S_ADD_I32}, {0x10u, Opcode::S_MULK_I32},
{0x13u, Opcode::S_SETREG_B32}, {0x17u, Opcode::S_WAITCNT}, {0x18u, Opcode::S_WAITCNT},
{0x19u, Opcode::S_WAITCNT}, {0x1au, Opcode::S_WAITCNT},
{0x1bu, Opcode::S_SUBVECTOR_LOOP_BEGIN}, {0x1cu, Opcode::S_SUBVECTOR_LOOP_END},
};
constexpr OpcodeMap SOPP_OPCODE_LIST[] = {
@@ -129,7 +132,6 @@ void DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t sdst = (word >> 16u) & 0x7fu;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOP1;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOP1_OPS, opcode);
@@ -169,7 +171,6 @@ void DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t sdst = (word >> 16u) & 0x7fu;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOP2;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOP2_OPS, opcode);
@@ -194,7 +195,6 @@ void DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
: static_cast<int32_t>(static_cast<int16_t>(word & 0xffffu));
inst.pc = pc;
inst.word = word;
inst.family = Family::SOPK;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOPK_OPS, opcode);
@@ -211,6 +211,11 @@ void DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
switch (inst.opcode) {
case Opcode::S_MOVK_I32: DecodeScalarDestination(sdst, pc, inst.dst); return;
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END:
DecodeScalarDestination(sdst, pc, inst.dst);
inst.branch_target = pc + 4u + static_cast<uint32_t>(imm * 4);
return;
case Opcode::S_WAITCNT: {
const uint32_t waitcnt = word & 0xffffu;
inst.dst.kind = OperandKind::Null;
@@ -250,7 +255,6 @@ void DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t opcode = (word >> 16u) & 0x7fu;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOPC;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOPC_OPS, opcode);
@@ -272,7 +276,6 @@ void DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t simm = word & 0xffffu;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOPP;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOPP_OPS, opcode);
@@ -288,8 +291,8 @@ void DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.opcode == Opcode::S_TTRACEDATA || inst.opcode == Opcode::S_INST_PREFETCH)
? 1
: 0;
inst.branch_offset = static_cast<int32_t>(static_cast<int16_t>(simm)) * 4;
inst.branch_target = pc + 4u + static_cast<uint32_t>(inst.branch_offset);
const auto branch_offset = static_cast<int32_t>(static_cast<int16_t>(simm)) * 4;
inst.branch_target = pc + 4u + static_cast<uint32_t>(branch_offset);
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::UNSUPPORTED) {
@@ -25,19 +25,6 @@ bool HasLiteral(const Instruction& inst) {
inst.src3.kind == OperandKind::LiteralConstant;
}
bool IsControlFlowBranch(Opcode opcode) {
switch (opcode) {
case Opcode::S_BRANCH:
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;
default: return false;
}
}
void ApplyLiteral(Operand& operand, uint32_t literal) {
if (operand.kind == OperandKind::LiteralConstant) {
operand.value = literal;
@@ -47,7 +34,7 @@ void ApplyLiteral(Operand& operand, uint32_t literal) {
std::string RawWordsToString(const Instruction& inst) {
std::string text;
for (uint32_t i = 0; i < inst.raw_count; i++) {
for (uint32_t i = 0; i < inst.word_count; i++) {
if (i != 0) {
text += " ";
}
@@ -198,6 +185,24 @@ std::string FormatExp(const Instruction& inst) {
} // namespace
bool IsConditionalBranch(Opcode opcode) {
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:
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END: return true;
default: return false;
}
}
bool IsDirectBranch(Opcode opcode) {
return opcode == Opcode::S_BRANCH || IsConditionalBranch(opcode);
}
const char* ImageDimensionToString(ImageDimension dimension) {
switch (dimension) {
case ImageDimension::Dim1D: return "1d";
@@ -234,8 +239,7 @@ void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand) {
if (code >= 240u && code <= 247u) {
constexpr float values[] = {0.5f, -0.5f, 1.0f, -1.0f, 2.0f, -2.0f, 4.0f, -4.0f};
operand.kind = OperandKind::FloatInlineConstant;
operand.float_val = values[code - 240u];
operand.value = FloatBits(operand.float_val);
operand.value = FloatBits(values[code - 240u]);
return;
}
if (code >= 256u && code <= 511u) {
@@ -253,8 +257,7 @@ void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand) {
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
case 248u:
operand.kind = OperandKind::FloatInlineConstant;
operand.float_val = 0.15915494309189535f;
operand.value = FloatBits(operand.float_val);
operand.value = FloatBits(0.15915494309189535f);
return;
case 251u: operand.kind = OperandKind::VccZ; return;
case 252u: operand.kind = OperandKind::ExecZ; return;
@@ -307,8 +310,7 @@ void ReadLiteralOperands(std::span<const uint32_t> code, uint32_t word_index, In
void SetRawWords(Instruction& inst, std::span<const uint32_t> code, uint32_t word_index,
uint32_t word_count) {
inst.word_count = word_count;
inst.raw_count = word_count;
for (uint32_t i = 0; i < inst.raw_count; i++) {
for (uint32_t i = 0; i < inst.word_count; i++) {
inst.raw[i] = code[word_index + i];
}
}
@@ -392,7 +394,7 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
const auto& inst = program.instructions.back();
word_index += inst.word_count;
if (IsControlFlowBranch(inst.opcode)) {
if (IsDirectBranch(inst.opcode)) {
const auto target_index = inst.branch_target / sizeof(uint32_t);
if (branch_targets.empty()) {
branch_targets.resize(code.size());
@@ -415,7 +417,9 @@ std::string OperandToString(const Operand& operand) {
case OperandKind::IntegerInlineConstant:
text = fmt::format("{}", operand.signed_val);
break;
case OperandKind::FloatInlineConstant: text = fmt::format("{:f}", operand.float_val); break;
case OperandKind::FloatInlineConstant:
text = fmt::format("{:f}", std::bit_cast<float>(operand.value));
break;
case OperandKind::Sgpr: text = fmt::format("s{}", operand.reg); break;
case OperandKind::Vgpr: text = fmt::format("v{}", operand.reg); break;
case OperandKind::VccLo: text = "vcc_lo"; break;
@@ -483,9 +487,11 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::S_BITSET0_B64:
case Opcode::S_BITSET1_B64:
case Opcode::S_NOT_B64:
case Opcode::S_WQM_B32:
case Opcode::S_WQM_B64:
case Opcode::S_QUADMASK_B64:
case Opcode::S_AND_SAVEEXEC_B32:
case Opcode::S_ORN2_SAVEEXEC_B32:
case Opcode::S_ANDN1_SAVEEXEC_B32:
case Opcode::S_AND_SAVEEXEC_B64:
case Opcode::S_ORN2_SAVEEXEC_B64:
@@ -532,6 +538,11 @@ std::string InstructionToString(const Instruction& inst) {
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: {} 0x{:08x}", inst.pc,
magic_enum::enum_name(inst.opcode),
inst.branch_target));
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END:
return WithUnsupportedReason(inst, fmt::format(
"0x{:08x}: {} {}, 0x{:08x}", inst.pc, magic_enum::enum_name(inst.opcode),
OperandToString(inst.dst), inst.branch_target));
case Opcode::EXP: return WithUnsupportedReason(inst, FormatExp(inst));
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_STORE:
@@ -627,6 +638,8 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_ADD_RTN_U32:
case Opcode::DS_SUB_U32:
case Opcode::DS_SUB_RTN_U32:
case Opcode::DS_INC_RTN_U32:
case Opcode::DS_DEC_RTN_U32:
case Opcode::DS_MIN_I32:
case Opcode::DS_MIN_RTN_I32:
case Opcode::DS_MAX_I32:
@@ -53,13 +53,17 @@ enum class Opcode {
S_QUADMASK_B64,
S_GETPC_B64,
S_SETPC_B64,
S_SUBVECTOR_LOOP_BEGIN,
S_SUBVECTOR_LOOP_END,
S_AND_SAVEEXEC_B32,
S_ORN2_SAVEEXEC_B32,
S_ANDN1_SAVEEXEC_B32,
S_AND_SAVEEXEC_B64,
S_ORN2_SAVEEXEC_B64,
S_ANDN1_SAVEEXEC_B64,
S_NOT_B32,
S_NOT_B64,
S_WQM_B32,
S_WQM_B64,
S_ADD_U32,
S_ADDC_U32,
@@ -488,6 +492,8 @@ enum class Opcode {
DS_ADD_RTN_U32,
DS_SUB_U32,
DS_SUB_RTN_U32,
DS_INC_RTN_U32,
DS_DEC_RTN_U32,
DS_MIN_I32,
DS_MIN_RTN_I32,
DS_MAX_I32,
@@ -634,7 +640,6 @@ struct Operand {
OperandKind kind = OperandKind::Unknown;
uint32_t value = 0;
int32_t signed_val = 0;
float float_val = 0.0f;
uint32_t reg = 0;
uint32_t sdwa_sel = 6;
// Native 16-bit destinations use the same selector fields internally but preserve the
@@ -659,10 +664,8 @@ struct Operand {
struct Instruction {
uint32_t pc = 0;
uint32_t word = 0;
uint32_t word_count = 1;
uint32_t raw[MaxInstructionRawWords] = {};
uint32_t raw_count = 1;
Family family = Family::Unknown;
uint32_t opcode_id = 0;
Opcode opcode = Opcode::UNKNOWN;
@@ -696,7 +699,6 @@ struct Instruction {
bool idxen = false;
bool offen = false;
bool image_r128 = false;
int32_t branch_offset = 0;
uint32_t branch_target = 0;
struct {
uint32_t target = 0;
@@ -718,6 +720,8 @@ Family GetInstructionFamily(uint32_t word);
// The output object must be freshly initialized.
void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst);
void DecodeProgram(std::span<const uint32_t> code, Program& program);
bool IsConditionalBranch(Opcode opcode);
bool IsDirectBranch(Opcode opcode);
void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand);
void DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand);
@@ -714,57 +714,7 @@ void DecodeVop1Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
ReadLiteralOperands(code, word_index, inst);
}
using Vop1ModifierDecodeFn = void (*)(uint32_t pc, std::span<const uint32_t> code,
uint32_t word_index, uint32_t opcode, uint32_t vdst,
Instruction& inst);
struct Vop1ModifierDecoder {
uint32_t escape = 0;
Vop1ModifierDecodeFn decode = nullptr;
};
constexpr Vop1ModifierDecoder VOP1_MODIFIER_DECODERS[] = {
{249u, DecodeVop1Sdwa},
{250u, DecodeVop1Dpp},
};
bool TryDecodeVop1Modifier(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
uint32_t src0, uint32_t opcode, uint32_t vdst, Instruction& inst) {
for (const auto& decoder: VOP1_MODIFIER_DECODERS) {
if (decoder.escape == src0) {
decoder.decode(pc, code, word_index, opcode, vdst, inst);
return true;
}
}
return false;
}
bool IsVop2FloatSourceOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::V_ADD_F32:
case Opcode::V_SUB_F32:
case Opcode::V_SUBREV_F32:
case Opcode::V_MUL_F32:
case Opcode::V_MIN_F32:
case Opcode::V_MAX_F32:
case Opcode::V_MAC_F32:
case Opcode::V_MADMK_F32:
case Opcode::V_MADAK_F32:
case Opcode::V_CVT_PKRTZ_F16_F32:
case Opcode::V_ADD_F16:
case Opcode::V_SUB_F16:
case Opcode::V_SUBREV_F16:
case Opcode::V_MUL_F16:
case Opcode::V_FMAC_F16:
case Opcode::V_FMAMK_F16:
case Opcode::V_FMAAK_F16:
case Opcode::V_MAX_F16:
case Opcode::V_MIN_F16: return true;
default: return false;
}
}
bool IsVop2FloatResultOpcode(Opcode opcode) {
bool IsVop2FloatOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::V_ADD_F32:
case Opcode::V_SUB_F32:
@@ -975,12 +925,12 @@ bool ValidateVop2Sdwa(Instruction& inst, uint32_t opcode, const Vop2SdwaFields&
SetUnsupported(inst, Family::VOP2, opcode, "VOP2 SDWA selector is invalid");
return false;
}
if ((fields.clamp != 0u || fields.omod != 0u) && !IsVop2FloatResultOpcode(inst.opcode)) {
if ((fields.clamp != 0u || fields.omod != 0u) && !IsVop2FloatOpcode(inst.opcode)) {
SetUnsupported(inst, Family::VOP2, opcode, "VOP2 SDWA output modifiers are not supported");
return false;
}
if (IsFullWidthVop2Sdwa(fields)) {
if (!IsVop2FloatSourceOpcode(inst.opcode) && inst.opcode != Opcode::V_CNDMASK_B32 &&
if (!IsVop2FloatOpcode(inst.opcode) && inst.opcode != Opcode::V_CNDMASK_B32 &&
(fields.src0_neg != 0u || fields.src0_abs != 0u || fields.src1_neg != 0u ||
fields.src1_abs != 0u)) {
SetUnsupported(inst, Family::VOP2, opcode,
@@ -1098,7 +1048,7 @@ void DecodeVop2Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
inst.src1.negate_hi = inst.src1.negate;
}
if (!IsVop2FloatSourceOpcode(inst.opcode) && !packed_fmac &&
if (!IsVop2FloatOpcode(inst.opcode) && !packed_fmac &&
(inst.src0.negate || inst.src0.absolute || inst.src1.negate || inst.src1.absolute)) {
SetUnsupported(inst, Family::VOP2, opcode,
"VOP2 DPP integer source modifiers are not supported");
@@ -1107,32 +1057,6 @@ void DecodeVop2Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
FinalizeVop2Instruction(code, word_index, inst);
}
using Vop2ModifierDecodeFn = void (*)(uint32_t pc, std::span<const uint32_t> code,
uint32_t word_index, uint32_t opcode, uint32_t vdst,
uint32_t vsrc1, Instruction& inst);
struct Vop2ModifierDecoder {
uint32_t escape = 0;
Vop2ModifierDecodeFn decode = nullptr;
};
constexpr Vop2ModifierDecoder VOP2_MODIFIER_DECODERS[] = {
{249u, DecodeVop2Sdwa},
{250u, DecodeVop2Dpp},
};
bool TryDecodeVop2Modifier(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
uint32_t src0, uint32_t opcode, uint32_t vdst, uint32_t vsrc1,
Instruction& inst) {
for (const auto& decoder: VOP2_MODIFIER_DECODERS) {
if (decoder.escape == src0) {
decoder.decode(pc, code, word_index, opcode, vdst, vsrc1, inst);
return true;
}
}
return false;
}
struct VopcSdwaFields {
uint32_t src0 = 0;
uint32_t sdst = 0;
@@ -1534,7 +1458,6 @@ void DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t vsrc1 = (word >> 9u) & 0xffu;
inst.pc = pc;
inst.word = word;
inst.family = Family::VOP2;
inst.opcode_id = opcode;
inst.opcode = LookupVop2Opcode(opcode);
@@ -1544,8 +1467,10 @@ void DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
SetUnsupported(inst, Family::VOP2, opcode, "VOP2 opcode is not implemented");
return;
}
if (TryDecodeVop2Modifier(pc, code, word_index, src0, opcode, vdst, vsrc1, inst)) {
return;
switch (src0) {
case 249u: DecodeVop2Sdwa(pc, code, word_index, opcode, vdst, vsrc1, inst); return;
case 250u: DecodeVop2Dpp(pc, code, word_index, opcode, vdst, vsrc1, inst); return;
default: break;
}
DecodeVectorGpr(vdst, inst.dst);
@@ -1562,7 +1487,6 @@ void DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t vdst = (word >> 17u) & 0xffu;
inst.pc = pc;
inst.word = word;
inst.family = Family::VOP1;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(VOP1_OPS, opcode);
@@ -1577,8 +1501,10 @@ void DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.src_count = 0;
return;
}
if (TryDecodeVop1Modifier(pc, code, word_index, src0, opcode, vdst, inst)) {
return;
switch (src0) {
case 249u: DecodeVop1Sdwa(pc, code, word_index, opcode, vdst, inst); return;
case 250u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
default: break;
}
const bool scalar_dst = UsesScalarDestination(inst.opcode);
if (scalar_dst) {
@@ -1599,7 +1525,6 @@ void DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t vsrc1 = (word >> 9u) & 0xffu;
inst.pc = pc;
inst.word = word;
inst.family = Family::VOPC;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(VOPC_OPS, opcode);
@@ -1638,7 +1563,6 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const uint32_t neg = (word1 >> 29u) & 0x7u;
inst.pc = pc;
inst.word = word0;
inst.family = Family::VOP3;
inst.opcode_id = opcode;
inst.opcode = LookupVop3Opcode(opcode);
@@ -1792,7 +1716,6 @@ void DecodeVop3p(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
const uint32_t neg = (word1 >> 29u) & 0x7u;
inst.pc = pc;
inst.word = word0;
inst.family = Family::VOP3P;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(VOP3P_OPS, opcode);
@@ -1842,7 +1765,6 @@ void DecodeVintrp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_ind
const uint32_t vsrc = word & 0xffu;
inst.pc = pc;
inst.word = word;
inst.word_count = 1;
inst.family = Family::VINTRP;
inst.opcode_id = opcode;
@@ -22,29 +22,63 @@ Decoder::Operand ConditionOperand(Decoder::OperandKind kind) {
} // namespace
void Translator::S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin) {
const auto zero = IR::U32(IR::Value(0u));
const auto lo = ir.GetExecLo();
const auto hi = ir.GetExecHi();
const auto saved = ReadU32(inst.dst);
if (begin) {
const auto low_active = ir.INotEqual(lo, zero);
instruction_branch_condition = ir.IEqual(ir.BitwiseOr(lo, hi), zero);
WriteRawU32(inst.dst, ir.Select(instruction_branch_condition, saved,
ir.Select(low_active, hi, lo)));
// Keep the ISA assignment order: SDST may itself name an EXEC half.
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecHi),
ir.Select(low_active, zero, ir.GetExecHi()));
} else {
const auto high_active = ir.INotEqual(hi, zero);
instruction_branch_condition =
ir.LogicalAnd(ir.LogicalNot(high_active), ir.INotEqual(saved, zero));
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecHi),
ir.Select(instruction_branch_condition, saved, hi));
WriteRawU32(inst.dst,
ir.Select(instruction_branch_condition, lo, ReadU32(inst.dst)));
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo),
ir.Select(high_active, saved,
ir.Select(instruction_branch_condition, zero, ir.GetExecLo())));
}
}
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
bool negate_exec, bool negate_source, bool write_64) {
if (!write_64) {
// Read the encoded scalar word and preserve EXEC_HI, including in wave32.
const auto old = ir.GetExecLo();
const auto src = ReadU32(inst.src0);
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
IR::U32 result;
switch (operation) {
case IR::ValueOpcode::LogicalAnd: result = ir.BitwiseAnd(lhs, rhs); break;
case IR::ValueOpcode::LogicalOr: result = ir.BitwiseOr(lhs, rhs); break;
default: EXIT("unsupported SAVEEXEC operation");
}
WriteRawU32(inst.dst, old);
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result);
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
return;
}
const auto old = ir.GetExec();
const auto src = ReadMask(inst.src0);
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
if (write_64) {
WriteMask(inst.dst, old, true);
} else {
WriteRawU32(inst.dst, ir.GetExecLo());
if (current_wave_size == 64u) {
const auto low_half =
ir.ULessThan(IR::U32(ir.Emit(IR::ValueOpcode::LaneId)), IR::U32(IR::Value(32u)));
result = IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {low_half, result, old}));
}
}
const auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
WriteMask(inst.dst, old, true);
const auto mask = BallotMask(result);
ir.SetExec(result);
ir.SetExecLo(mask[0]);
ir.SetExecHi(mask[1]);
ir.SetScc(
ir.INotEqual(write_64 ? ir.BitwiseOr(mask[0], mask[1]) : mask[0], IR::U32(IR::Value(0u))));
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
}
void Translator::ADD_U32(const Decoder::Instruction& inst, bool vector, bool use_carry_in) {
@@ -267,10 +301,19 @@ void Translator::S_MOV_B64(const Decoder::Instruction& inst) {
}
}
void Translator::S_WQM_B64(const Decoder::Instruction& inst) {
const auto mask_valid = ReadMaskValid(inst.src0);
const auto result =
IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {ReadOperand(inst.src0, IR::Type::U64)}));
void Translator::S_WQM(const Decoder::Instruction& inst, bool wide) {
const auto source = wide ? ReadU64(inst.src0)
: ir.ConstructU64(ReadU32(inst.src0), IR::U32(IR::Value(0u)));
const auto result = IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {source}));
if (!wide) {
const auto low = ir.CompositeExtract(result, 0);
// A 32-bit write preserves the other EXEC/VCC half and invalidates any
// overlapping SGPR-pair mask provenance through the ordinary scalar path.
WriteRawU32(inst.dst, low);
ir.SetScc(ir.INotEqual(low, IR::U32(IR::Value(0u))));
return;
}
const auto mask_valid = ReadMaskValid(inst.src0);
WriteOperand(DestinationOperand(inst), result);
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
@@ -328,7 +371,7 @@ void Translator::V_READFIRSTLANE_B32(const Decoder::Instruction& inst) {
}
void Translator::V_READLANE_B32(const Decoder::Instruction& inst) {
const auto lane_mask = IR::U32(IR::Value(current_wave_size == 32u ? 31u : 63u));
const auto lane_mask = IR::U32(IR::Value(program.wave_size == 32u ? 31u : 63u));
const auto lane = ir.BitwiseAnd(ReadU32(inst.src1), lane_mask);
WriteOperand(DestinationOperand(inst),
ir.Emit(IR::ValueOpcode::ReadLane, {ReadU32(inst.src0), lane}));
@@ -336,7 +379,7 @@ void Translator::V_READLANE_B32(const Decoder::Instruction& inst) {
void Translator::V_WRITELANE_B32(const Decoder::Instruction& inst) {
EXIT_IF(inst.dst.kind != Decoder::OperandKind::Vgpr);
const auto lane_mask = IR::U32(IR::Value(current_wave_size == 32u ? 31u : 63u));
const auto lane_mask = IR::U32(IR::Value(program.wave_size == 32u ? 31u : 63u));
const auto reg = static_cast<IR::VectorReg>(inst.dst.reg);
const auto lane = ir.BitwiseAnd(ReadU32(inst.src1), lane_mask);
const auto result = IR::U32(
@@ -89,10 +89,10 @@ void Translator::V_CVT_16_F16(const Decoder::Instruction& inst, bool signed_valu
if (signed_value) {
const auto converted =
ConvertF32ToI32Saturated(value, -32768.0f, 32768.0f, 32767.0f, 0xffff8000u, 0x7fffu);
WriteU16(DestinationOperand(inst), ir.BitwiseAnd(converted, IR::U32(IR::Value(0xffffu))));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(converted, IR::U32(IR::Value(0xffffu))));
return;
}
WriteU16(DestinationOperand(inst),
Write16Bits(DestinationOperand(inst),
ConvertF32ToU32Saturated(value, 65536.0f, 65535.0f, 0xffffu));
}
@@ -59,7 +59,7 @@ bool Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode
result = ApplyF32ResultModifiers(inst.dst, result);
const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f)));
const auto invalid = IR::U32(IR::Value(inst.dst.clamp ? 0u : 0xfe00u));
WriteU16(DestinationOperand(inst), ir.Select(negative, invalid, bits));
Write16Bits(DestinationOperand(inst), ir.Select(negative, invalid, bits));
return true;
}
@@ -95,7 +95,7 @@ bool Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode o
result = ApplyF32ResultModifiers(inst.dst, result);
const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f)));
const auto invalid = IR::U32(IR::Value(inst.dst.clamp ? 0u : 0xfe00u));
WriteU16(DestinationOperand(inst), ir.Select(infinite, invalid, bits));
Write16Bits(DestinationOperand(inst), ir.Select(infinite, invalid, bits));
return true;
}
@@ -111,7 +111,7 @@ bool Translator::Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcod
bool accumulator, bool mix) {
std::array<IR::Value, 3> args;
for (uint32_t index = 0; index < args.size(); index++) {
const auto operand = accumulator && index == 2u ? inst.dst : SourceAt(inst, index);
const auto& operand = accumulator && index == 2u ? inst.dst : SourceAt(inst, index);
args[index] = mix ? IR::Value(ReadMixF32(operand)) : IR::Value(ReadF16AsF32(operand));
}
WriteF16(DestinationOperand(inst), IR::F32(ir.Emit(opcode, {args[0], args[1], args[2]})));
@@ -128,7 +128,7 @@ bool Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode o
bool reverse) {
std::array<IR::Value, 2> args;
for (uint32_t index = 0; index < args.size(); index++) {
const auto operand = SourceAt(inst, reverse ? 1u - index : index);
const auto& operand = SourceAt(inst, reverse ? 1u - index : index);
args[index] = ReadOperand(operand, IR::ArgTypeOf(opcode, index));
}
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {args[0], args[1]}));
@@ -139,7 +139,7 @@ bool Translator::FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode
bool accumulator, bool mix) {
std::array<IR::Value, 3> args;
for (uint32_t index = 0; index < args.size(); index++) {
const auto operand = accumulator && index == 2u ? inst.dst : SourceAt(inst, index);
const auto& operand = accumulator && index == 2u ? inst.dst : SourceAt(inst, index);
const auto type = IR::ArgTypeOf(opcode, index);
args[index] = type == IR::Type::F32 && mix ? IR::Value(ReadMixF32(operand))
: ReadOperand(operand, type);
@@ -203,9 +203,6 @@ bool Translator::V_CUBEMA_F32(const Decoder::Instruction& inst) {
}
bool Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kind) {
const auto select_f32 = [&](IR::U1 condition, IR::F32 true_value, IR::F32 false_value) {
return IR::F32(ir.Emit(IR::ValueOpcode::SelectF32, {condition, true_value, false_value}));
};
const auto x = ReadMixF32(inst.src0);
const auto y = ReadMixF32(inst.src1);
const auto z = ReadMixF32(inst.src2);
@@ -223,18 +220,18 @@ bool Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kin
const auto y_neg = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThan32, {y, IR::Value::F32(0.0f)}));
const auto z_neg = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThan32, {z, IR::Value::F32(0.0f)}));
const auto select_face = [&](IR::F32 x_value, IR::F32 y_value, IR::F32 z_value) {
return select_f32(z_face, z_value, select_f32(y_face, y_value, x_value));
return SelectF32(z_face, z_value, SelectF32(y_face, y_value, x_value));
};
IR::F32 result;
switch (result_kind) {
case 0u:
result = select_face(
select_f32(x_neg, IR::F32(IR::Value::F32(1.0f)), IR::F32(IR::Value::F32(0.0f))),
select_f32(y_neg, IR::F32(IR::Value::F32(3.0f)), IR::F32(IR::Value::F32(2.0f))),
select_f32(z_neg, IR::F32(IR::Value::F32(5.0f)), IR::F32(IR::Value::F32(4.0f))));
SelectF32(x_neg, IR::F32(IR::Value::F32(1.0f)), IR::F32(IR::Value::F32(0.0f))),
SelectF32(y_neg, IR::F32(IR::Value::F32(3.0f)), IR::F32(IR::Value::F32(2.0f))),
SelectF32(z_neg, IR::F32(IR::Value::F32(5.0f)), IR::F32(IR::Value::F32(4.0f))));
break;
case 1u: result = select_face(select_f32(x_neg, z, nz), x, select_f32(z_neg, nx, x)); break;
case 2u: result = select_face(ny, select_f32(y_neg, nz, z), ny); break;
case 1u: result = select_face(SelectF32(x_neg, z, nz), x, SelectF32(z_neg, nx, x)); break;
case 2u: result = select_face(ny, SelectF32(y_neg, nz, z), ny); break;
case 3u: {
const auto two = IR::F32(IR::Value::F32(2.0f));
result = select_face(IR::F32(ir.Emit(IR::ValueOpcode::FPMul32, {x, two})),
@@ -9,7 +9,7 @@ bool Translator::Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcod
const auto value = ReadU16AsU32(inst.src1, arithmetic);
const auto count = ir.BitwiseAnd(ReadU16AsU32(inst.src0, false), IR::U32(IR::Value(15u)));
const auto result = IR::U32(ir.Emit(opcode, {value, count}));
WriteU16(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
return true;
}
@@ -18,7 +18,7 @@ bool Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco
const auto lhs = ReadU16AsU32(inst.src0, sign);
const auto rhs = ReadU16AsU32(inst.src1, sign);
const auto result = IR::U32(ir.Emit(opcode, {lhs, rhs}));
WriteU16(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
return true;
}
@@ -26,7 +26,7 @@ bool Translator::V_MED3_I16(const Decoder::Instruction& inst) {
const auto result = IR::U32(ir.Emit(
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
ReadU16AsU32(inst.src2, true)}));
WriteU16(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
return true;
}
@@ -143,7 +143,7 @@ bool Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
std::array<IR::Value, 3> args;
for (uint32_t index = 0; index < inst.src_count; index++) {
const auto arg_type = IR::ArgTypeOf(opcode, index);
const auto operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type);
if (mask_shift_count && index == 1u) {
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u)));
@@ -563,10 +563,8 @@ bool Translator::PackB16(const Decoder::Instruction& inst, bool high0, bool high
: ReadU32(inst.src0);
const auto hi = high1 ? ir.ShiftRightLogical(ReadU32(inst.src1), IR::U32(IR::Value(16u)))
: ReadU32(inst.src1);
const auto low_bits = ir.BitwiseAnd(lo, IR::U32(IR::Value(0xffffu)));
const auto high_bits = ir.ShiftLeftLogical(ir.BitwiseAnd(hi, IR::U32(IR::Value(0xffffu))),
IR::U32(IR::Value(16u)));
WriteOperand(DestinationOperand(inst), ir.BitwiseOr(low_bits, high_bits));
const auto result = PackU16Lanes(lo, hi);
WriteOperand(DestinationOperand(inst), result);
return true;
}
@@ -71,16 +71,22 @@ ResourceKind FlatSegmentResourceKind(uint32_t segment) {
}
}
bool IsScalarAddressLoad(Decoder::Opcode opcode) {
switch (opcode) {
case Decoder::Opcode::S_LOAD_DWORD:
case Decoder::Opcode::S_LOAD_DWORDX2:
case Decoder::Opcode::S_LOAD_DWORDX4:
case Decoder::Opcode::S_LOAD_DWORDX8:
case Decoder::Opcode::S_LOAD_DWORDX16: return true;
default: return false;
}
}
ResourceKind MemoryKind(const Decoder::Instruction& decoded) {
switch (decoded.family) {
case Decoder::Family::SMEM:
return decoded.opcode == Decoder::Opcode::S_LOAD_DWORD ||
decoded.opcode == Decoder::Opcode::S_LOAD_DWORDX2 ||
decoded.opcode == Decoder::Opcode::S_LOAD_DWORDX4 ||
decoded.opcode == Decoder::Opcode::S_LOAD_DWORDX8 ||
decoded.opcode == Decoder::Opcode::S_LOAD_DWORDX16
? ResourceKind::ScalarAddress
: ResourceKind::ScalarBuffer;
return IsScalarAddressLoad(decoded.opcode) ? ResourceKind::ScalarAddress
: ResourceKind::ScalarBuffer;
case Decoder::Family::MUBUF:
case Decoder::Family::MTBUF: return ResourceKind::Buffer;
case Decoder::Family::FLAT: return FlatSegmentResourceKind(decoded.memory_segment);
@@ -146,16 +152,6 @@ IR::MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded) {
return memory;
}
bool IsScalarAddressLoad(Decoder::Opcode opcode) {
switch (opcode) {
case Decoder::Opcode::S_LOAD_DWORD:
case Decoder::Opcode::S_LOAD_DWORDX2:
case Decoder::Opcode::S_LOAD_DWORDX4:
case Decoder::Opcode::S_LOAD_DWORDX8:
case Decoder::Opcode::S_LOAD_DWORDX16: return true;
default: return false;
}
}
bool IsScalarBufferLoad(Decoder::Opcode opcode) {
switch (opcode) {
@@ -290,15 +286,15 @@ IR::Value Translator::GetAddressResource(IR::Value low, IR::Value high) {
Translator::AddressOperands Translator::ReadAddressOperands(const Decoder::Instruction& inst,
uint32_t first_source) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto kind = MemoryKind(inst);
const auto low = ReadU32(MemorySourceAt(inst, first_source));
const auto high_or_base = MemorySourceAt(inst, first_source + 1u);
if (memory.kind == IR::ResourceKind::Scratch) {
if (kind == IR::ResourceKind::Scratch) {
const auto offset =
high_or_base.kind != Decoder::OperandKind::Vgpr ? ReadU32(high_or_base) : low;
return {ir.Emit(IR::ValueOpcode::GetScratchResource), offset, IR::Value(0u)};
}
if (memory.kind == IR::ResourceKind::Global &&
if (kind == IR::ResourceKind::Global &&
high_or_base.kind != Decoder::OperandKind::Vgpr) {
const auto base_low = ReadU32(high_or_base);
const auto base_high = ReadU32(OffsetOperand(high_or_base, 1u));
@@ -329,18 +325,17 @@ IR::Value Translator::GetSamplerResource(const IR::MemoryInfo& memory) {
IR::Value Translator::MakeImageAddress(const Decoder::Instruction& inst,
const Decoder::Operand& base) {
const auto memory = MemoryInfoFromDecoded(inst);
std::array<IR::Value, 13> components {};
components.fill(IR::Value(0u));
const auto count =
Decoder::ImageAddressDwordCount(memory.image_sample_flags, memory.image_address_components);
Decoder::ImageAddressDwordCount(inst.image_sample_flags, inst.image_address_components);
EXIT_IF(count > components.size());
const auto nsa_components =
std::min(memory.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
for (uint32_t index = 0; index < count; index++) {
if (index != 0u && index - 1u < nsa_components) {
components[index] =
ir.GetVectorReg(static_cast<IR::VectorReg>(memory.image_nsa_addr[index - 1u]));
ir.GetVectorReg(static_cast<IR::VectorReg>(inst.image_nsa_addr[index - 1u]));
} else {
components[index] = ReadRawU32(OffsetOperand(PlainOperand(base), index));
}
@@ -384,11 +379,10 @@ void Translator::WriteImageComponents(const Decoder::Operand& dst, IR::Value val
Translator::BufferAddress Translator::ReadBufferAddress(const Decoder::Instruction& inst,
uint32_t first_source) {
const auto memory = MemoryInfoFromDecoded(inst);
uint32_t cursor = first_source;
const auto next = [&]() { return ReadU32(MemorySourceAt(inst, cursor++)); };
const auto index = memory.idxen ? next() : IR::U32(IR::Value(0u));
const auto offset = memory.offen ? next() : IR::U32(IR::Value(0u));
const auto index = inst.idxen ? next() : IR::U32(IR::Value(0u));
const auto offset = inst.offen ? next() : IR::U32(IR::Value(0u));
const auto soffset = next();
return {index, offset, soffset};
}
@@ -974,6 +968,10 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicISub32, false);
case Decoder::Opcode::DS_SUB_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicISub32, true);
case Decoder::Opcode::DS_INC_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicInc32, true);
case Decoder::Opcode::DS_DEC_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicDec32, true);
case Decoder::Opcode::DS_MIN_I32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicSMin32, false);
case Decoder::Opcode::DS_MIN_RTN_I32:
@@ -8,9 +8,12 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_MOV_B32:
case O::S_MOVK_I32: MOV_B32(inst, false); return true;
case O::S_MOV_B64: S_MOV_B64(inst); return true;
case O::S_WQM_B64: S_WQM_B64(inst); return true;
case O::S_WQM_B32: S_WQM(inst, false); return true;
case O::S_WQM_B64: S_WQM(inst, true); return true;
case O::S_GETPC_B64: S_GETPC_B64(inst); return true;
case O::S_SETPC_B64: return true;
case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return true;
case O::S_SUBVECTOR_LOOP_END: S_SUBVECTOR_LOOP(inst, false); return true;
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return true;
case O::S_CSELECT_B64: ScalarSelect64(inst, inst.src1); return true;
case O::S_CMOV_B64: ScalarSelect64(inst, inst.dst); return true;
@@ -23,6 +26,9 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_ANDN1_SAVEEXEC_B32:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, true, false);
return true;
case O::S_ORN2_SAVEEXEC_B32:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalOr, true, false, false);
return true;
case O::S_AND_SAVEEXEC_B64:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, false, true);
return true;
@@ -9,7 +9,7 @@
namespace Libs::Graphics::ShaderRecompiler::Frontend {
Decoder::Operand Translator::SourceAt(const Decoder::Instruction& inst, uint32_t index) {
const Decoder::Operand& Translator::SourceAt(const Decoder::Instruction& inst, uint32_t index) {
switch (index) {
case 0: return inst.src0;
case 1: return inst.src1;
@@ -25,7 +25,7 @@ Decoder::Operand Translator::DestinationOperand(const Decoder::Instruction& inst
return destination;
}
for (uint32_t index = 0; index < std::min(inst.src_count, 3u); index++) {
const auto source = SourceAt(inst, index);
const auto& source = SourceAt(inst, index);
if (!source.dpp) {
continue;
}
@@ -112,7 +112,7 @@ Decoder::Operand Translator::PlainOperand(const Decoder::Operand& operand) {
std::array<IR::U32, 2> Translator::BallotMask(IR::U1 value) {
const auto mask = ir.Emit(IR::ValueOpcode::Ballot, {value});
return {ir.CompositeExtract(mask, 0),
current_wave_size == 64u ? ir.CompositeExtract(mask, 1) : IR::U32(IR::Value(0u))};
program.wave_size == 64u ? ir.CompositeExtract(mask, 1) : IR::U32(IR::Value(0u))};
}
IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
@@ -134,11 +134,15 @@ IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
case Decoder::OperandKind::Scc:
return ir.Select(ir.GetScc(), IR::U32(IR::Value(1u)), IR::U32(IR::Value(0u)));
case Decoder::OperandKind::VccZ:
return ir.Select(ir.LogicalNot(ir.GetVcc()), IR::U32(IR::Value(1u)),
IR::U32(IR::Value(0u)));
case Decoder::OperandKind::ExecZ:
return ir.Select(ir.LogicalNot(ir.GetExec()), IR::U32(IR::Value(1u)),
IR::U32(IR::Value(0u)));
case Decoder::OperandKind::ExecZ: {
const bool vcc = operand.kind == Decoder::OperandKind::VccZ;
auto mask = vcc ? ir.GetVccLo() : ir.GetExecLo();
if (program.wave_size == 64u) {
mask = ir.BitwiseOr(mask, vcc ? ir.GetVccHi() : ir.GetExecHi());
}
const auto zero = IR::U32(IR::Value(0u));
return ir.Select(ir.IEqual(mask, zero), IR::U32(IR::Value(1u)), zero);
}
default: EXIT("invalid decoded operand used as a raw U32 source");
}
}
@@ -440,10 +444,6 @@ void Translator::WriteF16(const Decoder::Operand& operand, IR::F32 value) {
Write16Bits(operand, bits);
}
void Translator::WriteU16(const Decoder::Operand& operand, IR::U32 value) {
Write16Bits(operand, value);
}
IR::U32 Translator::ReadU32(const Decoder::Operand& operand) {
return IR::U32(ReadOperand(operand, IR::Type::U32));
}
@@ -596,7 +596,7 @@ void Translator::WriteU32Pair(const Decoder::Operand& operand,
IR::U1 Translator::ThreadBit(const std::array<IR::U32, 2>& mask) {
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
const auto word = current_wave_size == 64u
const auto word = program.wave_size == 64u
? ir.Select(ir.ULessThan(lane, IR::U32(IR::Value(32u))), mask[0], mask[1])
: mask[0];
const auto bit = ir.BitwiseAnd(lane, IR::U32(IR::Value(31u)));
@@ -604,10 +604,6 @@ IR::U1 Translator::ThreadBit(const std::array<IR::U32, 2>& mask) {
IR::U32(IR::Value(0u)));
}
IR::U1 Translator::ReadCondition(const Decoder::Operand& operand) {
return IR::U1(ReadOperand(operand, IR::Type::U1));
}
IR::U32 Translator::ConditionBit(const Decoder::Operand& operand) {
return ir.Select(ReadMask(operand), IR::U32(IR::Value(1u)), IR::U32(IR::Value(0u)));
}
@@ -621,7 +617,7 @@ IR::U1 Translator::ReadMask(const Decoder::Operand& operand) {
switch (operand.kind) {
case Decoder::OperandKind::Sgpr: {
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
const auto mask = current_wave_size == 64u
const auto mask = program.wave_size == 64u
? ReadU32Pair(operand)
: std::array {ReadRawU32(operand), IR::U32(IR::Value(0u))};
return IR::U1(ir.Emit(
@@ -632,7 +628,7 @@ IR::U1 Translator::ReadMask(const Decoder::Operand& operand) {
case Decoder::OperandKind::ExecHi: return ir.GetExec();
case Decoder::OperandKind::VccLo:
case Decoder::OperandKind::VccHi:
return current_wave_size == 32u
return program.wave_size == 32u
? ThreadBit({ReadRawU32(operand), IR::U32(IR::Value(0u))})
: ir.GetVcc();
case Decoder::OperandKind::Scc: return ir.GetScc();
@@ -682,7 +678,7 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
}
ir.SetScalarReg(reg, mask[0]);
// A wave32 VALU mask destination must not overwrite the neighboring SGPR.
if ((write_64 || current_wave_size == 64u) &&
if ((write_64 || program.wave_size == 64u) &&
IR::RegIndex(reg) + 1u < IR::NumScalarRegs) {
const auto high = static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u);
ir.SetScalarReg(high, mask[1]);
@@ -700,7 +696,7 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
}
case Decoder::OperandKind::VccLo:
case Decoder::OperandKind::VccHi: {
if (!write_64 && current_wave_size == 32u) {
if (!write_64 && program.wave_size == 32u) {
WriteRawU32(operand, mask[0]);
return mask;
}
@@ -762,6 +758,10 @@ void Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo
case CFG::BranchCondition::VccNonZero: condition = ir.GetVcc(); break;
case CFG::BranchCondition::ExecZero: condition = ir.LogicalNot(ir.GetExec()); break;
case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break;
case CFG::BranchCondition::ScalarInstruction:
EXIT_IF(instruction_branch_condition.IsEmpty());
condition = instruction_branch_condition;
break;
case CFG::BranchCondition::GotoVariable:
if (source.terminator.goto_variable == UINT32_MAX) {
EXIT("block %u reads an invalid goto variable", source.id);
@@ -1117,7 +1117,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
: u32(0);
const auto vertex = entry_ir.IMul(local, step);
const auto first = entry_ir.IAdd(vertex, parity);
const auto second = mesh.InputPrimitiveSize() == 3u
const auto second = mesh.InputPrimitiveSize() >= 2u
? entry_ir.ISub(entry_ir.IAdd(vertex, u32(1)), parity)
: u32(0);
const auto third = mesh.InputPrimitiveSize() == 3u
@@ -1198,7 +1198,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
}
for (const auto& cfg_block: cfg.blocks) {
const auto typed_index = block_indices.at(cfg_block.id);
Translator translator(result, result.blocks[typed_index], vector_limit, options.wave_size);
Translator translator(result, result.blocks[typed_index], vector_limit);
for (uint32_t index = cfg_block.inst_begin; index < cfg_block.inst_end; index++) {
const auto& instruction = decoded.instructions[index];
if (IsCodeTableLoad(cfg, instruction.pc)) {
@@ -9,9 +9,8 @@ namespace Libs::Graphics::ShaderRecompiler::Frontend {
class Translator {
public:
Translator(IR::Program& program, IR::Block* block, uint32_t vector_limit, uint32_t wave_size)
: program(program), ir(block), current_vector_limit(vector_limit),
current_wave_size(wave_size) {}
Translator(IR::Program& program, IR::Block* block, uint32_t vector_limit)
: program(program), ir(block), current_vector_limit(vector_limit) {}
void TranslateInstruction(const Decoder::Instruction& inst);
void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
@@ -19,7 +18,7 @@ public:
void AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info);
private:
Decoder::Operand SourceAt(const Decoder::Instruction& inst, uint32_t index);
const Decoder::Operand& SourceAt(const Decoder::Instruction& inst, uint32_t index);
Decoder::Operand DestinationOperand(const Decoder::Instruction& inst);
Decoder::Operand OffsetOperand(const Decoder::Operand& operand, uint32_t offset);
Decoder::Operand ScalarDestinationOperand(const Decoder::Operand& operand, uint32_t offset);
@@ -36,7 +35,6 @@ private:
IR::U32 PackHalf2x16(IR::F32 low, IR::F32 high);
void Write16Bits(const Decoder::Operand& operand, IR::U32 value);
void WriteF16(const Decoder::Operand& operand, IR::F32 value);
void WriteU16(const Decoder::Operand& operand, IR::U32 value);
IR::U32 ReadU32(const Decoder::Operand& operand);
std::array<IR::U32, 2> ReadU32Pair(const Decoder::Operand& operand);
IR::U64 ReadU64(const Decoder::Operand& operand);
@@ -49,7 +47,6 @@ private:
IR::U32 ReadF16LaneBits(const Decoder::Operand& operand, bool high_lane);
std::array<IR::U32, 2> ExtractU64(IR::U64 value);
void WriteU32Pair(const Decoder::Operand& operand, const std::array<IR::U32, 2>& value);
IR::U1 ReadCondition(const Decoder::Operand& operand);
IR::U32 ConditionBit(const Decoder::Operand& operand);
IR::U1 ReadMask(const Decoder::Operand& operand);
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
@@ -215,6 +212,7 @@ private:
bool V_CNDMASK_B32(const Decoder::Instruction& inst);
bool PackB16(const Decoder::Instruction& inst, bool high0, bool high1);
void S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin);
void S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation, bool negate_exec,
bool negate_source, bool write_64);
void ADD_U32(const Decoder::Instruction& inst, bool vector, bool use_carry_in);
@@ -236,7 +234,7 @@ private:
void ScalarSelect64(const Decoder::Instruction& inst, const Decoder::Operand& false_source);
void MOV_B32(const Decoder::Instruction& inst, bool apply_float_modifiers);
void S_MOV_B64(const Decoder::Instruction& inst);
void S_WQM_B64(const Decoder::Instruction& inst);
void S_WQM(const Decoder::Instruction& inst, bool wide);
void V_MOVRELS_B32(const Decoder::Instruction& inst);
void V_MOVRELD_B32(const Decoder::Instruction& inst);
void V_READFIRSTLANE_B32(const Decoder::Instruction& inst);
@@ -255,10 +253,10 @@ private:
IR::Program& program;
IR::IREmitter ir;
IR::U1 instruction_branch_condition;
Decoder::Opcode current_opcode = Decoder::Opcode::UNKNOWN;
uint32_t current_pc = 0;
uint32_t current_vector_limit = 1;
uint32_t current_wave_size = 64;
};
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
+9 -17
View File
@@ -189,28 +189,27 @@ void ValidateProgram(const Program& program, bool require_ssa) {
program.blocks.size() != program.block_storage.size()) {
return Fail("value IR block storage is inconsistent");
}
std::unordered_set<const Block*> blocks;
std::unordered_map<const Block*, size_t> block_indices;
std::unordered_map<uint32_t, const Block*> blocks_by_id;
std::unordered_set<const Inst*> instructions;
std::unordered_map<const Inst*, size_t> instruction_positions;
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
const auto* block = program.blocks[block_index];
if (block == nullptr || program.block_storage[block_index] == nullptr ||
block != program.block_storage[block_index].get()) {
return Fail("value IR block pointer is inconsistent");
}
if (!blocks.insert(block).second) {
if (!block_indices.emplace(block, block_index).second) {
return Fail("value IR block pointer is duplicated");
}
block_indices.emplace(block, block_index);
if (program.block_info[block_index].id == UINT32_MAX) {
return Fail("value IR block uses the reserved exit id");
}
if (!blocks_by_id.emplace(program.block_info[block_index].id, block).second) {
return Fail("value IR block id is duplicated");
}
size_t position = 0;
for (const auto& inst: *block) {
if (!instructions.insert(&inst).second) {
if (!instruction_positions.emplace(&inst, position++).second) {
return Fail("value IR instruction is duplicated");
}
}
@@ -223,7 +222,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
const auto* block = program.blocks[block_index];
std::unordered_set<const Block*> predecessors;
for (const auto* predecessor: block->ImmPredecessors()) {
if (predecessor == nullptr || !blocks.contains(predecessor)) {
if (predecessor == nullptr || !block_indices.contains(predecessor)) {
return Fail("value IR block has a foreign predecessor");
}
if (!predecessors.insert(predecessor).second) {
@@ -237,7 +236,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
std::unordered_set<const Block*> successors;
for (const auto* successor: block->ImmSuccessors()) {
if (successor == nullptr || !blocks.contains(successor)) {
if (successor == nullptr || !block_indices.contains(successor)) {
return Fail("value IR block has a foreign successor");
}
if (!successors.insert(successor).second) {
@@ -264,7 +263,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
return false;
}
const auto* definition = value.TryInstruction();
return definition == nullptr || instructions.contains(definition);
return definition == nullptr || instruction_positions.contains(definition);
};
switch (terminator.kind) {
case CFG::TerminatorKind::Branch:
@@ -338,7 +337,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
std::unordered_set<const Block*> incoming_blocks;
for (size_t arg_index = 0; arg_index < inst.NumArgs(); arg_index++) {
const auto* predecessor = inst.PhiBlock(arg_index);
if (predecessor == nullptr || !blocks.contains(predecessor) ||
if (predecessor == nullptr || !block_indices.contains(predecessor) ||
!predecessors.contains(predecessor)) {
return Fail("value IR Phi has a foreign or non-predecessor parent");
}
@@ -520,7 +519,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
TypeName(ArgTypeOf(inst.GetOpcode(), arg_index))));
}
if (const auto* definition = arg.TryInstruction();
definition != nullptr && !instructions.contains(definition)) {
definition != nullptr && !instruction_positions.contains(definition)) {
return Fail("value IR argument has a foreign definition");
}
if (const auto* definition = arg.TryInstruction(); definition != nullptr) {
@@ -582,13 +581,6 @@ void ValidateProgram(const Program& program, bool require_ssa) {
}
}
std::unordered_map<const Inst*, size_t> instruction_positions;
for (const auto* block: program.blocks) {
size_t position = 0;
for (const auto& inst: *block) {
instruction_positions.emplace(&inst, position++);
}
}
const auto dominates = [&](const Block* definition, const Block* use) {
return dominators[block_indices.at(use)][block_indices.at(definition)];
};
+1 -1
View File
@@ -415,7 +415,7 @@ struct BindingLayout {
struct ShaderInfo {
static constexpr uint32_t MaxBuffers = 32;
static constexpr uint32_t MaxImages = 32;
static constexpr uint32_t MaxImages = 64;
static constexpr uint32_t MaxSamplers = 32;
static constexpr uint32_t MaxSampledPairs = 64;
@@ -163,6 +163,8 @@ SharedAccess SharedAccessOf(ValueOpcode opcode) {
case ValueOpcode::SharedAtomicSwap32:
case ValueOpcode::SharedAtomicIAdd32:
case ValueOpcode::SharedAtomicISub32:
case ValueOpcode::SharedAtomicInc32:
case ValueOpcode::SharedAtomicDec32:
case ValueOpcode::SharedAtomicSMin32:
case ValueOpcode::SharedAtomicUMin32:
case ValueOpcode::SharedAtomicSMax32:
@@ -255,6 +255,8 @@ VALUE_OPCODE(SharedAtomicFMax32, Void, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSwap32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicIAdd32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicISub32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicInc32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicDec32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSMin32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicUMin32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSMax32, U32, U32, U32, U1)
@@ -280,8 +280,7 @@ private:
return finish(false);
}
}
if (op == ValueOpcode::GetBufferResource || op == ValueOpcode::GetImageResource ||
op == ValueOpcode::GetSamplerResource || op == ValueOpcode::GetAddressResource) {
if (IsDescriptorHandle(op)) {
size_t expected = 4u;
if (op == ValueOpcode::GetImageResource) {
expected = 8u;
@@ -123,46 +123,6 @@ struct DefTable {
std::unordered_map<uint32_t, ValueMap> goto_variables;
};
ValueOpcode UndefOpcode(ScalarReg) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(ThreadBitScalarReg) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(ScalarMaskTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(VectorReg) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(GotoVariable) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(SccTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(ExecTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(ExecLoTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(ExecHiTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(VccTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(VccLoTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(VccHiTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(M0Tag) {
return ValueOpcode::UndefU32;
}
Value InitialValue(ScalarReg) {
return Value(0u);
}
@@ -248,7 +208,7 @@ public:
state.result = def;
} else if (!block->IsSsaSealed()) {
auto& phi = *block->PrependNewInst(block->begin(), ValueOpcode::Phi);
phi.SetFlags(TypeOf(UndefOpcode(variable)));
phi.SetFlags(InitialValue(variable).GetType());
incomplete_phis[block][Variable(variable)] = &phi;
state.result = Value(&phi);
} else if (const auto predecessors = block->ImmPredecessors();
@@ -258,7 +218,7 @@ public:
break;
} else {
auto& phi = *block->PrependNewInst(block->begin(), ValueOpcode::Phi);
phi.SetFlags(TypeOf(UndefOpcode(variable)));
phi.SetFlags(InitialValue(variable).GetType());
Write(variable, block, Value(&phi));
state.phi = &phi;
prepare_phi();
+17 -14
View File
@@ -593,7 +593,6 @@ static void ShaderGetStaticInputInfoPS(
}
ps_info.ps_pos_x = (active_inputs & 0x00000100u) != 0;
ps_info.ps_pos_y = (active_inputs & 0x00000200u) != 0;
ps_info.ps_pos_xy = ps_info.ps_pos_x && ps_info.ps_pos_y;
ps_info.ps_pos_z = (active_inputs & 0x00000400u) != 0;
ps_info.ps_pos_w = (active_inputs & 0x00000800u) != 0;
ps_info.ps_front_face = (active_inputs & 0x00001000u) != 0;
@@ -756,19 +755,9 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
}
return params;
}
EXIT_IF(regs.gs_regs.data_addr == 0);
const auto back = ShaderGetMappedData(regs.gs_regs.data_addr, "ShaderGetInputInfoGS():");
const auto back_params =
GetShaderParams(regs.gs_regs.data_addr, "ShaderRecompiler GS",
GetDeclaredShaderHash(regs.gs_regs.data_addr), {}, back);
params.back_code = back_params.code;
// Merged shaders receive the GS-back user-data pointer in s0:s1, followed
// by the ordinary user SGPRs at s8. Keep both in the runtime register snapshot.
// NGG user SGPRs start at s8; a separately compiled GS back half also receives
// its user-data pointer in s0:s1.
params.user_data.insert(params.user_data.begin(), 8u, 0u);
params.user_data[0] = static_cast<uint32_t>(regs.gs_regs.user_data_addr);
params.user_data[1] = static_cast<uint32_t>(regs.gs_regs.user_data_addr >> 32u);
const uint64_t hashes[] = {params.hash, back_params.hash};
params.hash = XXH3_64bits(hashes, sizeof(hashes));
info = {};
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
auto& mesh = info.mesh;
@@ -777,11 +766,25 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
mesh.max_vertices = sh.m_geMaxOutputPerSubgroup;
mesh.provoking_vertex = context.GetModeControl().provoking_vtx_last ? 2u : 0u;
mesh.lds_size_dwords = static_cast<uint32_t>(regs.gs_regs.rsrc2.lds_size) * 128u;
mesh.scratch_size_dwords = std::max(data.scratch_size_dwords, back.scratch_size_dwords);
mesh.scratch_size_dwords = data.scratch_size_dwords;
if (data.type == Prospero::ShaderBinaryType::kGsFront) {
EXIT_IF(regs.gs_regs.data_addr == 0);
const auto back = ShaderGetMappedData(regs.gs_regs.data_addr, "ShaderGetInputInfoGS():");
const auto back_params =
GetShaderParams(regs.gs_regs.data_addr, "ShaderRecompiler GS",
GetDeclaredShaderHash(regs.gs_regs.data_addr), {}, back);
params.back_code = back_params.code;
params.user_data[0] = static_cast<uint32_t>(regs.gs_regs.user_data_addr);
params.user_data[1] = static_cast<uint32_t>(regs.gs_regs.user_data_addr >> 32u);
const uint64_t hashes[] = {params.hash, back_params.hash};
params.hash = XXH3_64bits(hashes, sizeof(hashes));
mesh.scratch_size_dwords = std::max(mesh.scratch_size_dwords, back.scratch_size_dwords);
}
EXIT_NOT_IMPLEMENTED(regs.gs_regs.rsrc1.gs_vgpr_component_count != 3u ||
regs.gs_regs.rsrc2.es_vgpr_component_count != 3u);
const auto& group = user_config.GetGeControl();
if ((user_config.GetPrimType() != Prospero::PrimitiveType::kPointList &&
user_config.GetPrimType() != Prospero::PrimitiveType::kLineList &&
user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip &&
user_config.GetPrimType() != Prospero::PrimitiveType::kTriList) ||
sh.m_vgtGsOutPrimType != 2u || sh.m_vgtGsMaxVertOut < 3u ||
+8 -4
View File
@@ -80,10 +80,15 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
uint32_t provoking_vertex = 0;
[[nodiscard]] constexpr uint32_t InputPrimitiveSize() const {
return input_primitive == static_cast<uint32_t>(Prospero::PrimitiveType::kPointList) ? 1u : 3u;
switch (static_cast<Prospero::PrimitiveType>(input_primitive)) {
case Prospero::PrimitiveType::kPointList: return 1u;
case Prospero::PrimitiveType::kLineList: return 2u;
default: return 3u;
}
}
[[nodiscard]] constexpr uint32_t InputPrimitiveStep() const {
return input_primitive == static_cast<uint32_t>(Prospero::PrimitiveType::kTriList) ? 3u : 1u;
return input_primitive == static_cast<uint32_t>(Prospero::PrimitiveType::kTriStrip)
? 1u : InputPrimitiveSize();
}
[[nodiscard]] constexpr uint32_t InputPrimitiveCount(uint32_t vertices) const {
const auto size = InputPrimitiveSize();
@@ -102,7 +107,6 @@ struct ShaderVertexInputInfo {
ShaderVertexInputBuffer buffers[RES_MAX];
ShaderStageRuntime stage;
int resources_num = 0;
int fetch_shader_reg = 0;
int fetch_attrib_reg = 0;
int fetch_buffer_reg = 0;
int buffers_num = 0;
@@ -135,7 +139,6 @@ struct ShaderPixelInputInfo {
uint32_t scratch_size_dwords = 0;
bool ps_pos_x = false;
bool ps_pos_y = false;
bool ps_pos_xy = false;
bool ps_pos_z = false;
bool ps_pos_w = false;
bool ps_front_face = false;
@@ -259,6 +262,7 @@ struct Shader {
};
struct ShaderMappedData {
Prospero::ShaderBinaryType type {};
ShaderUserData* user_data = nullptr;
ShaderSemantic* input_semantics = nullptr;
uint32_t num_input_semantics = 0;
+127 -185
View File
@@ -28,7 +28,8 @@ namespace Libs::LibKernel::FileSystem {
LIB_NAME("libkernel", "libkernel");
constexpr int DESCRIPTOR_MIN = 3;
constexpr int DESCRIPTOR_MIN = 3;
constexpr uint64_t DIR_BLOCK_SIZE = 512;
enum class SpecialFile {
None,
@@ -50,8 +51,7 @@ public:
void Mount(const std::filesystem::path& folder, const std::string& point);
void Umount(const std::string& folder_or_point);
[[nodiscard]] std::filesystem::path GetRealFilename(const std::string& mounted_file_name);
[[nodiscard]] std::filesystem::path GetRealDirectory(const std::string& mounted_directory);
[[nodiscard]] std::filesystem::path ResolvePath(const std::string& mounted_name);
private:
std::vector<MountPair> m_mount_pairs;
@@ -69,7 +69,7 @@ struct File {
std::atomic_bool sync_writes;
SpecialFile special;
Common::Mutex mutex;
std::vector<Common::File::DirEntry> dents;
std::vector<uint8_t> dirents;
uint64_t dents_offset;
};
@@ -120,6 +120,51 @@ static uint64_t AlignUp(uint64_t value, uint64_t alignment) {
return (alignment != 0 ? (value + alignment - 1) & ~(alignment - 1) : value);
}
static std::vector<uint8_t> PackDirents(const std::vector<Common::File::DirEntry>& entries) {
std::vector<uint8_t> dirents;
uint64_t offset = 0;
uint64_t last_reclen_offset = 0;
for (const auto& entry: entries) {
const auto& name = entry.name;
EXIT_NOT_IMPLEMENTED(name.size() > 255);
const auto reclen = AlignUp(8 + name.size() + 1, 4);
if (offset + reclen > dirents.size()) {
if (!dirents.empty()) {
auto* last_reclen =
reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen += static_cast<uint16_t>(dirents.size() - offset);
}
offset = dirents.size();
dirents.resize(offset + DIR_BLOCK_SIZE);
}
*reinterpret_cast<uint32_t*>(dirents.data() + offset) =
Common::hash(name.data(), static_cast<uint32_t>(name.size()));
*reinterpret_cast<uint16_t*>(dirents.data() + offset + 4) = static_cast<uint16_t>(reclen);
dirents[offset + 6] = entry.is_file ? 8 : 4;
dirents[offset + 7] = static_cast<uint8_t>(name.size());
memcpy(dirents.data() + offset + 8, name.c_str(), name.size() + 1);
last_reclen_offset = offset + 4;
offset += reclen;
}
if (!dirents.empty()) {
auto* last_reclen = reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen += static_cast<uint16_t>(dirents.size() - offset);
}
return dirents;
}
static uint64_t ReadDirectory(File* file, void* buf, uint64_t count) {
const auto remaining =
file->dents_offset < file->dirents.size() ? file->dirents.size() - file->dents_offset : 0;
const auto bytes = std::min(count, remaining);
if (bytes != 0) {
Memory::InvalidateMemory(reinterpret_cast<uint64_t>(buf), bytes);
memcpy(buf, file->dirents.data() + file->dents_offset, bytes);
file->dents_offset += bytes;
}
return bytes;
}
static int PosixToKernel(int posix_errno) {
return KERNEL_ERROR_UNKNOWN + posix_errno;
}
@@ -284,26 +329,24 @@ static bool HasWindowsForbiddenFilenameCharacter(const std::string& relative_pat
}
#endif
std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_file_name) {
std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name) {
Common::LockGuard lock(m_mutex);
auto mounted_path =
Common::DirectoryWithoutFilename(Common::FixFilenameSlash(mounted_file_name));
// Match the entire guest path so a mount root works with or without a trailing slash.
const auto mounted_path = Common::FixDirectorySlash(mounted_name);
const auto it = std::find_if(
m_mount_pairs.begin(), m_mount_pairs.end(),
[&mounted_path](const MountPair& p) { return Common::StartsWith(mounted_path, p.point); });
if (it != m_mount_pairs.end()) {
const auto& p = *it;
auto rel_path =
Common::RemoveFirst(Common::FixFilenameSlash(mounted_file_name), p.point.size());
auto rel_path = Common::RemoveFirst(Common::FixFilenameSlash(mounted_name), p.point.size());
while (Common::StartsWith(rel_path, '/')) {
rel_path = Common::RemoveFirst(rel_path, 1);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (HasWindowsForbiddenFilenameCharacter(rel_path)) {
::printf("FileSystem: Windows-incompatible guest filename: %s\n",
mounted_file_name.c_str());
mounted_name.c_str());
}
return p.dir / rel_path;
#else
@@ -311,32 +354,7 @@ std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_fi
#endif
}
return mounted_file_name;
}
std::filesystem::path MountPoints::GetRealDirectory(const std::string& mounted_directory) {
Common::LockGuard lock(m_mutex);
auto mounted_path = Common::FixDirectorySlash(mounted_directory);
const auto it = std::find_if(
m_mount_pairs.begin(), m_mount_pairs.end(),
[&mounted_path](const MountPair& p) { return Common::StartsWith(mounted_path, p.point); });
if (it != m_mount_pairs.end()) {
const auto& p = *it;
auto rel_path =
Common::RemoveFirst(Common::FixDirectorySlash(mounted_directory), p.point.size());
while (Common::StartsWith(rel_path, '/')) {
rel_path = Common::RemoveFirst(rel_path, 1);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return p.dir / rel_path;
#else
return ResolvePathIgnoringCase(p.dir / rel_path);
#endif
}
return mounted_directory;
return mounted_name;
}
void Initialize() {
@@ -370,7 +388,7 @@ void Umount(const std::string& folder_or_point) {
std::filesystem::path GetRealFilename(const std::string& mounted_file_name) {
return g_mount_points->GetRealFilename(mounted_file_name);
return g_mount_points->ResolvePath(mounted_file_name);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
@@ -437,8 +455,7 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
return descriptor;
}
file->real_name = (directory ? g_mount_points->GetRealDirectory(file->name)
: g_mount_points->GetRealFilename(file->name));
file->real_name = g_mount_points->ResolvePath(file->name);
if (trunc && rw_mode == Common::File::Mode::Read) {
return KERNEL_ERROR_EACCES;
@@ -462,15 +479,16 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
EXIT_NOT_IMPLEMENTED(!directory && rw_mode != Common::File::Mode::Read);
EXIT_NOT_IMPLEMENTED(!directory && (trunc || creat));
file->dents = Common::File::GetDirEntries(file->real_name);
const auto entries = Common::File::GetDirEntries(file->real_name);
file->dirents = PackDirents(entries);
file->dents_offset = 0;
file->directory = true;
LOGF_COLOR(Log::Color::Green, "\tOpen dir: %s, entries = %" PRIu32 ", [ok]\n",
Common::PathToString(file->real_name).c_str(),
static_cast<uint32_t>(file->dents.size()));
static_cast<uint32_t>(entries.size()));
for (const auto& f: file->dents) {
for (const auto& f: entries) {
LOGF("\t\t%s %s\n", f.is_file ? "[file]" : "[dir ]", f.name.c_str());
}
} else {
@@ -567,11 +585,18 @@ int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes) {
return KERNEL_ERROR_EBADF;
}
EXIT_NOT_IMPLEMENTED(file->directory);
EXIT_IF(!file->opened);
EXIT_NOT_IMPLEMENTED(nbytes > UINT_MAX);
if (nbytes > INT_MAX) {
return KERNEL_ERROR_EINVAL;
}
if (file->directory) {
Common::LockGuard lock(file->mutex);
const auto bytes = ReadDirectory(file, buf, nbytes);
LOGF("\tRead %" PRIu64 " directory bytes from: %s\n", bytes,
Common::PathToString(file->real_name).c_str());
return static_cast<int64_t>(bytes);
}
if (file->special == SpecialFile::Random) {
FillRandomBuffer(buf, nbytes);
@@ -790,7 +815,7 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
PRINT_NAME();
if (d < DESCRIPTOR_MIN) {
return KERNEL_ERROR_EPERM;
return KERNEL_ERROR_EBADF;
}
auto* file = g_files->GetFile(d);
@@ -799,8 +824,6 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
return KERNEL_ERROR_EBADF;
}
EXIT_NOT_IMPLEMENTED(file->directory);
EXIT_IF(!file->opened);
if (file->special != SpecialFile::None) {
@@ -809,38 +832,47 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
Common::LockGuard lock(file->mutex);
bool is_invalid = file->f.IsInvalid();
if (whence == 1) {
offset = static_cast<int64_t>(file->f.Tell()) + offset;
whence = 0;
}
if (whence == 2) {
offset = static_cast<int64_t>(file->f.Size()) + offset;
whence = 0;
}
EXIT_NOT_IMPLEMENTED(whence != 0);
if (offset < 0) {
return KERNEL_ERROR_EINVAL;
}
file->f.Seek(offset);
auto pos = static_cast<int64_t>(file->f.Tell());
EXIT_IF(pos != offset);
if (is_invalid) {
if (!file->directory && file->f.IsInvalid()) {
LOGF("\tfile is invalid\n");
return KERNEL_ERROR_EIO;
}
LOGF("\tLseek (pos = %" PRId64 ") to: %s\n", offset,
uint64_t base = 0;
switch (whence) {
case 0: break;
case 1: base = file->directory ? file->dents_offset : file->f.Tell(); break;
case 2: base = file->directory ? file->dirents.size() : file->f.Size(); break;
default: return KERNEL_ERROR_EINVAL;
}
uint64_t position = 0;
if (offset >= 0) {
if (base > static_cast<uint64_t>(INT64_MAX - offset)) {
return KERNEL_ERROR_EOVERFLOW;
}
position = base + static_cast<uint64_t>(offset);
} else {
const auto distance = uint64_t {0} - static_cast<uint64_t>(offset);
if (base < distance) {
return KERNEL_ERROR_EINVAL;
}
position = base - distance;
if (position > INT64_MAX) {
return KERNEL_ERROR_EOVERFLOW;
}
}
if (file->directory) {
file->dents_offset = position;
} else {
file->f.Seek(position);
EXIT_IF(file->f.Tell() != position);
}
LOGF("\tLseek (pos = %" PRIu64 ") to: %s\n", position,
Common::PathToString(file->real_name).c_str());
return pos;
return static_cast<int64_t>(position);
}
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
@@ -852,12 +884,9 @@ int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
LOGF("\t KernelStat: %s\n", path);
std::string path_s = std::string(path);
auto real_file_name = g_mount_points->GetRealFilename(path_s);
auto real_directory = g_mount_points->GetRealDirectory(path_s);
auto real_file_name = g_mount_points->ResolvePath(path);
bool is_dir = Common::File::IsDirectoryExisting(real_file_name) ||
Common::File::IsDirectoryExisting(real_directory);
bool is_dir = Common::File::IsDirectoryExisting(real_file_name);
bool is_file = Common::File::IsFileExisting(real_file_name);
if (!is_dir && !is_file) {
@@ -874,9 +903,10 @@ int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
auto wt = at;
if (is_dir) {
stat.st_size = 0;
stat.st_size =
static_cast<int64_t>(PackDirents(Common::File::GetDirEntries(real_file_name)).size());
stat.st_blksize = 512;
stat.st_blocks = 0;
stat.st_blocks = stat.st_size / 512;
} else {
stat.st_size = static_cast<int64_t>(Common::File::Size(real_file_name));
stat.st_blksize = 512;
@@ -952,9 +982,9 @@ int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb) {
stat.st_blksize = 512;
stat.st_blocks = (stat.st_size + 511) / 512;
} else {
stat.st_size = 0;
stat.st_size = static_cast<int64_t>(file->dirents.size());
stat.st_blksize = 512;
stat.st_blocks = 0;
stat.st_blocks = stat.st_size / 512;
}
SecToTimespec(&stat.st_atim, at.ToUnix());
@@ -1014,12 +1044,9 @@ int KYTY_SYSV_ABI KernelUnlink(const char* path) {
return KERNEL_ERROR_EINVAL;
}
auto path_s = std::string(path);
auto real_file_name = g_mount_points->GetRealFilename(path_s);
auto real_directory = g_mount_points->GetRealDirectory(path_s);
auto real_file_name = g_mount_points->ResolvePath(path);
bool is_dir = Common::File::IsDirectoryExisting(real_file_name) ||
Common::File::IsDirectoryExisting(real_directory);
bool is_dir = Common::File::IsDirectoryExisting(real_file_name);
bool is_file = Common::File::IsFileExisting(real_file_name);
if (is_dir) {
@@ -1053,8 +1080,8 @@ int KYTY_SYSV_ABI KernelRename(const char* from, const char* to) {
auto from_path = std::string(from);
auto to_path = std::string(to);
auto real_from = g_mount_points->GetRealFilename(from_path);
auto real_to = g_mount_points->GetRealFilename(to_path);
auto real_from = g_mount_points->ResolvePath(from_path);
auto real_to = g_mount_points->ResolvePath(to_path);
if (!Common::File::IsFileExisting(real_from)) {
return KERNEL_ERROR_ENOENT;
@@ -1092,14 +1119,15 @@ int KYTY_SYSV_ABI KernelGetdirentries(int fd, char* buf, int nbytes, int64_t* ba
return KERNEL_ERROR_EBADF;
}
constexpr uint64_t DIR_BLOCK_SIZE = 512;
constexpr uint64_t DIRENT_META_SIZE = 8;
if (!file->directory || nbytes < static_cast<int>(DIR_BLOCK_SIZE)) {
return KERNEL_ERROR_EINVAL;
}
EXIT_IF(!file->opened);
Common::LockGuard lock(file->mutex);
if (file->dents_offset > file->dirents.size() || file->dents_offset % DIR_BLOCK_SIZE != 0) {
return KERNEL_ERROR_EINVAL;
}
LOGF("\t dir = %s\n"
"\t nbytes = %d\n"
@@ -1109,94 +1137,8 @@ int KYTY_SYSV_ABI KernelGetdirentries(int fd, char* buf, int nbytes, int64_t* ba
if (basep != nullptr) {
*basep = static_cast<int64_t>(file->dents_offset);
}
std::vector<uint8_t> dirents;
dirents.reserve(
std::max<size_t>(static_cast<size_t>(DIR_BLOCK_SIZE), file->dents.size() * 64u));
uint64_t next_ceiling = 0;
uint64_t dirent_offset = 0;
int64_t last_reclen_offset = -1;
for (const auto& entry: file->dents) {
const auto& str = entry.name;
auto str_size = str.size();
EXIT_NOT_IMPLEMENTED(str_size > 255);
uint64_t reclen = AlignUp(DIRENT_META_SIZE + str_size + 1, 4);
if (dirent_offset + reclen > next_ceiling) {
if (last_reclen_offset >= 0) {
auto* last_reclen =
reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen = static_cast<uint16_t>(*last_reclen + (next_ceiling - dirent_offset));
}
dirent_offset = next_ceiling;
next_ceiling += DIR_BLOCK_SIZE;
dirents.resize(next_ceiling);
std::fill(dirents.begin() + static_cast<std::ptrdiff_t>(dirent_offset), dirents.end(),
0);
}
*reinterpret_cast<uint32_t*>(dirents.data() + dirent_offset + 0) =
Common::hash(str.data(), static_cast<uint32_t>(str.size()));
*reinterpret_cast<uint16_t*>(dirents.data() + dirent_offset + 4) =
static_cast<uint16_t>(reclen);
*reinterpret_cast<uint8_t*>(dirents.data() + dirent_offset + 6) = (entry.is_file ? 8 : 4);
*reinterpret_cast<uint8_t*>(dirents.data() + dirent_offset + 7) =
static_cast<uint8_t>(str_size);
memcpy(dirents.data() + dirent_offset + 8, str.data(), str_size);
dirents[dirent_offset + 8 + str_size] = '\0';
last_reclen_offset = static_cast<int64_t>(dirent_offset + 4);
dirent_offset += reclen;
LOGF("\t name = %s\n", str.data());
}
if (last_reclen_offset >= 0) {
auto* last_reclen = reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen = static_cast<uint16_t>(*last_reclen + (next_ceiling - dirent_offset));
}
const uint64_t directory_size = next_ceiling;
if (file->dents_offset >= directory_size) {
return 0;
}
uint64_t bytes_written = 0;
uint64_t working_offset = file->dents_offset;
uint64_t dirent_buffer_offset = 0;
uint64_t aligned_count = AlignDown(static_cast<uint64_t>(nbytes), DIR_BLOCK_SIZE);
while (dirent_buffer_offset < dirents.size()) {
const auto* normal_dirent = dirents.data() + dirent_buffer_offset;
uint16_t reclen = *reinterpret_cast<const uint16_t*>(normal_dirent + 4);
uint8_t namlen = *(normal_dirent + 7);
if (namlen == 0 || reclen == 0) {
break;
}
if (working_offset >= reclen) {
dirent_buffer_offset += reclen;
working_offset -= reclen;
continue;
}
if (bytes_written + reclen > aligned_count) {
break;
}
const auto bytes_to_copy = reclen - working_offset;
memcpy(buf + bytes_written, normal_dirent + working_offset, bytes_to_copy);
bytes_written += bytes_to_copy;
dirent_buffer_offset += reclen;
working_offset = 0;
}
file->dents_offset += bytes_written;
return static_cast<int64_t>(bytes_written);
return static_cast<int>(
ReadDirectory(file, buf, AlignDown(static_cast<uint64_t>(nbytes), DIR_BLOCK_SIZE)));
}
int KYTY_SYSV_ABI KernelGetdents(int fd, char* buf, int nbytes) {
@@ -1220,7 +1162,7 @@ int KYTY_SYSV_ABI KernelMkdir(const char* path, uint16_t mode) {
"\t mode = %04" PRIx16 "\n",
path, mode);
auto real_name = g_mount_points->GetRealDirectory(std::string(path));
auto real_name = g_mount_points->ResolvePath(std::string(path));
if (Common::File::IsDirectoryExisting(real_name)) {
return KERNEL_ERROR_EEXIST;
@@ -1246,7 +1188,7 @@ int KYTY_SYSV_ABI KernelRmdir(const char* path) {
LOGF("\t path = %s\n", path);
auto real_name = g_mount_points->GetRealDirectory(std::string(path));
auto real_name = g_mount_points->ResolvePath(std::string(path));
if (!Common::File::IsDirectoryExisting(real_name)) {
return KERNEL_ERROR_ENOENT;
@@ -1278,7 +1220,7 @@ int KYTY_SYSV_ABI KernelCheckReachability(const char* path) {
return OK;
}
auto real_name = g_mount_points->GetRealFilename(mounted_path);
auto real_name = g_mount_points->ResolvePath(mounted_path);
if (Common::File::IsFileExisting(real_name) || Common::File::IsDirectoryExisting(real_name)) {
return OK;
+7 -1
View File
@@ -26,9 +26,12 @@
#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <QtAlgorithms>
#if QT_CONFIG(vulkan)
#include <QVulkanInstance>
#include <QVulkanWindow>
#include <QtAlgorithms>
#endif
#include "ui_configuration_edit_dialog.h"
@@ -168,6 +171,8 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
ListInit(m_ui->comboBox_present_mode, info.present_mode);
m_ui->comboBox_gpu->clear();
m_ui->comboBox_gpu->addItem(tr("Auto"));
// Keep Auto when Qt is built without Vulkan support.
#if QT_CONFIG(vulkan)
#if defined(__APPLE__)
if (!qEnvironmentVariableIsSet("QT_VULKAN_LIB")) {
const auto base = QCoreApplication::applicationDirPath();
@@ -192,6 +197,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->comboBox_gpu->addItem(QString::fromUtf8(device.deviceName));
}
}
#endif
m_ui->comboBox_gpu->setCurrentIndex(
info.gpu_index >= 0 && info.gpu_index < m_ui->comboBox_gpu->count() - 1 ? info.gpu_index + 1
: 0);
+1
View File
@@ -662,6 +662,7 @@ int KYTY_SYSV_ABI AgcCreateShader(Shader** dst, void* header, const volatile voi
LOGF("\t base = 0x%016" PRIx64 "\n", base);
ShaderMappedData map;
map.type = static_cast<Prospero::ShaderBinaryType>(h->type);
map.user_data = h->user_data;
map.input_semantics = h->input_semantics;
map.num_input_semantics = h->num_input_semantics;
+46 -177
View File
@@ -101,109 +101,23 @@ static uint32_t ComputeFileId(const char* guest_path) {
return hash & static_cast<uint32_t>(std::numeric_limits<int32_t>::max());
}
static bool CopyStringToOutput(const std::string& str, char* out, size_t out_size) {
if (str.empty() || str.size() + 1 > out_size) {
return false;
static int ReadGuestCString(uint64_t addr, char* out, size_t out_size) {
if (addr == 0) {
return LibKernel::KERNEL_ERROR_EFAULT;
}
std::memcpy(out, str.c_str(), str.size() + 1);
return true;
}
static bool ReadGuestCString(uint64_t addr, char* out, size_t out_size) {
if (out == nullptr || out_size == 0 || addr == 0) {
return false;
}
const auto max_len = static_cast<uint64_t>(out_size - 1);
const auto* src = reinterpret_cast<const char*>(static_cast<uintptr_t>(addr));
for (uint64_t pos = 0; pos < max_len; pos++) {
const auto* src = reinterpret_cast<const char*>(addr);
for (size_t pos = 0; pos < out_size; pos++) {
if (addr > std::numeric_limits<uint64_t>::max() - pos) {
out[pos] = '\0';
return false;
return LibKernel::KERNEL_ERROR_EFAULT;
}
const auto ch = src[pos];
out[pos] = ch;
if (ch == '\0') {
return pos != 0;
out[pos] = src[pos];
if (out[pos] == '\0') {
return OK;
}
}
out[max_len] = '\0';
return false;
}
static bool ReadGuestWideCString(uint64_t addr, char* out, size_t out_size) {
if (out == nullptr || out_size == 0 || addr == 0) {
return false;
}
std::array<char16_t, 1024> tmp {};
const auto max_len = std::min<uint64_t>(tmp.size() - 1, out_size - 1);
const auto* src = reinterpret_cast<const char*>(static_cast<uintptr_t>(addr));
for (uint64_t pos = 0; pos < max_len; pos++) {
if (pos > (std::numeric_limits<uint64_t>::max() - addr) / sizeof(char16_t)) {
return false;
}
char16_t ch = 0;
std::memcpy(&ch, src + pos * sizeof(char16_t), sizeof(ch));
tmp[static_cast<size_t>(pos)] = ch;
if (ch == u'\0') {
return pos != 0 && CopyStringToOutput(Common::Utf16ToUtf8(tmp.data()), out, out_size);
}
}
return false;
}
static bool ReadGuestPathText(uint64_t addr, char* out, size_t out_size) {
std::array<char, 1024> narrow {};
if (ReadGuestCString(addr, narrow.data(), std::min(narrow.size(), out_size))) {
const auto narrow_len = std::strlen(narrow.data());
if (narrow_len > 1 || !ReadGuestWideCString(addr, out, out_size)) {
std::memcpy(out, narrow.data(), std::strlen(narrow.data()) + 1);
}
return true;
}
if (ReadGuestWideCString(addr, out, out_size)) {
return true;
}
return false;
}
static bool ReadPathPointer(uint64_t pointer_addr, char* out, size_t out_size) {
uint64_t path_addr = 0;
if (!ReadGuest(pointer_addr, &path_addr)) {
return false;
}
if (path_addr == 0) {
return false;
}
if (ReadGuestPathText(path_addr, out, out_size)) {
return true;
}
return false;
}
static bool ResolvePathFromList(uint64_t path_list, uint64_t index, char* out, size_t out_size) {
if (ReadPathPointer(path_list + index * sizeof(uint64_t), out, out_size)) {
return true;
}
if (index != 0) {
return false;
}
if (ReadGuestPathText(path_list, out, out_size)) {
return true;
}
for (uint64_t offset = 0; offset < 0x40; offset += sizeof(uint64_t)) {
if (ReadPathPointer(path_list + offset, out, out_size)) {
return true;
}
}
return false;
return LibKernel::KERNEL_ERROR_ENAMETOOLONG;
}
static void RegisterHostPathLocked(uint32_t file_id, const std::string& host_path,
@@ -280,36 +194,6 @@ static int GetHostPathStat(const std::string& host_path, LibKernel::FileSystem::
return OK;
}
static bool JoinPrefixPath(const char* prefix, const char* path, char* out, size_t out_size) {
if (path == nullptr || out == nullptr || out_size == 0) {
return false;
}
if (prefix == nullptr || prefix[0] == '\0') {
std::strncpy(out, path, out_size - 1);
out[out_size - 1] = '\0';
return true;
}
const auto prefix_len = std::strlen(prefix);
const auto path_len = std::strlen(path);
const bool needs_sep = prefix_len != 0 && path_len != 0 && prefix[prefix_len - 1] != '/' &&
prefix[prefix_len - 1] != '\\' && path[0] != '/' && path[0] != '\\';
const auto total_len = prefix_len + (needs_sep ? 1u : 0u) + path_len;
if (total_len + 1u > out_size) {
return false;
}
std::memcpy(out, prefix, prefix_len);
auto pos = prefix_len;
if (needs_sep) {
out[pos++] = '/';
}
std::memcpy(out + pos, path, path_len);
out[total_len] = '\0';
return true;
}
static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size) {
if (guest_path == nullptr || guest_path[0] == '\0') {
return LibKernel::KERNEL_ERROR_EINVAL;
@@ -381,8 +265,8 @@ static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size)
return OK;
}
static int ResolvePathsCommon(const void* path_list, uint64_t count, uint32_t* ids, uint64_t* sizes,
uint32_t* error_index, const char* prefix, int* results) {
static int ResolvePathsCommon(const char* const* path_list, uint32_t count, uint32_t* ids,
uint64_t* sizes, uint32_t* error_index, const char* prefix, int* results) {
if (path_list == nullptr || count == 0 || count > 1024 ||
(ids == nullptr && sizes == nullptr && results == nullptr)) {
return LibKernel::KERNEL_ERROR_EINVAL;
@@ -406,18 +290,27 @@ static int ResolvePathsCommon(const void* path_list, uint64_t count, uint32_t* i
return LibKernel::KERNEL_ERROR_EFAULT;
}
const auto path_list_addr = reinterpret_cast<uint64_t>(path_list);
bool any_error = false;
int first_error = OK;
uint32_t success_count = 0;
for (uint64_t i = 0; i < count; i++) {
char guest_path[1024] {};
char resolved_path[2048] {};
int result = OK;
if (!AprShared::ResolvePathFromList(path_list_addr, i, guest_path, sizeof(guest_path)) ||
!JoinPrefixPath(prefix, guest_path, resolved_path, sizeof(resolved_path))) {
result = LibKernel::KERNEL_ERROR_EFAULT;
} else {
if (!IsValidGuestRange(reinterpret_cast<uint64_t>(path_list), count * sizeof(*path_list))) {
return LibKernel::KERNEL_ERROR_EFAULT;
}
char prefix_buf[1024] {};
if (prefix != nullptr) {
const int result = ReadGuestCString(reinterpret_cast<uint64_t>(prefix), prefix_buf,
sizeof(prefix_buf));
if (result != OK) {
return result;
}
}
const auto prefix_size = std::strlen(prefix_buf);
uint32_t success_count = 0;
for (uint32_t i = 0; i < count; i++) {
char resolved_path[1024] {};
// Concatenate the prefix literally, including an empty prefix.
std::memcpy(resolved_path, prefix_buf, prefix_size);
int result = ReadGuestCString(reinterpret_cast<uint64_t>(path_list[i]),
resolved_path + prefix_size, sizeof(resolved_path) - prefix_size);
if (result == OK) {
result = ResolveOnePath(resolved_path, ids != nullptr ? &ids[i] : nullptr,
sizes != nullptr ? &sizes[i] : nullptr);
}
@@ -437,17 +330,13 @@ static int ResolvePathsCommon(const void* path_list, uint64_t count, uint32_t* i
if (error_index != nullptr) {
*error_index = static_cast<uint32_t>(i);
}
if (!any_error) {
any_error = true;
first_error = result;
}
if (results == nullptr) {
return result;
}
}
}
return results != nullptr ? static_cast<int>(success_count) : (any_error ? first_error : OK);
return results != nullptr ? static_cast<int>(success_count) : OK;
}
static uint32_t AllocateSubmissionId(uint64_t command_buffer, uint64_t result) {
@@ -521,7 +410,7 @@ static int KernelSyscallResult(int result) {
return -1;
}
static int KYTY_SYSV_ABI ResolveFilepathsToIds(const void* path_list, uint32_t count, uint32_t* ids,
static int KYTY_SYSV_ABI ResolveFilepathsToIds(const char* const* path_list, uint32_t count, uint32_t* ids,
uint32_t* error_index) {
PRINT_NAME();
@@ -582,7 +471,7 @@ static int KYTY_SYSV_ABI GetFileSize(uint32_t file_id, uint64_t* size) {
return OK;
}
static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizes(const void* path_list, uint32_t count,
static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizes(const char* const* path_list, uint32_t count,
uint32_t* ids, uint64_t* sizes,
uint32_t* error_index) {
PRINT_NAME();
@@ -591,37 +480,27 @@ static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizes(const void* path_list
AprShared::ResolvePathsCommon(path_list, count, ids, sizes, error_index, nullptr, nullptr));
}
static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIds(const char* prefix, const void* path_list,
static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIds(const char* prefix, const char* const* path_list,
uint32_t count, uint32_t* ids,
uint32_t* error_index) {
PRINT_NAME();
char prefix_buf[1024] {};
if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast<uint64_t>(prefix),
prefix_buf, sizeof(prefix_buf))) {
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
}
return KernelSyscallResult(AprShared::ResolvePathsCommon(path_list, count, ids, nullptr,
error_index, prefix_buf, nullptr));
error_index, prefix, nullptr));
}
static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIdsAndFileSizes(const char* prefix,
const void* path_list,
const char* const* path_list,
uint32_t count, uint32_t* ids,
uint64_t* sizes,
uint32_t* error_index) {
PRINT_NAME();
char prefix_buf[1024] {};
if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast<uint64_t>(prefix),
prefix_buf, sizeof(prefix_buf))) {
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
}
return KernelSyscallResult(AprShared::ResolvePathsCommon(path_list, count, ids, sizes,
error_index, prefix_buf, nullptr));
error_index, prefix, nullptr));
}
static int KYTY_SYSV_ABI ResolveFilepathsToIdsForEach(const void* path_list, uint32_t count,
static int KYTY_SYSV_ABI ResolveFilepathsToIdsForEach(const char* const* path_list, uint32_t count,
uint32_t* ids, int* results) {
PRINT_NAME();
@@ -629,7 +508,7 @@ static int KYTY_SYSV_ABI ResolveFilepathsToIdsForEach(const void* path_list, uin
AprShared::ResolvePathsCommon(path_list, count, ids, nullptr, nullptr, nullptr, results));
}
static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizesForEach(const void* path_list,
static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizesForEach(const char* const* path_list,
uint32_t count, uint32_t* ids,
uint64_t* sizes, int* results) {
PRINT_NAME();
@@ -639,32 +518,22 @@ static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizesForEach(const void* pa
}
static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIdsForEach(const char* prefix,
const void* path_list,
const char* const* path_list,
uint32_t count, uint32_t* ids,
int* results) {
PRINT_NAME();
char prefix_buf[1024] {};
if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast<uint64_t>(prefix),
prefix_buf, sizeof(prefix_buf))) {
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
}
return KernelSyscallResult(AprShared::ResolvePathsCommon(path_list, count, ids, nullptr,
nullptr, prefix_buf, results));
nullptr, prefix, results));
}
static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIdsAndFileSizesForEach(
const char* prefix, const void* path_list, uint32_t count, uint32_t* ids, uint64_t* sizes,
const char* prefix, const char* const* path_list, uint32_t count, uint32_t* ids, uint64_t* sizes,
int* results) {
PRINT_NAME();
char prefix_buf[1024] {};
if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast<uint64_t>(prefix),
prefix_buf, sizeof(prefix_buf))) {
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
}
return KernelSyscallResult(
AprShared::ResolvePathsCommon(path_list, count, ids, sizes, nullptr, prefix_buf, results));
AprShared::ResolvePathsCommon(path_list, count, ids, sizes, nullptr, prefix, results));
}
static int KYTY_SYSV_ABI SubmitCommandBufferAndGetResult(void* command_buffer, uint64_t,
+323
View File
@@ -0,0 +1,323 @@
#include "common/abi.h"
#include "common/assert.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <SDL_stdinc.h>
#include <array>
#include <cstring>
namespace Libs {
namespace LibCes {
LIB_VERSION("Ces", 1, "Ces", 1, 1);
static const uint8_t* KYTY_SYSV_ABI CesRefersUcsProfileCp1252() {
PRINT_NAME();
static const uint8_t profile = 0;
return &profile;
}
static uint32_t CesDecodeUtf8(const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len) {
if (utf8 == nullptr || utf8max == 0) {
if (utf8_len != nullptr) {
*utf8_len = 0;
}
return 0xfffd;
}
const uint8_t c0 = utf8[0];
if (c0 < 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 1;
}
return c0;
}
if ((c0 & 0xe0) == 0xc0 && utf8max >= 2 && (utf8[1] & 0xc0) == 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 2;
}
return ((c0 & 0x1f) << 6u) | (utf8[1] & 0x3fu);
}
if ((c0 & 0xf0) == 0xe0 && utf8max >= 3 && (utf8[1] & 0xc0) == 0x80 &&
(utf8[2] & 0xc0) == 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 3;
}
return ((c0 & 0x0f) << 12u) | ((utf8[1] & 0x3fu) << 6u) | (utf8[2] & 0x3fu);
}
if ((c0 & 0xf8) == 0xf0 && utf8max >= 4 && (utf8[1] & 0xc0) == 0x80 &&
(utf8[2] & 0xc0) == 0x80 && (utf8[3] & 0xc0) == 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 4;
}
return ((c0 & 0x07) << 18u) | ((utf8[1] & 0x3fu) << 12u) | ((utf8[2] & 0x3fu) << 6u) |
(utf8[3] & 0x3fu);
}
if (utf8_len != nullptr) {
*utf8_len = 1;
}
return 0xfffd;
}
struct Cp1252Mapping {
uint8_t cp1252;
uint32_t unicode;
};
static constexpr Cp1252Mapping CP1252_EXTENDED_MAP[] = {
{0x80, 0x20ac}, {0x82, 0x201a}, {0x83, 0x0192}, {0x84, 0x201e}, {0x85, 0x2026}, {0x86, 0x2020},
{0x87, 0x2021}, {0x88, 0x02c6}, {0x89, 0x2030}, {0x8a, 0x0160}, {0x8b, 0x2039}, {0x8c, 0x0152},
{0x8e, 0x017d}, {0x91, 0x2018}, {0x92, 0x2019}, {0x93, 0x201c}, {0x94, 0x201d}, {0x95, 0x2022},
{0x96, 0x2013}, {0x97, 0x2014}, {0x98, 0x02dc}, {0x99, 0x2122}, {0x9a, 0x0161}, {0x9b, 0x203a},
{0x9c, 0x0153}, {0x9e, 0x017e}, {0x9f, 0x0178},
};
static uint8_t CesUnicodeToCp1252(uint32_t code) {
if (code <= 0x7f || (code >= 0xa0 && code <= 0xff)) {
return static_cast<uint8_t>(code);
}
for (const auto& map: CP1252_EXTENDED_MAP) {
if (map.unicode == code) {
return map.cp1252;
}
}
return '?';
}
static uint32_t CesCp1252ToUnicode(uint8_t sbc) {
if (sbc <= 0x7f || sbc >= 0xa0) {
return sbc;
}
for (const auto& map: CP1252_EXTENDED_MAP) {
if (map.cp1252 == sbc) {
return map.unicode;
}
}
return sbc;
}
static uint32_t CesEncodeUtf8(uint32_t code, uint8_t* utf8, uint32_t utf8max) {
uint8_t tmp[4] {};
uint32_t len = 0;
if (code <= 0x7f) {
tmp[0] = static_cast<uint8_t>(code);
len = 1;
} else if (code <= 0x7ff) {
tmp[0] = static_cast<uint8_t>(0xc0u | ((code >> 6u) & 0x1fu));
tmp[1] = static_cast<uint8_t>(0x80u | (code & 0x3fu));
len = 2;
} else if (code <= 0xffff) {
tmp[0] = static_cast<uint8_t>(0xe0u | ((code >> 12u) & 0x0fu));
tmp[1] = static_cast<uint8_t>(0x80u | ((code >> 6u) & 0x3fu));
tmp[2] = static_cast<uint8_t>(0x80u | (code & 0x3fu));
len = 3;
} else {
tmp[0] = static_cast<uint8_t>(0xf0u | ((code >> 18u) & 0x07u));
tmp[1] = static_cast<uint8_t>(0x80u | ((code >> 12u) & 0x3fu));
tmp[2] = static_cast<uint8_t>(0x80u | ((code >> 6u) & 0x3fu));
tmp[3] = static_cast<uint8_t>(0x80u | (code & 0x3fu));
len = 4;
}
if (utf8 != nullptr && utf8max >= len) {
std::memcpy(utf8, tmp, len);
}
return len;
}
static int KYTY_SYSV_ABI CesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len,
const uint8_t* profile, uint8_t* sbc) {
PRINT_NAME();
if (sbc == nullptr || profile == nullptr) {
return -1;
}
uint32_t local_len = 0;
const auto code = CesDecodeUtf8(utf8, utf8max, &local_len);
if (utf8_len != nullptr) {
*utf8_len = local_len;
}
*sbc = CesUnicodeToCp1252(code);
return 0;
}
static int KYTY_SYSV_ABI CesSbcToUtf8(const uint8_t* profile, uint8_t sbc, uint8_t* utf8,
uint32_t utf8max, uint32_t* utf8_len) {
PRINT_NAME();
if (profile == nullptr || utf8 == nullptr) {
return -1;
}
const auto code = CesCp1252ToUnicode(sbc);
const auto len = CesEncodeUtf8(code, utf8, utf8max);
if (utf8_len != nullptr) {
*utf8_len = len;
}
return utf8max >= len ? 0 : -1;
}
// Profile sheets and contexts each reserve 32 pointers for library-private state.
struct CesProfile {
const char* encoding;
const void* reserved[31];
};
struct CesContext {
const CesProfile* profile;
const void* reserved[31];
};
static_assert(sizeof(CesProfile) == 256 && alignof(CesProfile) == 8);
static_assert(sizeof(CesContext) == 256 && alignof(CesContext) == 8);
static constexpr char CP932[] = "CP932";
static constexpr int CES_ERROR_INVALID_PARAMETER = static_cast<int>(0x805c0001u);
static constexpr int CES_ERROR_INVALID_PROFILE = static_cast<int>(0x805c0004u);
static constexpr int CES_ERROR_INVALID_SRC_BUFFER = static_cast<int>(0x805c0010u);
static constexpr int CES_ERROR_SRC_BUFFER_END = static_cast<int>(0x805c0011u);
static constexpr int CES_ERROR_INVALID_ENCODE = static_cast<int>(0x805c0014u);
static constexpr int CES_ERROR_UNASSIGNED_CODE = static_cast<int>(0x805c0020u);
static constexpr int CES_ERROR_INVALID_DST_BUFFER = static_cast<int>(0x805c0030u);
static constexpr int CES_ERROR_DST_BUFFER_END = static_cast<int>(0x805c0031u);
static CesProfile* KYTY_SYSV_ABI CesUcsProfileInitSJis1997Cp932(CesProfile* sheet) {
PRINT_NAME();
if (sheet != nullptr) {
*sheet = {};
sheet->encoding = CP932;
}
return sheet;
}
static int KYTY_SYSV_ABI CesMbcsUcsContextInit(CesContext* context, const CesProfile* profile) {
PRINT_NAME();
if (context == nullptr || profile == nullptr) {
return CES_ERROR_INVALID_PARAMETER;
}
*context = {};
context->profile = profile;
return 0;
}
static int ConvertMbcsToUtf8(CesContext* context, const uint8_t* source, uint32_t source_max,
uint32_t* source_len, uint8_t* destination, uint32_t destination_max,
uint32_t* destination_len, bool measure) {
if (source_len != nullptr) {
*source_len = 0;
}
if (destination_len != nullptr) {
*destination_len = 0;
}
if (destination != nullptr && destination_max != 0) {
destination[0] = 0;
}
if (context == nullptr) {
return CES_ERROR_INVALID_PARAMETER;
}
if (context->profile == nullptr || context->profile->encoding != CP932) {
return CES_ERROR_INVALID_PROFILE;
}
if (source == nullptr) {
return CES_ERROR_INVALID_SRC_BUFFER;
}
if (!measure && destination == nullptr) {
return CES_ERROR_INVALID_DST_BUFFER;
}
if (!measure && destination_max == 0) {
return CES_ERROR_DST_BUFFER_END;
}
size_t source_size = 0;
while ((source_max == 0 || source_size < source_max) && source[source_size] != 0) {
++source_size;
}
const bool bounded_end = source_max != 0 && source_size == source_max;
const auto converter = SDL_iconv_open("UTF-8", context->profile->encoding);
EXIT_IF(converter == reinterpret_cast<SDL_iconv_t>(-1));
const char* input = reinterpret_cast<const char*>(source);
size_t input_left = source_size;
size_t produced = 0;
int result = 0;
std::array<char, 256> scratch;
do {
char* output = measure ? scratch.data() : reinterpret_cast<char*>(destination) + produced;
const size_t capacity = measure ? scratch.size() : destination_max - 1 - produced;
size_t output_left = capacity;
const auto status = SDL_iconv(converter, &input, &input_left, &output, &output_left);
produced += capacity - output_left;
if (status == SDL_ICONV_E2BIG) {
if (measure) {
continue;
}
result = CES_ERROR_DST_BUFFER_END;
} else if (status == SDL_ICONV_EINVAL) {
result = bounded_end ? CES_ERROR_SRC_BUFFER_END : CES_ERROR_INVALID_ENCODE;
} else if (status == SDL_ICONV_EILSEQ || status == SDL_ICONV_ERROR) {
const auto* code = reinterpret_cast<const uint8_t*>(input);
const bool lead =
(code[0] >= 0x81 && code[0] <= 0x9f) || (code[0] >= 0xe0 && code[0] <= 0xfc);
const bool trail = input_left >= 2 && ((code[1] >= 0x40 && code[1] <= 0x7e) ||
(code[1] >= 0x80 && code[1] <= 0xfc));
result = lead && trail ? CES_ERROR_UNASSIGNED_CODE : CES_ERROR_INVALID_ENCODE;
}
break;
} while (input_left != 0);
SDL_iconv_close(converter);
if (!measure) {
destination[produced] = 0;
}
if (source_len != nullptr) {
*source_len = static_cast<uint32_t>(source_size - input_left);
}
if (destination_len != nullptr) {
*destination_len = static_cast<uint32_t>(produced);
}
return result;
}
static int KYTY_SYSV_ABI CesMbcsStrGetUtf8Len(CesContext* context, const uint8_t* source,
uint32_t source_max, uint32_t* source_len,
uint32_t* destination_len) {
PRINT_NAME();
return ConvertMbcsToUtf8(context, source, source_max, source_len, nullptr, 0, destination_len,
true);
}
static int KYTY_SYSV_ABI CesMbcsStrToUtf8Str(CesContext* context, const uint8_t* source,
uint32_t source_max, uint32_t* source_len,
uint8_t* destination, uint32_t destination_max,
uint32_t* destination_len) {
PRINT_NAME();
return ConvertMbcsToUtf8(context, source, source_max, source_len, destination, destination_max,
destination_len, false);
}
LIB_DEFINE(InitCes_1) {
LIB_FUNC("ZiDCxUUGbec", LibCes::CesUcsProfileInitSJis1997Cp932);
LIB_FUNC("538bRGc6Zo8", LibCes::CesMbcsUcsContextInit);
LIB_FUNC("r7Sr1i7KLus", LibCes::CesMbcsStrGetUtf8Len);
LIB_FUNC("yGKn6vdYInc", LibCes::CesMbcsStrToUtf8Str);
LIB_FUNC("LPzYZ+FR0BI", LibCes::CesRefersUcsProfileCp1252);
LIB_FUNC("3Q1gOWWarcw", LibCes::CesUtf8ToSbc);
LIB_FUNC("xTd54EEL1Ao", LibCes::CesSbcToUtf8);
}
} // namespace LibCes
} // namespace Libs
+2 -1
View File
@@ -745,7 +745,8 @@ static bool ttf_data_has_supported_magic(const uint8_t* data, uint32_t size) {
}
return (data[0] == 0x00 && data[1] == 0x01 && data[2] == 0x00 && data[3] == 0x00) ||
(data[0] == 't' && data[1] == 'r' && data[2] == 'u' && data[3] == 'e');
(data[0] == 't' && data[1] == 'r' && data[2] == 'u' && data[3] == 'e') ||
(data[0] == 'O' && data[1] == 'T' && data[2] == 'T' && data[3] == 'O');
}
static bool init_stb_font(FontState* font, const uint8_t* data, uint32_t size) {
+9
View File
@@ -525,6 +525,14 @@ static void* KYTY_SYSV_ABI JsonValueCtor(void* self) {
return self;
}
static JsonValue* KYTY_SYSV_ABI JsonValueCopyCtor(JsonValue* self, const JsonValue* src) {
PRINT_NAME();
JsonValueInit(self);
JsonValueCopy(self, src);
return self;
}
static JsonValue* KYTY_SYSV_ABI JsonValueTypeCtor(JsonValue* self, uint32_t type) {
PRINT_NAME();
@@ -1014,6 +1022,7 @@ LIB_DEFINE(InitNet_1_Json2) {
LIB_FUNC("OcAgPxcq5Vk", LibJson2::JsonMemAllocatorDtor);
LIB_FUNC("qBMjqyBn3OM", LibJson2::JsonValueCtor);
LIB_FUNC("-wa17B7TGnw", LibJson2::JsonValueCtor);
LIB_FUNC("fSb2oQTNrgA", LibJson2::JsonValueCopyCtor);
LIB_FUNC("CbrT3dwDILo", LibJson2::JsonValueTypeCtor);
LIB_FUNC("WTtYf+cNnXI", LibJson2::JsonValueDtor);
LIB_FUNC("0eUrW9JAxM0", LibJson2::JsonValueDtor);
+28
View File
@@ -2112,6 +2112,33 @@ int KYTY_SYSV_ABI KernelSyncOnAddressWake(volatile void* address, int32_t count)
return LibKernel::SyncOnAddress::Wake(address, count);
}
int KYTY_SYSV_ABI UmtxOp(volatile void* address, int operation, uint64_t value,
void* uaddr, const void* timeout) {
constexpr int UMTX_OP_WAIT = 2;
constexpr int UMTX_OP_WAKE = 3;
EXIT_NOT_IMPLEMENTED(uaddr != nullptr);
switch (operation) {
case UMTX_OP_WAIT:
if (timeout != nullptr) {
LibKernel::KernelTimespec duration {};
std::memcpy(&duration, timeout, sizeof(duration));
if (duration.tv_sec < 0 || duration.tv_nsec < 0 || duration.tv_nsec >= 1000000000) {
*GetErrorAddr() = POSIX_EINVAL;
return -1;
}
EXIT("Valid _umtx_op timed waits are not implemented\n");
}
return POSIX_CALL(LibKernel::SyncOnAddress::Wait64(
static_cast<volatile uint64_t*>(address), value, nullptr,
LibKernel::KernelDispatchPendingSignalForCurrentThread));
case UMTX_OP_WAKE:
return POSIX_CALL(LibKernel::SyncOnAddress::Wake(address, static_cast<int32_t>(value)));
default: EXIT("Unsupported _umtx_op operation: %d\n", operation);
}
}
LIB_DEFINE(InitLibKernel_1_Posix) {
LIB_FUNC("k+AXqu2-eBc", getpagesize);
LIB_FUNC("lLMT9vJAck0", clock_gettime);
@@ -3310,6 +3337,7 @@ static void AddLibkernelUnityFunc(Loader::SymbolDatabase* s, const char* nid, ui
}
LIB_DEFINE(InitLibKernel_1) {
LIB_FUNC("04AjkP0jO9U", Posix::UmtxOp);
InitLibKernel_1_FS(s);
InitLibKernel_1_Mem(s);
InitLibKernel_1_Equeue(s);
-181
View File
@@ -3003,186 +3003,6 @@ LIB_DEFINE(InitNet_1_NpUniversalDataSystem) {
} // namespace LibNpUniversalDataSystem
namespace LibCes {
LIB_VERSION("Ces", 1, "Ces", 1, 1);
static const uint8_t* KYTY_SYSV_ABI CesRefersUcsProfileCp1252() {
PRINT_NAME();
static const uint8_t profile = 0;
return &profile;
}
static uint32_t CesDecodeUtf8(const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len) {
if (utf8 == nullptr || utf8max == 0) {
if (utf8_len != nullptr) {
*utf8_len = 0;
}
return 0xfffd;
}
const uint8_t c0 = utf8[0];
if (c0 < 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 1;
}
return c0;
}
if ((c0 & 0xe0) == 0xc0 && utf8max >= 2 && (utf8[1] & 0xc0) == 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 2;
}
return ((c0 & 0x1f) << 6u) | (utf8[1] & 0x3fu);
}
if ((c0 & 0xf0) == 0xe0 && utf8max >= 3 && (utf8[1] & 0xc0) == 0x80 &&
(utf8[2] & 0xc0) == 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 3;
}
return ((c0 & 0x0f) << 12u) | ((utf8[1] & 0x3fu) << 6u) | (utf8[2] & 0x3fu);
}
if ((c0 & 0xf8) == 0xf0 && utf8max >= 4 && (utf8[1] & 0xc0) == 0x80 &&
(utf8[2] & 0xc0) == 0x80 && (utf8[3] & 0xc0) == 0x80) {
if (utf8_len != nullptr) {
*utf8_len = 4;
}
return ((c0 & 0x07) << 18u) | ((utf8[1] & 0x3fu) << 12u) | ((utf8[2] & 0x3fu) << 6u) |
(utf8[3] & 0x3fu);
}
if (utf8_len != nullptr) {
*utf8_len = 1;
}
return 0xfffd;
}
struct Cp1252Mapping {
uint8_t cp1252;
uint32_t unicode;
};
static constexpr Cp1252Mapping CP1252_EXTENDED_MAP[] = {
{0x80, 0x20ac}, {0x82, 0x201a}, {0x83, 0x0192}, {0x84, 0x201e}, {0x85, 0x2026}, {0x86, 0x2020},
{0x87, 0x2021}, {0x88, 0x02c6}, {0x89, 0x2030}, {0x8a, 0x0160}, {0x8b, 0x2039}, {0x8c, 0x0152},
{0x8e, 0x017d}, {0x91, 0x2018}, {0x92, 0x2019}, {0x93, 0x201c}, {0x94, 0x201d}, {0x95, 0x2022},
{0x96, 0x2013}, {0x97, 0x2014}, {0x98, 0x02dc}, {0x99, 0x2122}, {0x9a, 0x0161}, {0x9b, 0x203a},
{0x9c, 0x0153}, {0x9e, 0x017e}, {0x9f, 0x0178},
};
static uint8_t CesUnicodeToCp1252(uint32_t code) {
if (code <= 0x7f || (code >= 0xa0 && code <= 0xff)) {
return static_cast<uint8_t>(code);
}
for (const auto& map: CP1252_EXTENDED_MAP) {
if (map.unicode == code) {
return map.cp1252;
}
}
return '?';
}
static uint32_t CesCp1252ToUnicode(uint8_t sbc) {
if (sbc <= 0x7f || sbc >= 0xa0) {
return sbc;
}
for (const auto& map: CP1252_EXTENDED_MAP) {
if (map.cp1252 == sbc) {
return map.unicode;
}
}
return sbc;
}
static uint32_t CesEncodeUtf8(uint32_t code, uint8_t* utf8, uint32_t utf8max) {
uint8_t tmp[4] {};
uint32_t len = 0;
if (code <= 0x7f) {
tmp[0] = static_cast<uint8_t>(code);
len = 1;
} else if (code <= 0x7ff) {
tmp[0] = static_cast<uint8_t>(0xc0u | ((code >> 6u) & 0x1fu));
tmp[1] = static_cast<uint8_t>(0x80u | (code & 0x3fu));
len = 2;
} else if (code <= 0xffff) {
tmp[0] = static_cast<uint8_t>(0xe0u | ((code >> 12u) & 0x0fu));
tmp[1] = static_cast<uint8_t>(0x80u | ((code >> 6u) & 0x3fu));
tmp[2] = static_cast<uint8_t>(0x80u | (code & 0x3fu));
len = 3;
} else {
tmp[0] = static_cast<uint8_t>(0xf0u | ((code >> 18u) & 0x07u));
tmp[1] = static_cast<uint8_t>(0x80u | ((code >> 12u) & 0x3fu));
tmp[2] = static_cast<uint8_t>(0x80u | ((code >> 6u) & 0x3fu));
tmp[3] = static_cast<uint8_t>(0x80u | (code & 0x3fu));
len = 4;
}
if (utf8 != nullptr && utf8max >= len) {
std::memcpy(utf8, tmp, len);
}
return len;
}
static int KYTY_SYSV_ABI CesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len,
const uint8_t* profile, uint8_t* sbc) {
PRINT_NAME();
if (sbc == nullptr || profile == nullptr) {
return -1;
}
uint32_t local_len = 0;
const auto code = CesDecodeUtf8(utf8, utf8max, &local_len);
if (utf8_len != nullptr) {
*utf8_len = local_len;
}
*sbc = CesUnicodeToCp1252(code);
return 0;
}
static int KYTY_SYSV_ABI CesSbcToUtf8(const uint8_t* profile, uint8_t sbc, uint8_t* utf8,
uint32_t utf8max, uint32_t* utf8_len) {
PRINT_NAME();
if (profile == nullptr || utf8 == nullptr) {
return -1;
}
const auto code = CesCp1252ToUnicode(sbc);
const auto len = CesEncodeUtf8(code, utf8, utf8max);
if (utf8_len != nullptr) {
*utf8_len = len;
}
return utf8max >= len ? 0 : -1;
}
static int KYTY_SYSV_ABI CesStub(uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3) {
PRINT_NAME();
(void)arg0;
(void)arg1;
(void)arg2;
(void)arg3;
return 0;
}
LIB_DEFINE(InitNet_1_Ces) {
LIB_FUNC("ZiDCxUUGbec", LibCes::CesStub);
LIB_FUNC("538bRGc6Zo8", LibCes::CesStub);
LIB_FUNC("LPzYZ+FR0BI", LibCes::CesRefersUcsProfileCp1252);
LIB_FUNC("3Q1gOWWarcw", LibCes::CesUtf8ToSbc);
LIB_FUNC("xTd54EEL1Ao", LibCes::CesSbcToUtf8);
}
} // namespace LibCes
namespace LibNpGameIntent {
LIB_VERSION("NpGameIntent", 1, "NpGameIntent", 1, 1);
@@ -3992,7 +3812,6 @@ LIB_DEFINE(InitNet_1) {
LibNpAuth::InitNet_1_NpAuth(s);
LibNpTrophy2::InitNet_1_NpTrophy2(s);
LibNpUniversalDataSystem::InitNet_1_NpUniversalDataSystem(s);
LibCes::InitNet_1_Ces(s);
LibNpGameIntent::InitNet_1_NpGameIntent(s);
LibNpWebApi2::InitNet_1_NpWebApi2(s);
LibGameUpdate::InitNet_1_GameUpdate(s);
+3 -1
View File
@@ -26,6 +26,7 @@ namespace SystemService {
[[maybe_unused]] constexpr int PARAM_ID_GAME_PARENTAL_LEVEL = 7;
[[maybe_unused]] constexpr int PARAM_ID_CC_ENABLE = 100;
[[maybe_unused]] constexpr int PARAM_ID_SCREEN_READER = 208;
constexpr int PARAM_ID_LANG_PS5 = 400; // PPSA01325
[[maybe_unused]] constexpr int PARAM_ID_ENTER_BUTTON_ASSIGN = 1000;
[[maybe_unused]] constexpr int PARAM_DATE_FORMAT_YYYYMMDD = 0;
@@ -96,7 +97,8 @@ static int KYTY_SYSV_ABI SystemServiceParamGetInt(int param_id, int* value) {
int v = 0;
switch (param_id) {
case PARAM_ID_LANG: v = static_cast<int>(Config::GetConsoleLanguage()); break;
case PARAM_ID_LANG:
case PARAM_ID_LANG_PS5: v = static_cast<int>(Config::GetConsoleLanguage()); break;
case PARAM_ID_DATE_FORMAT: v = PARAM_DATE_FORMAT_DDMMYYYY; break;
case PARAM_ID_TIME_FORMAT: v = PARAM_TIME_FORMAT_24HOUR; break;
case PARAM_ID_TIME_ZONE: v = +180; break;
+5
View File
@@ -42,6 +42,10 @@ namespace LibPsml {
LIB_DEFINE(InitPsml_1);
} // namespace LibPsml
namespace LibCes {
LIB_DEFINE(InitCes_1);
} // namespace LibCes
namespace LibC {
LIB_DEFINE(InitLibC_1);
} // namespace LibC
@@ -103,6 +107,7 @@ void InitAll(Loader::SymbolDatabase* s) {
LibContentExport::InitContentExport_1(s);
LibContentSearch::InitContentSearch_1(s);
LibC::InitLibC_1(s);
LibCes::InitCes_1(s);
InitDbgAddressSanitizer_1(s);
LibRazorCpu::InitRazorCpu_1(s);
InitDialog_1(s);
+2 -16
View File
@@ -117,8 +117,7 @@ public:
[[nodiscard]] Result DecodeInput(const Input& input, const FrameBuffer& frame_buffer,
Output* output) {
std::scoped_lock lock(m_mutex);
*output = {};
m_draining = false;
*output = {};
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
@@ -200,18 +199,7 @@ public:
return Result::ApiFail;
}
if (!m_draining) {
const int send_result = avcodec_send_packet(m_codec, nullptr);
if (send_result == 0 || send_result == AVERROR_EOF) {
m_draining = true;
} else if (send_result != AVERROR(EAGAIN)) {
LOGF("Videodec2: flushing decoder failed: %s (%d)\n", AvErrorString(send_result),
send_result);
av_frame_free(&frame);
return Result::ApiFail;
}
}
// Guest Flush collects available pictures between AUs while retaining reference frames.
const int receive_result = avcodec_receive_frame(m_codec, frame);
if (receive_result == AVERROR(EAGAIN) || receive_result == AVERROR_EOF) {
av_frame_free(&frame);
@@ -232,7 +220,6 @@ public:
void ResetDecoder() {
std::scoped_lock lock(m_mutex);
avcodec_flush_buffers(m_codec);
m_draining = false;
ClearPictureMetadata();
}
@@ -357,7 +344,6 @@ private:
Config m_config;
AVCodecContext* m_codec = nullptr;
SwsContext* m_sws = nullptr;
bool m_draining = false;
std::mutex m_mutex;
std::unordered_set<void*> m_picture_buffers;
};
+36
View File
@@ -801,6 +801,42 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
return true;
}
}
// Report whatever guest context can be read safely before terminating: which guest thread
// faulted, the register file, the faulting code bytes and the top of its stack.
{
char thread_name[64] = "(host thread)";
if (auto self = Libs::LibKernel::PthreadSelfOrNull(); self != nullptr) {
if (Libs::LibKernel::PthreadGetname(self, thread_name) != 0) {
std::snprintf(thread_name, sizeof(thread_name), "(unnamed guest thread)");
}
}
std::printf("--- Guest fault context ---\n");
std::printf("thread: %s\n", thread_name);
std::printf("rax=%016" PRIx64 " rbx=%016" PRIx64 " rcx=%016" PRIx64 " rdx=%016" PRIx64 "\n"
"rsi=%016" PRIx64 " rdi=%016" PRIx64 " rbp=%016" PRIx64 " rsp=%016" PRIx64 "\n"
"r8 =%016" PRIx64 " r9 =%016" PRIx64 " r10=%016" PRIx64 " r11=%016" PRIx64 "\n"
"r12=%016" PRIx64 " r13=%016" PRIx64 " r14=%016" PRIx64 " r15=%016" PRIx64 "\n",
info->rax, info->rbx, info->rcx, info->rdx, info->rsi, info->rdi, info->rbp,
info->rsp, info->r8, info->r9, info->r10, info->r11, info->r12, info->r13,
info->r14, info->r15);
if (IsReadableRange(info->exception_address - 48, 96)) {
const auto* code = reinterpret_cast<const uint8_t*>(info->exception_address - 48);
std::printf("code (pc-48 .. pc+48, fault at byte 48):");
for (int i = 0; i < 96; i++) {
std::printf("%s%02x", (i % 16 == 0) ? "\n " : " ", code[i]);
}
std::printf("\n");
}
if (IsReadableRange(info->rsp, 32 * sizeof(uint64_t))) {
const auto* stack = reinterpret_cast<const uint64_t*>(info->rsp);
std::printf("stack:");
for (int i = 0; i < 32; i++) {
std::printf("%s %016" PRIx64, (i % 4 == 0) ? "\n " : "", stack[i]);
}
std::printf("\n");
}
std::fflush(stdout);
}
EXIT("Unhandled host exception: type=%u code=%u pc=0x%016" PRIx64
" access=%u address=0x%016" PRIx64 "\n",
static_cast<unsigned>(info->type), info->native_code, info->exception_address,
-1
View File
@@ -3,7 +3,6 @@
#include "common/debug.h"
#include "common/file.h"
#include "common/magicEnum.h"
#include "common/platform/sysDbg.h"
#include "common/stringUtils.h"
#include "common/threads.h"
#include "common/virtualMemory.h"
+234
View File
@@ -1,11 +1,14 @@
#include "SDL.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "common/threads.h"
#include "graphics/presentation/window/windowInternal.h"
#include "kernel/fileSystem.h"
#include "libs/errno.h"
#include "libs/network.h"
#include "loader/symbolDatabase.h"
#include <array>
@@ -14,8 +17,15 @@
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace Libs::LibKernelApr {
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
}
namespace {
@@ -79,6 +89,217 @@ void CheckSaveRename(const std::filesystem::path &root,
"renamed save contents");
}
void CheckMountRoot(const std::filesystem::path &root) {
Common::File cache;
Check(cache.Create(root / "rpf.cache"), "create directory listing fixture");
cache.Close();
FileSystem::Mount(root, "/app0");
Check(FileSystem::GetRealFilename("/app0/rpf.cache") == root / "rpf.cache",
"resolve mount descendant");
Check(FileSystem::GetRealFilename("/app01/rpf.cache") == "/app01/rpf.cache",
"mount prefix must end at a path component");
for (const char *path : {"/app0", "/app0/"}) {
for (const int flags : {0, 0x00020000}) {
const int fd = FileSystem::KernelOpen(path, flags, 0);
Check(fd >= 3, "open mounted root with O_RDONLY or O_DIRECTORY");
std::array<char, 512> entries {};
const int size = FileSystem::KernelGetdents(fd, entries.data(), entries.size());
Check(size > 0 && size <= entries.size(), "enumerate mounted root");
bool found = false;
for (int offset = 0; offset < size;) {
// Directory record: inode, record length, type, name length, name.
Check(size - offset >= 8, "directory record header fits");
uint16_t length = 0;
std::memcpy(&length, entries.data() + offset + 4, sizeof(length));
const auto name_length = static_cast<uint8_t>(entries[offset + 7]);
Check(length >= 8 + name_length + 1 && length <= size - offset,
"directory record and name fit");
if (std::string_view(entries.data() + offset + 8, name_length) == "rpf.cache") {
Check(entries[offset + 6] == 8, "cache directory entry is a regular file");
found = true;
}
offset += length;
}
Check(found, "mounted root listing contains rpf.cache");
Check(FileSystem::KernelClose(fd) == OK, "close mounted root");
}
}
FileSystem::Umount("/app0");
}
void CheckDirectoryStream(const std::filesystem::path &root) {
const auto directory = root / "directory-stream";
Check(std::filesystem::create_directory(directory),
"create seek fixture directory");
for (int i = 0; i < 48; ++i) {
Common::File fixture;
Check(fixture.Create(directory / ("directory-entry-" + std::to_string(i))),
"create enough entries to cross directory blocks");
fixture.Close();
}
FileSystem::Mount(directory, "/app0");
const int fd = FileSystem::KernelOpen("/app0/", 0, 0);
Check(fd >= 3, "open directory as read-only asset");
const auto end = FileSystem::KernelLseek(fd, 0, 2);
Check(end > 512 && end % 512 == 0,
"directory SEEK_END uses padded stream size");
FileSystem::FileStat stat{};
Check(FileSystem::KernelFstat(fd, &stat) == OK && stat.st_size == end &&
stat.st_blksize == 512,
"directory stat agrees with seek and enumeration");
Check(FileSystem::KernelStat("/app0/", &stat) == OK && stat.st_size == end &&
stat.st_blocks == end / 512,
"path and descriptor directory stat agree");
Check(FileSystem::KernelLseek(fd, 0, 0) == 0,
"rewind directory for asset read");
std::vector<char> raw(static_cast<size_t>(end));
Check(FileSystem::KernelRead(fd, raw.data(), raw.size()) == end &&
FileSystem::KernelRead(fd, raw.data(), 1) == 0,
"raw directory read reaches EOF");
Check(FileSystem::KernelLseek(fd, -end, 1) == 0,
"directory SEEK_CUR uses the position advanced by read");
std::array<char, 512> block{};
int64_t base = -1;
for (int64_t offset = 0; offset < end; offset += block.size()) {
Check(FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) == block.size() &&
base == offset &&
std::memcmp(block.data(), raw.data() + offset, block.size()) == 0,
"directory enumeration shares raw bytes and reports each block "
"position");
for (size_t pos = 0; pos < block.size();) {
Check(block.size() - pos >= 8, "directory record header fits");
uint16_t length = 0;
std::memcpy(&length, block.data() + pos + 4, sizeof(length));
const auto name_length = static_cast<uint8_t>(block[pos + 7]);
Check(length >= 9 + name_length && length <= block.size() - pos &&
block[pos + 8 + name_length] == '\0',
"directory entries remain complete within every block");
pos += length;
}
}
Check(FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) == 0 &&
base == end,
"directory enumeration reports EOF position");
Check(FileSystem::KernelLseek(fd, 512, 0) == 512 &&
FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) == block.size() &&
base == 512 &&
std::memcmp(block.data(), raw.data() + 512, block.size()) == 0,
"restore and reread a directory enumeration position");
const auto position = FileSystem::KernelLseek(fd, 0, 1);
Check(
FileSystem::KernelLseek(fd, 0, 9) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, -1, 0) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, std::numeric_limits<int64_t>::min(), 1) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, std::numeric_limits<int64_t>::max(), 1) ==
Libs::LibKernel::KERNEL_ERROR_EOVERFLOW &&
FileSystem::KernelLseek(fd, 0, 1) == position,
"invalid and overflowing directory seeks preserve the position");
Check(FileSystem::KernelLseek(fd, -19, 2) == end - 19 &&
FileSystem::KernelRead(fd, block.data(), block.size()) == 19 &&
std::memcmp(block.data(), raw.data() + end - 19, 19) == 0,
"raw directory reads support byte positions and stop at EOF");
Check(FileSystem::KernelLseek(fd, 1, 0) == 1 &&
FileSystem::KernelGetdents(fd, block.data(), block.size()) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, 0, 1) == 1,
"directory enumeration rejects an incomplete record position");
Check(FileSystem::KernelLseek(fd, 512, 2) == end + 512 &&
FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, 0, 1) == end + 512,
"directory enumeration rejects a position beyond EOF");
Check(FileSystem::KernelRead(
fd, block.data(),
static_cast<size_t>(std::numeric_limits<int>::max()) + 1) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, 0, 1) == end + 512,
"oversized read fails without changing the directory position");
Check(FileSystem::KernelClose(fd) == OK, "close directory stream");
Check(FileSystem::KernelLseek(fd, 0, 0) ==
Libs::LibKernel::KERNEL_ERROR_EBADF,
"seek rejects a closed descriptor");
FileSystem::Umount("/app0");
}
void CheckAprPaths(const std::filesystem::path &root) {
Loader::SymbolDatabase symbols;
Libs::LibKernelApr::InitLibKernel_1_Apr(&symbols);
const auto *resolve_symbol = symbols.FindByNid("w5fcCG+t31g", Loader::SymbolType::Func);
const auto *each_symbol = symbols.FindByNid("C+Khtbbx2g8", Loader::SymbolType::Func);
Check(resolve_symbol && each_symbol, "APR path exports are registered");
using Resolve = int (KYTY_SYSV_ABI *)(const char *, const char *const *, uint32_t,
uint32_t *, uint64_t *, uint32_t *);
using ResolveEach = int (KYTY_SYSV_ABI *)(const char *, const char *const *, uint32_t,
uint32_t *, uint64_t *, int *);
const auto resolve = reinterpret_cast<Resolve>(resolve_symbol->vaddr);
const auto resolve_each = reinterpret_cast<ResolveEach>(each_symbol->vaddr);
Common::File fixture;
Check(fixture.Create(root / "apr.dat"), "create APR fixture");
fixture.Write("APR", 3);
fixture.Close();
FileSystem::Mount(root, "/app0");
uint32_t expected_id = 0xffffffffu;
for (const auto &parts : {std::array{"", "/app0/apr.dat"},
std::array{"/app0/", "apr.dat"},
std::array{"/", "app0/apr.dat"},
std::array{"/app", "0/apr.dat"}}) {
uint32_t id = 0xffffffffu, error_index = 0xffffffffu;
uint64_t size = 0;
Check(resolve(parts[0], &parts[1], 1, &id, &size, &error_index) == OK &&
id != 0xffffffffu && size == 3,
"APR concatenates empty, one-character and partial-component prefixes");
if (expected_id == 0xffffffffu) {
expected_id = id;
}
Check(id == expected_id, "equivalent APR paths return the same ID");
}
const char *paths[] = {"/app0/missing.dat", "/app0/apr.dat"};
uint32_t ids[2] = {}, error_index = 0xffffffffu;
uint64_t sizes[2] = {1, 1};
int results[2] = {};
Check(resolve_each("", paths, 2, ids, sizes, results) == 1 &&
results[0] == Libs::LibKernel::KERNEL_ERROR_ENOENT && results[1] == OK &&
ids[0] == 0xffffffffu && ids[1] == expected_id && sizes[0] == 0 && sizes[1] == 3,
"APR foreach reports a missing path and continues to the valid file");
// PATH_MAX includes NUL; all components remain below NAME_MAX (255).
std::string longest = "/app0/";
for (int i = 0; i < 3; ++i) {
longest += std::string(254, 'a') + '/';
}
longest += std::string(1023 - longest.size(), 'b');
paths[0] = longest.c_str();
Check(resolve("", paths, 1, ids, sizes, &error_index) == -1 &&
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENOENT && error_index == 0,
"APR accepts a pathname whose final NUL is at PATH_MAX minus one");
Check(resolve("/", paths, 1, ids, sizes, &error_index) == -1 &&
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENAMETOOLONG,
"APR rejects concatenated paths exceeding PATH_MAX");
std::array<char, 1024> unterminated;
unterminated.fill('/');
paths[0] = unterminated.data();
Check(resolve("", paths, 1, ids, sizes, &error_index) == -1 &&
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENAMETOOLONG,
"APR rejects an unterminated pathname");
paths[0] = "apr.dat";
Check(resolve(unterminated.data(), paths, 1, ids, sizes, &error_index) == -1 &&
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENAMETOOLONG,
"APR rejects an unterminated prefix");
FileSystem::Umount("/app0");
}
void CheckSocketWakeup() {
namespace Net = Libs::Network::Net;
// Guest sockaddr_in: length, family, network-order port/address, padding.
@@ -154,13 +375,26 @@ int main() {
Config::Load(options);
subsystems.Initialize<Log::Lifecycle>();
Check(SDL_InitSubSystem(SDL_INIT_VIDEO) == 0, "initialize Vulkan test video");
auto graphics = std::make_unique<Libs::Graphics::WindowContext>();
graphics->graphic_ctx.screen_width = 64;
graphics->graphic_ctx.screen_height = 64;
graphics->window = SDL_CreateWindow("KernelFileSystemTests", 0, 0, 64, 64,
SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN);
Check(graphics->window != nullptr, "create hidden Vulkan test window");
graphics->CreateVulkan();
TempDirectory temporary;
FileSystem::Initialize();
CheckMountRoot(temporary.Path());
CheckDirectoryStream(temporary.Path());
CheckAprPaths(temporary.Path());
FileSystem::Mount(temporary.Path(), "/savedata0");
CheckSaveRename(temporary.Path(), "first-save");
CheckSaveRename(temporary.Path(), "replacement-save");
FileSystem::Shutdown();
CheckSocketWakeup();
graphics.reset();
subsystems.Destroy();
std::printf("KernelFileSystemTests: all cases passed\n");
File diff suppressed because it is too large. Load diff
+75 -11
View File
@@ -4033,6 +4033,16 @@ void TestNewShaderDecoderArchitecture() {
Check(GetInstructionFamily(EncodeDs0(0x36)) == Family::DS,
"decoder did not classify DS directly");
const uint32_t orn2_saveexec_code[] = {0xbeea407eu};
Instruction orn2_saveexec;
ShaderRecompiler::Decoder::DecodeInstruction(orn2_saveexec_code, 0u, orn2_saveexec);
Check(orn2_saveexec.family == Family::SOP1 &&
orn2_saveexec.opcode == Opcode::S_ORN2_SAVEEXEC_B32 &&
orn2_saveexec.word_count == 1u && orn2_saveexec.src_count == 1u &&
orn2_saveexec.dst.kind == OperandKind::VccLo &&
orn2_saveexec.src0.kind == OperandKind::ExecLo,
"decoder rejected or misdecoded captured S_ORN2_SAVEEXEC_B32");
const uint32_t offset_code[] = {0u, EncodeVop1(0x01, 2, 3)};
Instruction direct;
ShaderRecompiler::Decoder::DecodeInstruction(offset_code, 1u, direct);
@@ -4224,6 +4234,19 @@ void TestNewShaderDecoderArchitecture() {
Check(ds.opcode == Opcode::DS_READ_B32 && ds.gds,
"DS decoder lost the GFX10 opcode or GDS fields");
for (bool decrement : {false, true}) {
const uint32_t words[] = {decrement ? 0xd8920004u : 0xd88e0004u, 0x03000302u};
Instruction atomic;
ShaderRecompiler::Decoder::DecodeInstruction(words, 0u, atomic);
Check(atomic.family == Family::DS &&
atomic.opcode == (decrement ? Opcode::DS_DEC_RTN_U32 : Opcode::DS_INC_RTN_U32) &&
atomic.word_count == 2u && atomic.src_count == 2u &&
atomic.data_dwords == 1u && atomic.data_bits == 32u &&
atomic.gds && atomic.offset == 4u && atomic.dst.reg == 3u &&
atomic.src0.reg == 2u && atomic.src1.reg == 3u,
"DS decoder misdecoded bounded atomic return fields");
}
const uint32_t boot_ds[] = {0xd8d4c480u, 0x45000045u};
Instruction boot;
ShaderRecompiler::Decoder::DecodeInstruction(boot_ds, 0u, boot);
@@ -4328,7 +4351,7 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() {
Check(decoded.family == Decoder::Family::VOPC &&
decoded.opcode == Decoder::Opcode::V_CMPX_CLASS_F32 &&
decoded.opcode_id == 0x98u && decoded.word_count == 2u &&
decoded.raw_count == 2u &&
decoded.raw[0] == shader[0] && decoded.raw[1] == shader[1] &&
decoded.dst.kind == Decoder::OperandKind::ExecLo &&
decoded.src_count == 2u &&
decoded.src0.kind == Decoder::OperandKind::Vgpr &&
@@ -9117,6 +9140,8 @@ void TestMeshExportStorage() {
EncodeExp0(0x14, 0x1), EncodeExp1(0, 0, 0, 0), // primitive
EncodeSopp(0x01),
};
std::vector<uint32_t> monolithic(std::begin(front), std::begin(front) + 2);
monolithic.insert(monolithic.end(), std::begin(back), std::end(back));
ShaderVertexInputInfo input{};
input.pa_cl_vs_out_cntl = (1u << 21u) | (1u << 18u);
auto &mesh = input.mesh;
@@ -9136,16 +9161,23 @@ void TestMeshExportStorage() {
ShaderRecompiler::CompileOptions options{};
options.stage = ShaderType::Mesh;
options.input_info.vertex = &input;
options.back_code = back;
options.user_data = user_data;
for (const auto [subgroup_size, push_data_start] :
{std::pair{32u, PushData::MeshDrawDwordCount},
std::pair{64u, PushData::MeshDrawDwordCount},
std::pair{32u, PushData::DwordCount},
std::pair{64u, PushData::DwordCount}}) {
struct Case {
uint32_t subgroup_size, push_data_start;
bool split;
};
const Case cases[] = {
{32u, PushData::MeshDrawDwordCount, true}, {64u, PushData::MeshDrawDwordCount, true},
{32u, PushData::DwordCount, true}, {64u, PushData::DwordCount, true},
{32u, PushData::MeshDrawDwordCount, false}, {64u, PushData::MeshDrawDwordCount, false},
{32u, PushData::DwordCount, false}, {64u, PushData::DwordCount, false},
};
for (const auto [subgroup_size, push_data_start, split] : cases) {
mesh.host_subgroup_size = subgroup_size;
const auto result =
RecompileForTest(front, options, nullptr, nullptr, push_data_start);
options.back_code = split ? std::span{back} : std::span<const uint32_t>{};
const auto result = RecompileForTest(
split ? std::span{front} : std::span<const uint32_t>{monolithic},
options, nullptr, nullptr, push_data_start);
CheckSpirvBinaryValidates(result.spirv);
const auto &layout = result.program.bindings;
Check(layout.UsesPushData() == (push_data_start == PushData::MeshDrawDwordCount) &&
@@ -9232,9 +9264,13 @@ void TestMergedShaderUserDataSnapshot() {
const std::array<uint32_t, 4> front_data = {regs.gs_user_sgpr.value[0],
regs.gs_user_sgpr.value[1], regs.gs_user_sgpr.value[2], regs.gs_user_sgpr.value[3]};
ShaderMappedData mapped{};
mapped.type = Prospero::ShaderBinaryType::kGsFront;
mapped.code_size_bytes = sizeof(front);
mapped.scratch_size_dwords = 3;
ShaderMapUserData(regs.es_regs.data_addr, mapped);
mapped.type = Prospero::ShaderBinaryType::kGsBack;
mapped.code_size_bytes = sizeof(back);
mapped.scratch_size_dwords = 7;
ShaderMapUserData(regs.gs_regs.data_addr, mapped);
HW::Context context;
context.SetShaderStages(0x20);
@@ -9255,8 +9291,9 @@ void TestMergedShaderUserDataSnapshot() {
second.user_data[8] == front_data[0] + 1,
"merged shader parameters did not snapshot ordinary user SGPRs at s8");
Check(first.hash == second.hash &&
MakeStageStaticKey(first_input) == MakeStageStaticKey(second_input),
"a dynamic merged-shader user-data pointer changed shader identity");
MakeStageStaticKey(first_input) == MakeStageStaticKey(second_input) &&
first_input.mesh.scratch_size_dwords == 7,
"merged shader identity or maximum scratch size changed with dynamic user data");
CompileOptions options{};
options.stage = ShaderType::Mesh;
options.user_data_base = 0;
@@ -9281,6 +9318,25 @@ void TestMergedShaderUserDataSnapshot() {
std::equal(table.begin(), table.end(), back_descriptor.dwords.begin()),
"merged shader resource plan did not follow the current s0:s1 pointer and s8 data");
}
const uint32_t monolithic[] = {
EncodeMubuf0(0x1c), EncodeMubuf1(0, 2, 1), EncodeSopp(0x01),
};
regs.es_regs.data_addr = reinterpret_cast<uint64_t>(monolithic);
mapped.type = Prospero::ShaderBinaryType::kGs;
mapped.code_size_bytes = sizeof(monolithic);
mapped.scratch_size_dwords = 3;
ShaderMapUserData(regs.es_regs.data_addr, mapped);
for (const auto back_address : {regs.gs_regs.data_addr, uint64_t{0}}) {
regs.gs_regs.data_addr = back_address;
ShaderVertexInputInfo input{};
const auto params = PrepareProgram(regs, context, user_config, input);
Check(params.back_code.empty() && params.hash == XXH3_64bits(monolithic, sizeof(monolithic)) &&
input.mesh.scratch_size_dwords == 3 && params.user_data.size() == 12 &&
params.user_data[0] == 0 && params.user_data[1] == 0 &&
std::equal(second.user_data.begin() + 8, second.user_data.end(),
params.user_data.begin() + 8),
"monolithic NGG shader used stale GS-back state or lost its s8 user data");
}
}
void TestEmbeddedVertexFormatSwizzle() {
@@ -9418,6 +9474,14 @@ void TestMeshInputAssembly() {
0x40000305, 1, 2, 3, 0, 15, false},
{Prospero::PrimitiveType::kTriStrip, 5, 8, 0, 1, 0, 0, 11,
0x40000305, 2, 1, 3, 0, 12, false},
{Prospero::PrimitiveType::kLineList, 5, 17, 3, 0, 0, 0, 11,
0x40000204, 0, 1, 0, 0, 23, false},
{Prospero::PrimitiveType::kLineList, 6, 17, 2, 4, 2, 0x1002, 11,
0x40000205, 8, 9, 0, 32, 0xabd8, true},
{Prospero::PrimitiveType::kLineList, 6, 17, 2, 5, 2, 0x1002, 0,
0x40000205, 10, 11, 0, 36, 0, false},
{Prospero::PrimitiveType::kLineList, 8, 180, 1, 64, 4, 0x1000, 0,
0x41000000, 128, 129, 0, 288, 0, false},
{Prospero::PrimitiveType::kPointList, 12, 265, 22, 0, 2, 0x1002, 0,
0x40000101, 0, 0, 0, 528, 0xabcd, true},
{Prospero::PrimitiveType::kPointList, 12, 265, 22, 1, 2, 0x1002, 0,