mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b9997baea | ||
|
|
393257eaae | ||
|
|
13e3ca314c | ||
|
|
b4853a0e08 | ||
|
|
ee4dd90649 | ||
|
|
3df456675d | ||
|
|
8abf99c56e | ||
|
|
7c6121cba5 | ||
|
|
46997dfc7a | ||
|
|
97fda7b10f | ||
|
|
dbe5353f3a | ||
|
|
e1ac893a0c | ||
|
|
b35cdc39b7 | ||
|
|
52dbc724f8 | ||
|
|
2f4b8a335d | ||
|
|
b38b7454a6 | ||
|
|
b65dc12208 | ||
|
|
58978d154a | ||
|
|
1f3adf5109 | ||
|
|
107d5f750a | ||
|
|
f40755173f | ||
|
|
4e14eed770 | ||
|
|
aa3b2cd57f | ||
|
|
4865dd92ca | ||
|
|
3c62359e05 | ||
|
|
62643c3424 | ||
|
|
210d4dcd99 | ||
|
|
5a8808086a | ||
|
|
861dd76d7f | ||
|
|
db41d0482f | ||
|
|
de62664908 | ||
|
|
34f6fd1f5f | ||
|
|
79a91ecfaf | ||
|
|
420bb51d6c | ||
|
|
c86c8b4d93 | ||
|
|
e43aadf75f | ||
|
|
9f25e3c23e | ||
|
|
7881c69a54 | ||
|
|
9e666e4dd2 | ||
|
|
c3a12397b0 | ||
|
|
45b6d2c597 | ||
|
|
7e011fa565 | ||
|
|
4d10ea17a0 | ||
|
|
006875ed4f | ||
|
|
a87889f032 | ||
|
|
9c79d9d298 | ||
|
|
c30ac7c904 | ||
|
|
038d2257de | ||
|
|
190f838ea4 | ||
|
|
470669c677 | ||
|
|
dd754065d8 | ||
|
|
743fdc6990 | ||
|
|
9f83fd5f0c | ||
|
|
5297abb7ac | ||
|
|
a8beac190f | ||
|
|
b96871e2bf | ||
|
|
21a1346e27 | ||
|
|
3ba6ebae81 | ||
|
|
fbf6ae400c | ||
|
|
d2413fc2eb | ||
|
|
96eb681b90 | ||
|
|
26cce9909a | ||
|
|
5af370c257 | ||
|
|
67d7db685a | ||
|
|
bb98f26433 | ||
|
|
cc44a43ebc | ||
|
|
2a18e66a4c |
No files matched your search
+9
-11
@@ -244,7 +244,7 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
"${KYTY_SOURCE_DIR}/loader/*.h"
|
||||
)
|
||||
|
||||
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
|
||||
find_program(KYTY_GLSLANG_VALIDATOR NAMES glslangValidator glslang REQUIRED)
|
||||
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
|
||||
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
||||
set(gpu_tiler_shader_names
|
||||
@@ -393,8 +393,10 @@ add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.c
|
||||
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
@@ -477,7 +479,6 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/BindingLayout.cpp"
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
@@ -674,15 +675,12 @@ if(BUILD_TESTING)
|
||||
add_test(NAME color_render_target_1d
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --rt1d-only)
|
||||
|
||||
if(WIN32)
|
||||
# These tests still depend on the Windows multisample-depth path.
|
||||
add_test(NAME texture_cache_image_overlap
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||
add_test(NAME texture_cache_htile_clear
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
endif()
|
||||
add_test(NAME texture_cache_image_overlap
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||
add_test(NAME texture_cache_htile_clear
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
ime_dialog_tests
|
||||
|
||||
@@ -141,7 +141,8 @@ the Vulkan/SPIR-V validation rules.
|
||||
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
|
||||
**C++ Clang tools for Windows** component
|
||||
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
|
||||
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslangValidator`) on `PATH`
|
||||
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslang` or `glslangValidator`) on `PATH`
|
||||
- Python 3 on `PATH`; the bundled SPIRV-Tools runs it at configure time
|
||||
|
||||
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
|
||||
|
||||
@@ -176,7 +177,7 @@ build has no working sound and no gamepad hotplug:
|
||||
|
||||
```bash
|
||||
sudo apt-get install --no-install-recommends \
|
||||
clang lld ninja-build cmake git glslang-tools pkg-config \
|
||||
clang lld ninja-build cmake git glslang-tools python3 pkg-config \
|
||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
||||
@@ -251,7 +252,7 @@ Requirements:
|
||||
|
||||
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
|
||||
- Xcode (or the Command Line Tools)
|
||||
- Homebrew packages: `brew install cmake ninja glslang`
|
||||
- Homebrew packages: `brew install cmake ninja glslang python`
|
||||
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
|
||||
universal and works; Homebrew's Qt is arm64-only and will not link
|
||||
|
||||
|
||||
@@ -152,6 +152,10 @@ bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
bool SyncRawImageBuffersEnabled() {
|
||||
return g_config->sync_raw_image_buffers;
|
||||
}
|
||||
|
||||
bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
@@ -164,6 +168,10 @@ bool PlayGoHackEnabled() {
|
||||
return g_config->playgo_hack_enabled;
|
||||
}
|
||||
|
||||
bool SkipNoticeScreen() {
|
||||
return g_config->skip_notice_screen;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled() {
|
||||
return g_config->red_zone_protection_enabled;
|
||||
|
||||
@@ -73,9 +73,11 @@ struct ConfigOptions {
|
||||
bool gpu_assisted_validation_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
bool playgo_hack_enabled = false;
|
||||
bool skip_notice_screen = false;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool red_zone_protection_enabled = false;
|
||||
#endif
|
||||
@@ -123,9 +125,11 @@ bool GpuAssistedValidationEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
bool SyncRawImageBuffersEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool TrophyEnabled();
|
||||
bool PlayGoHackEnabled();
|
||||
bool SkipNoticeScreen();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled();
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,12 @@ enum class ContextStateOperation : uint32_t {
|
||||
PushClear = 3,
|
||||
};
|
||||
|
||||
struct IndirectDrawRegisters {
|
||||
uint32_t vertex_offset;
|
||||
uint32_t instance_offset;
|
||||
uint32_t index_offset;
|
||||
};
|
||||
|
||||
class Pm4Execution {
|
||||
public:
|
||||
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
|
||||
@@ -79,10 +85,11 @@ public:
|
||||
void DrawIndex(DrawIndexArgs args);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
|
||||
void DrawIndexAuto(DrawAutoArgs args);
|
||||
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
uint32_t draw_initiator, bool indexed);
|
||||
IndirectDrawRegisters registers, uint32_t draw_initiator, bool indexed);
|
||||
void WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index,
|
||||
uint32_t event_write_source, void* dst_gpu_addr, uint32_t value,
|
||||
|
||||
@@ -184,7 +184,6 @@ static HW::RenderControl DecodeRenderControl(uint32_t value) {
|
||||
KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_STENCIL_TO_COLOR) != 0;
|
||||
r.copy_centroid = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_CENTROID) != 0;
|
||||
r.copy_sample = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_SAMPLE);
|
||||
EXIT_NOT_IMPLEMENTED(r.copy_depth_to_color || r.copy_stencil_to_color);
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -1642,8 +1641,12 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirect) {
|
||||
const auto data_offset = buffer[0];
|
||||
const auto draw_initiator = buffer[3];
|
||||
const bool indexed = (cmd_id == 0xc0032500);
|
||||
// The native indexed packet enables the high-half first-index destination in word 3.
|
||||
const IndirectDrawRegisters registers {
|
||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
||||
indexed && (buffer[2] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP};
|
||||
|
||||
cp.DrawIndirect(data_offset, draw_initiator, indexed);
|
||||
cp.DrawIndirect(data_offset, registers, draw_initiator, indexed);
|
||||
|
||||
return 4;
|
||||
}
|
||||
@@ -1661,13 +1664,17 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirectMulti) {
|
||||
const auto stride_in_bytes = buffer[7];
|
||||
const auto draw_initiator = buffer[8];
|
||||
const bool indexed = (cmd_id == 0xc0083800);
|
||||
// Multi-draw moves the first-index enable to word 4; the destinations stay in 2/3.
|
||||
const IndirectDrawRegisters registers {
|
||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
||||
indexed && (buffer[3] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP};
|
||||
|
||||
if (count_indirect == 0) {
|
||||
count_addr = nullptr;
|
||||
}
|
||||
|
||||
cp.DrawIndirectMulti(data_offset, max_count_or_count, count_addr, stride_in_bytes,
|
||||
draw_initiator, indexed);
|
||||
registers, draw_initiator, indexed);
|
||||
|
||||
return 9;
|
||||
}
|
||||
|
||||
@@ -875,15 +875,32 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
|
||||
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||
uint32_t vertex_offset, uint32_t instance_offset,
|
||||
uint32_t first_index) {
|
||||
const auto write = [&](uint32_t location, uint32_t value) {
|
||||
if (location == Pm4::SH_NOP) {
|
||||
return;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(location >= Pm4::SH_NUM ||
|
||||
g_hw_sh_indirect_func[location] == nullptr);
|
||||
g_hw_sh_indirect_func[location](cp, location, value);
|
||||
};
|
||||
write(registers.vertex_offset, vertex_offset);
|
||||
write(registers.instance_offset, instance_offset);
|
||||
write(registers.index_offset, first_index);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
DrawIndirectMulti(data_offset, 1, nullptr, args_size, draw_initiator, indexed);
|
||||
DrawIndirectMulti(data_offset, 1, nullptr, args_size, registers, draw_initiator, indexed);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr,
|
||||
uint32_t stride_in_bytes, uint32_t draw_initiator,
|
||||
bool indexed) {
|
||||
uint32_t stride_in_bytes, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u));
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
|
||||
@@ -918,6 +935,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
|
||||
if (!indexed) {
|
||||
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location,
|
||||
args->start_instance_location, 0);
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
@@ -928,6 +947,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location,
|
||||
args->start_instance_location, args->start_index_location);
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size);
|
||||
|
||||
@@ -82,9 +82,8 @@ public:
|
||||
static_assert(std::is_nothrow_invocable_v<RangeFunc&, uint64_t, uint64_t>);
|
||||
static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
|
||||
CheckNotInUploadCallback();
|
||||
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
|
||||
const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
Iterate<true>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->lock.lock();
|
||||
manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset,
|
||||
bytes, range_func);
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#elif defined(__x86_64__)
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
@@ -62,7 +68,13 @@ class SpinGuard final {
|
||||
public:
|
||||
explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) {
|
||||
while (m_lock.test_and_set(std::memory_order_acquire)) {
|
||||
std::atomic_signal_fence(std::memory_order_seq_cst);
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
_mm_pause();
|
||||
#elif defined(_M_ARM64)
|
||||
__yield();
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
~SpinGuard() { m_lock.clear(std::memory_order_release); }
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#elif defined(__x86_64__)
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
@@ -32,7 +38,13 @@ public:
|
||||
const auto thread = CurrentThread();
|
||||
while (m_lock.test_and_set(std::memory_order_acquire)) {
|
||||
EXIT_NOT_IMPLEMENTED(m_owner.load(std::memory_order_relaxed) == thread);
|
||||
std::atomic_signal_fence(std::memory_order_seq_cst);
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
_mm_pause();
|
||||
#elif defined(_M_ARM64)
|
||||
__yield();
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
m_owner.store(thread, std::memory_order_relaxed);
|
||||
}
|
||||
@@ -48,7 +60,9 @@ private:
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__APPLE__)
|
||||
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
static thread_local const uint32_t tid =
|
||||
static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
return tid;
|
||||
#elif defined(__linux__)
|
||||
static thread_local const uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
|
||||
return tid;
|
||||
|
||||
+29
-2
@@ -34,9 +34,36 @@ void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void*
|
||||
auto* bytes = static_cast<const uint8_t*>(source);
|
||||
while (size != 0) {
|
||||
const auto chunk = std::min(size, m_staging_buffer.Size());
|
||||
// Copy waits for ring reuse and flushes the fresh host data before submission.
|
||||
const auto offset = m_staging_buffer.Copy(bytes, chunk, 4);
|
||||
buffer.CopyFrom(m_scheduler.Current(), m_staging_buffer, offset, buffer.Offset(address),
|
||||
chunk, vk::AccessFlagBits::eHostWrite);
|
||||
const auto destination_offset = buffer.Offset(address);
|
||||
EXIT_IF(destination_offset > buffer.Size() || chunk > buffer.Size() - destination_offset);
|
||||
m_scheduler.EndRendering();
|
||||
const auto command = m_scheduler.Current().Handle();
|
||||
vk::BufferMemoryBarrier2 before {};
|
||||
before.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
before.srcAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
before.dstStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
||||
before.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
||||
before.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
before.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
before.buffer = buffer.Handle();
|
||||
before.offset = destination_offset;
|
||||
before.size = chunk;
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &before;
|
||||
command.pipelineBarrier2(dependency);
|
||||
const vk::BufferCopy copy {offset, destination_offset, chunk};
|
||||
command.copyBuffer(m_staging_buffer.Handle(), buffer.Handle(), 1, ©);
|
||||
auto after = before;
|
||||
after.srcStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
||||
after.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
||||
after.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
after.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
dependency.pBufferMemoryBarriers = &after;
|
||||
command.pipelineBarrier2(dependency);
|
||||
bytes += chunk;
|
||||
address += chunk;
|
||||
size -= chunk;
|
||||
|
||||
+9
-10
@@ -694,7 +694,9 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
requested.type == cached.info.type && requested.pitch == cached.info.pitch &&
|
||||
!requested.HasStencil() && !cached.info.HasStencil() && !requested.HasMetadata() &&
|
||||
!cached.info.HasMetadata();
|
||||
// PPSA04264
|
||||
// PPSA04264, PPSA04288
|
||||
// A partial view retains the entire matching array layout. HTile belongs to
|
||||
// the depth source, independently of the data slices copied into color storage.
|
||||
const bool retain_cached_layout =
|
||||
requested.samples == 1 && cached.info.samples == 1 && cached.backing.samples == 1 &&
|
||||
requested.bytes_per_block == cached.info.bytes_per_block &&
|
||||
@@ -713,7 +715,8 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
requested.data.size / requested.resources.layers ==
|
||||
cached.info.data.size / cached.info.resources.layers &&
|
||||
!requested.HasStencil() && !cached.info.HasStencil() && !requested.HasMetadata() &&
|
||||
!cached.info.HasMetadata();
|
||||
(cached.info.metadata.kind == ImageMetadataKind::None ||
|
||||
cached.info.metadata.kind == ImageMetadataKind::Htile);
|
||||
bool recreate = cached.info.resources < requested.resources ||
|
||||
requested.IsVolume() != cached.info.IsVolume();
|
||||
switch (binding) {
|
||||
@@ -738,8 +741,6 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
info.data = cached.info.data;
|
||||
info.resources = cached.info.resources;
|
||||
info.mip_layout = cached.info.mip_layout;
|
||||
} else {
|
||||
info.resources = std::max(requested.resources, cached.info.resources);
|
||||
}
|
||||
info.htile_clear_mask = 0;
|
||||
const auto replacement_id = InsertImage(info);
|
||||
@@ -1791,14 +1792,12 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
const auto available = buffer.Size() - buf_offset;
|
||||
uint32_t levels = 0;
|
||||
uint64_t copy_size = 0;
|
||||
if (image.info.IsVolume()) {
|
||||
// Volume mips contain strided block slices, so a mip's linear span cannot prove that
|
||||
// every retained slice fits. Keep volume synchronization whole-image only.
|
||||
if (!buffer.IsInBounds(image.info.data.address, image.info.data.size)) {
|
||||
return false;
|
||||
}
|
||||
if (buffer.IsInBounds(image.info.data.address, image.info.data.size)) {
|
||||
levels = image.info.resources.levels;
|
||||
copy_size = image.info.data.size;
|
||||
} else if (image.info.IsVolume() || image.info.resources.layers > 1) {
|
||||
// Array and volume slices are strided across the full mip chain.
|
||||
return false;
|
||||
} else {
|
||||
for (; levels < image.info.resources.levels; ++levels) {
|
||||
const auto& mip = image.info.mip_layout[levels];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
@@ -25,7 +26,6 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "kernel/memory.h"
|
||||
@@ -75,10 +75,6 @@ vk::DescriptorType NativeDescriptorType(BindingKind kind) {
|
||||
EXIT("invalid native descriptor binding kind");
|
||||
}
|
||||
|
||||
uint32_t NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
|
||||
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
|
||||
}
|
||||
|
||||
vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t element) {
|
||||
vk::ImageView view = nullptr;
|
||||
if (texture.mip_views.empty()) {
|
||||
@@ -126,8 +122,9 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
|
||||
if (size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
|
||||
EXIT("storage buffer range is unsupported\n");
|
||||
}
|
||||
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(address, size, resource.written,
|
||||
resource.formatted, id);
|
||||
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(
|
||||
address, size, resource.written,
|
||||
resource.formatted || (resource.read && Config::SyncRawImageBuffersEnabled()), id);
|
||||
const auto aligned_offset = Common::AlignDown(offset, alignment);
|
||||
const auto adjustment = offset - aligned_offset;
|
||||
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
|
||||
@@ -797,12 +794,11 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
m_context, program, i, snapshot.samplers[program.info.samplers[i].snapshot_index]));
|
||||
}
|
||||
prepared.shader_data.reserve(program.bindings.ShaderDataDwords());
|
||||
for (const auto reg: program.bindings.user_data_registers) {
|
||||
for (const auto reg: program.info.user_data_registers) {
|
||||
prepared.shader_data.push_back(snapshot.user_data[reg - program.user_data_base]);
|
||||
}
|
||||
prepared.shader_data.resize(program.bindings.ShaderDataDwords());
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
|
||||
if (program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::Gds)] != 0) {
|
||||
prepared.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
|
||||
}
|
||||
}
|
||||
@@ -815,19 +811,20 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
prepared.buffer_sources.clear();
|
||||
const auto& layout = program.bindings;
|
||||
if (layout.memory_offset_count == 0) {
|
||||
prepared.buffer_sources.resize(layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)] == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto& resources = layout.descriptors.front().resources;
|
||||
prepared.buffer_sources.reserve(resources.size());
|
||||
for (const auto resource: resources) {
|
||||
for (uint32_t resource = 0; resource < program.info.buffers.size(); ++resource) {
|
||||
const auto& metadata = program.info.buffers[resource];
|
||||
if (metadata.descriptor_index == UINT32_MAX) continue;
|
||||
auto& source = prepared.buffer_sources[metadata.descriptor_index];
|
||||
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[resource]);
|
||||
const auto address = descriptor.Base48();
|
||||
auto size = descriptor.GetSize();
|
||||
if (address == 0 || size == 0) {
|
||||
prepared.buffer_sources.push_back({});
|
||||
source = {};
|
||||
continue;
|
||||
}
|
||||
if (descriptor.NumRecords() == UINT32_MAX) {
|
||||
@@ -851,7 +848,7 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
}
|
||||
}
|
||||
size = Libs::LibKernel::Memory::ClampRangeSize(address, size);
|
||||
prepared.buffer_sources.push_back({address, size, cache.FindBuffer(address, size)});
|
||||
source = {address, size, cache.FindBuffer(address, size)};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -862,10 +859,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_sources.size() != layout.memory_offset_count);
|
||||
EXIT_IF(prepared.buffer_sources.size() != layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
|
||||
prepared.buffers.clear();
|
||||
prepared.buffers.reserve(layout.memory_offset_count);
|
||||
prepared.buffers.resize(layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
EXIT_IF(prepared.shader_data.size() != layout.ShaderDataDwords());
|
||||
std::fill(prepared.shader_data.begin() + layout.memory_offset_dword,
|
||||
prepared.shader_data.end(), 0);
|
||||
@@ -874,20 +870,18 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto shift = (index % 4u) * 8u;
|
||||
prepared.shader_data[dword] |= offset << shift;
|
||||
};
|
||||
for (uint32_t i = 0; i < layout.memory_offset_count; i++) {
|
||||
const auto resource = layout.descriptors.front().resources[i];
|
||||
for (const auto& resource: program.info.buffers) {
|
||||
const auto index = resource.descriptor_index;
|
||||
if (index == UINT32_MAX) continue;
|
||||
uint32_t buffer_offset = 0;
|
||||
prepared.buffers.push_back(NativeStorageBuffer(m_context, prepared.buffer_sources[i],
|
||||
program.info.buffers[resource],
|
||||
buffer_offset));
|
||||
pack_memory_offset(i, buffer_offset);
|
||||
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
||||
resource, buffer_offset);
|
||||
pack_memory_offset(index, buffer_offset);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
layout, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] != 0) {
|
||||
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::ShaderData) != nullptr) {
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] != 0) {
|
||||
prepared.shader_data_buffer = NativeUpload(m_context, prepared.shader_data);
|
||||
}
|
||||
}
|
||||
@@ -979,7 +973,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
std::span<PreparedBindings* const> prepared_bindings) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto vk_buffer = buffer.Handle();
|
||||
size_t descriptor_count = 0;
|
||||
size_t image_descriptor_count = 0;
|
||||
size_t write_count = 0;
|
||||
ShaderRecompiler::IR::PushData push_data;
|
||||
bool has_push_data = false;
|
||||
@@ -993,9 +987,14 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
for (const auto* prepared: prepared_bindings) {
|
||||
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);
|
||||
write_count += std::popcount(program.bindings.descriptor_mask);
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto count = program.bindings.descriptor_counts[index];
|
||||
if (index >= ShaderRecompiler::IR::FirstImageBinding &&
|
||||
index <= static_cast<size_t>(BindingKind::Samplers)) {
|
||||
image_descriptor_count += count;
|
||||
}
|
||||
}
|
||||
const auto shader_stage = NativeShaderStage(program.stage);
|
||||
push_stages |= shader_stage;
|
||||
@@ -1027,10 +1026,9 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
const auto& program = *writer->runtime->program;
|
||||
for (uint32_t i = 0; i < writer->buffer_sources.size(); ++i) {
|
||||
const auto resource = program.bindings.descriptors.front().resources[i];
|
||||
if (!program.info.buffers[resource].written) continue;
|
||||
const auto& written = writer->buffer_sources[i];
|
||||
for (const auto& resource: program.info.buffers) {
|
||||
if (!resource.written || resource.descriptor_index == UINT32_MAX) continue;
|
||||
const auto& written = writer->buffer_sources[resource.descriptor_index];
|
||||
if (written.size != 0 && ImageRangeOverlaps(address, size,
|
||||
written.address, written.size)) {
|
||||
EXIT("scalar resource reads overlap a shader buffer write\n");
|
||||
@@ -1039,12 +1037,11 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
m_descriptor_buffers.clear();
|
||||
m_descriptor_images.clear();
|
||||
m_descriptor_images.resize(image_descriptor_count);
|
||||
m_descriptor_writes.clear();
|
||||
m_descriptor_buffers.reserve(descriptor_count);
|
||||
m_descriptor_images.reserve(descriptor_count);
|
||||
m_descriptor_writes.reserve(write_count);
|
||||
size_t image_cursor = 0;
|
||||
std::array<vk::DescriptorBufferInfo, 2> address_buffers;
|
||||
|
||||
for (auto* prepared: prepared_bindings) {
|
||||
const auto& program = *prepared->runtime->program;
|
||||
@@ -1109,37 +1106,37 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
binding.layout = image.backing.state.layout;
|
||||
}
|
||||
|
||||
m_image_occurrences.assign(descriptors.images.size(), 0);
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
std::array<size_t, ShaderRecompiler::IR::ImageBindingCount> image_starts {};
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto kind = static_cast<BindingKind>(index);
|
||||
vk::WriteDescriptorSet write {};
|
||||
write.dstBinding = ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind);
|
||||
write.descriptorType = NativeDescriptorType(binding.kind);
|
||||
write.descriptorCount = NativeDescriptorCount(binding);
|
||||
const auto buffer_start = m_descriptor_buffers.size();
|
||||
const auto image_start = m_descriptor_images.size();
|
||||
if (ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind) !=
|
||||
write.dstBinding = ShaderRecompiler::IR::NativeBinding(program.stage, kind);
|
||||
write.descriptorType = NativeDescriptorType(kind);
|
||||
write.descriptorCount = program.bindings.descriptor_counts[index];
|
||||
const auto image_start = image_cursor;
|
||||
if (ShaderRecompiler::IR::ImageBindingResourceClass(kind) !=
|
||||
ShaderRecompiler::IR::ImageResourceClass::None) {
|
||||
for (const auto resource: binding.resources) {
|
||||
m_descriptor_images.push_back(MakeImageInfo(
|
||||
descriptors.images.at(resource), m_image_occurrences.at(resource)++));
|
||||
}
|
||||
image_starts[ShaderRecompiler::IR::ImageBindingIndex(kind)] = image_start;
|
||||
image_cursor += write.descriptorCount;
|
||||
} else {
|
||||
switch (binding.kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Buffers:
|
||||
EXIT_IF(descriptors.buffers.size() != binding.resources.size());
|
||||
EXIT_IF(descriptors.buffers.size() != write.descriptorCount);
|
||||
for (const auto& view: descriptors.buffers) {
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
m_descriptor_buffers.push_back(view);
|
||||
}
|
||||
write.pBufferInfo = descriptors.buffers.data();
|
||||
break;
|
||||
case BindingKind::BdaPagetable:
|
||||
case BindingKind::FaultBuffer: {
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
const auto* bda_buffer = binding.kind == BindingKind::BdaPagetable
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
const auto* bda_buffer = kind == BindingKind::BdaPagetable
|
||||
? cache.GetBdaPageTableBuffer()
|
||||
: cache.GetFaultBuffer();
|
||||
m_descriptor_buffers.emplace_back(bda_buffer->Handle(), 0,
|
||||
bda_buffer->Size());
|
||||
auto& view = address_buffers[kind == BindingKind::FaultBuffer];
|
||||
view = {bda_buffer->Handle(), 0, bda_buffer->Size()};
|
||||
write.pBufferInfo = &view;
|
||||
break;
|
||||
}
|
||||
case BindingKind::FlattenedSrt:
|
||||
@@ -1147,42 +1144,41 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
case BindingKind::SharedMemory:
|
||||
case BindingKind::Gds: {
|
||||
const vk::DescriptorBufferInfo* view = &descriptors.gds;
|
||||
if (binding.kind == BindingKind::FlattenedSrt) {
|
||||
if (kind == BindingKind::FlattenedSrt) {
|
||||
view = &descriptors.flattened_srt;
|
||||
} else if (binding.kind == BindingKind::ShaderData) {
|
||||
} else if (kind == BindingKind::ShaderData) {
|
||||
view = &descriptors.shader_data_buffer;
|
||||
} else if (binding.kind == BindingKind::SharedMemory) {
|
||||
} else if (kind == BindingKind::SharedMemory) {
|
||||
view = &descriptors.shared_memory;
|
||||
}
|
||||
EXIT_IF(view->buffer == nullptr);
|
||||
m_descriptor_buffers.push_back(*view);
|
||||
write.pBufferInfo = view;
|
||||
break;
|
||||
}
|
||||
case BindingKind::Samplers:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto sampler = descriptors.samplers.at(resource);
|
||||
for (const auto sampler: descriptors.samplers) {
|
||||
EXIT_IF(sampler == nullptr);
|
||||
m_descriptor_images.emplace_back(sampler, nullptr,
|
||||
vk::ImageLayout::eUndefined);
|
||||
m_descriptor_images[image_cursor++] = {sampler, nullptr, vk::ImageLayout::eUndefined};
|
||||
}
|
||||
break;
|
||||
case BindingKind::Count: EXIT("invalid descriptor binding kind");
|
||||
}
|
||||
}
|
||||
if (m_descriptor_buffers.size() != buffer_start) {
|
||||
write.pBufferInfo = m_descriptor_buffers.data() + buffer_start;
|
||||
}
|
||||
if (m_descriptor_images.size() != image_start) {
|
||||
if (image_cursor != image_start) {
|
||||
write.pImageInfo = m_descriptor_images.data() + image_start;
|
||||
}
|
||||
m_descriptor_writes.push_back(write);
|
||||
}
|
||||
for (uint32_t i = 0; i < descriptors.images.size(); i++) {
|
||||
const auto expected =
|
||||
descriptors.images[i].mip_views.empty()
|
||||
? 1u
|
||||
: static_cast<uint32_t>(descriptors.images[i].mip_views.size());
|
||||
EXIT_IF(m_image_occurrences[i] != expected);
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& resource = program.info.images[i];
|
||||
const auto& texture = descriptors.images[i];
|
||||
EXIT_IF(resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size()));
|
||||
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(resource);
|
||||
const auto start = image_starts[ShaderRecompiler::IR::ImageBindingIndex(kind)] +
|
||||
resource.descriptor_index;
|
||||
for (uint32_t mip = 0; mip < resource.mip_count; ++mip) {
|
||||
m_descriptor_images[start + mip] = MakeImageInfo(texture, mip);
|
||||
}
|
||||
}
|
||||
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
|
||||
@@ -50,8 +50,6 @@ struct PreparedBindings {
|
||||
|
||||
[[nodiscard]] vk::DescriptorType
|
||||
NativeDescriptorType(ShaderRecompiler::IR::DescriptorBindingKind kind);
|
||||
[[nodiscard]] uint32_t
|
||||
NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding);
|
||||
[[nodiscard]] vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
|
||||
uint32_t element = 0);
|
||||
|
||||
|
||||
@@ -617,6 +617,13 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
ShaderParams pixel_params;
|
||||
if (pixel_active) {
|
||||
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
if (!mesh_active && !tess_active && Prospero::IsRectList(user_config.GetPrimType())) {
|
||||
pixel_info.parameter_mode = ShaderPixelParameterMode::Rectangle;
|
||||
} else if (context.GetModeControl().provoking_vtx_last) {
|
||||
pixel_info.parameter_mode = ShaderPixelParameterMode::LastVertex;
|
||||
} else {
|
||||
pixel_info.parameter_mode = ShaderPixelParameterMode::FirstVertex;
|
||||
}
|
||||
const auto& blend = context.GetBlendControl(0);
|
||||
pixel_info.dual_source_blending =
|
||||
blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
@@ -180,10 +181,12 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
vk::ShaderStageFlagBits stage) {
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto kind = static_cast<ShaderRecompiler::IR::DescriptorBindingKind>(index);
|
||||
descriptor_bindings.push_back(
|
||||
{ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind),
|
||||
NativeDescriptorType(binding.kind), NativeDescriptorCount(binding), stage, nullptr});
|
||||
{ShaderRecompiler::IR::NativeBinding(program.stage, kind), NativeDescriptorType(kind),
|
||||
program.bindings.descriptor_counts[index], stage, nullptr});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -213,10 +213,8 @@ private:
|
||||
GraphicsBindings m_graphics_bindings;
|
||||
PreparedBindings m_compute_bindings;
|
||||
std::vector<ImageId> m_bound_images;
|
||||
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
|
||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
||||
std::vector<uint32_t> m_image_occurrences;
|
||||
|
||||
friend class CommandProcessor;
|
||||
friend struct RenderExecutorTestAccess;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
#include "kernel/pthread.h"
|
||||
@@ -198,8 +197,8 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
||||
|
||||
static void BindSharedMemory(RenderContext& context, ShaderComputeInputInfo& input,
|
||||
PreparedBindings& bindings, uint64_t indirect_args = 0) {
|
||||
if (ShaderRecompiler::IR::FindBinding(input.stage.program->bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::SharedMemory) == nullptr) {
|
||||
if (input.stage.program->bindings.descriptor_counts[
|
||||
static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::SharedMemory)] == 0) {
|
||||
return;
|
||||
}
|
||||
auto& cache = context.GetBufferCache();
|
||||
|
||||
@@ -589,8 +589,12 @@ static uint32_t DrawColorOutputMask(const HW::Context& ctx) {
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask;
|
||||
uint32_t output_mask = 0;
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
if (sh_regs.target_output_mode[slot] != 0 &&
|
||||
for (uint32_t index = 0; index < RENDER_COLOR_ATTACHMENTS_MAX; index++) {
|
||||
const auto slot = ShaderPixelExportTarget(sh_regs.m_cbShaderMask, index);
|
||||
if (slot >= RENDER_COLOR_ATTACHMENTS_MAX) {
|
||||
break;
|
||||
}
|
||||
if (sh_regs.target_output_mode[index] != 0 &&
|
||||
render_target_mask_slot(write_mask, slot) != 0) {
|
||||
output_mask |= 1u << slot;
|
||||
}
|
||||
@@ -902,7 +906,7 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal
|
||||
if (state.ps_active) {
|
||||
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt) {
|
||||
mrt_mask |= 1u << output.index;
|
||||
mrt_mask |= 1u << output.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1280,8 +1284,10 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
emit.vertex_offset = vertex_offset + args.base_vertex;
|
||||
emit.first_instance = instance_offset;
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex;
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
|
||||
primitive_restart);
|
||||
@@ -1369,8 +1375,10 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
DrawEmitInfo emit {};
|
||||
emit.first_vertex = static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = instance_offset;
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.first_vertex = native_indirect ? 0 :
|
||||
static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false);
|
||||
|
||||
@@ -195,6 +195,37 @@ std::mutex g_trophy_notification_mutex;
|
||||
std::deque<TrophyNotification> g_trophy_notifications;
|
||||
std::atomic<bool> g_trophy_notification_active {false};
|
||||
|
||||
std::vector<std::string_view> WrapTrophyTitle(std::string_view text, float font_size, float width) {
|
||||
std::vector<std::string_view> lines;
|
||||
auto* const font = ImGui::GetFont();
|
||||
for (;;) {
|
||||
const auto newline = text.find('\n');
|
||||
const auto paragraph = text.substr(0, newline);
|
||||
auto line = paragraph.substr(0, 0);
|
||||
for (auto start = paragraph.find_first_not_of(" \t\r"); start != std::string_view::npos;) {
|
||||
const auto end = std::min(paragraph.find_first_of(" \t\r", start), paragraph.size());
|
||||
const auto word = paragraph.substr(start, end - start);
|
||||
const std::string_view candidate(line.empty() ? word.begin() : line.begin(),
|
||||
word.end());
|
||||
// Keep an oversized word intact; the toast's clip rectangle handles overflow.
|
||||
if (!line.empty() && font->CalcTextSizeA(font_size, std::numeric_limits<float>::max(),
|
||||
0.0f, candidate.data(),
|
||||
candidate.data() + candidate.size()).x > width) {
|
||||
lines.push_back(line);
|
||||
line = word;
|
||||
} else {
|
||||
line = candidate;
|
||||
}
|
||||
start = paragraph.find_first_not_of(" \t\r", end);
|
||||
}
|
||||
lines.push_back(line);
|
||||
if (newline == std::string_view::npos) {
|
||||
return lines;
|
||||
}
|
||||
text.remove_prefix(newline + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearInputEvents() {
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
g_input_events.clear();
|
||||
@@ -1101,7 +1132,26 @@ struct SystemOverlay::Impl {
|
||||
0.65f, 1.25f);
|
||||
const float width = std::max(
|
||||
0.0f, std::min(520.0f * screen_scale, static_cast<float>(frame_extent.width) - 32.0f));
|
||||
const float height = 104.0f * screen_scale;
|
||||
constexpr std::array GRADE_COLORS {
|
||||
IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255), IM_COL32(211, 220, 232, 255),
|
||||
IM_COL32(211, 142, 91, 255)};
|
||||
const int grade = trophy_notification->grade;
|
||||
const bool has_grade = grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size());
|
||||
const float pad = 10.0f * screen_scale;
|
||||
const float base_height = 104.0f * screen_scale;
|
||||
const float icon_size = base_height - 2.0f * pad;
|
||||
const float text_offset = trophy_notification_image != nullptr
|
||||
? pad + icon_size + 18.0f * screen_scale
|
||||
: 20.0f * screen_scale;
|
||||
const float title_offset = text_offset + (has_grade ? 30.0f * screen_scale : 0.0f);
|
||||
const float title_size = 26.0f * screen_scale;
|
||||
const float line_height = 30.0f * screen_scale;
|
||||
const float text_padding = 16.0f * screen_scale;
|
||||
const auto title_lines = WrapTrophyTitle(trophy_notification->name, title_size,
|
||||
width - title_offset - text_padding);
|
||||
const float block_height =
|
||||
static_cast<float>(title_lines.size()) * line_height + 28.0f * screen_scale;
|
||||
const float height = std::max(base_height, block_height + 2.0f * text_padding);
|
||||
const float rounding = 14.0f * screen_scale;
|
||||
const float fade_in = std::clamp(age / 0.22f, 0.0f, 1.0f);
|
||||
const float fade_out = std::clamp((TOAST_LIFETIME - age) / 0.4f, 0.0f, 1.0f);
|
||||
@@ -1121,26 +1171,19 @@ struct SystemOverlay::Impl {
|
||||
draw->AddRect(top_left, bottom_right, fade(255, 255, 255, 28), rounding,
|
||||
ImDrawFlags_RoundCornersAll, 1.0f);
|
||||
|
||||
const float pad = 10.0f * screen_scale;
|
||||
const float icon_size = height - 2.0f * pad;
|
||||
float text_left = top_left.x + 20.0f * screen_scale;
|
||||
const float text_left = top_left.x + text_offset;
|
||||
if (trophy_notification_image != nullptr) {
|
||||
const ImVec2 icon_min {top_left.x + pad, top_left.y + pad};
|
||||
const ImVec2 icon_min {top_left.x + pad, top_left.y + (height - icon_size) * 0.5f};
|
||||
const ImVec2 icon_max {icon_min.x + icon_size, icon_min.y + icon_size};
|
||||
draw->AddImageRounded(trophy_notification_image->GetTexRef(), icon_min, icon_max,
|
||||
{0, 0}, {1, 1}, fade(255, 255, 255, 255), 8.0f * screen_scale);
|
||||
draw->AddRect(icon_min, icon_max, fade(255, 255, 255, 40), 8.0f * screen_scale,
|
||||
ImDrawFlags_RoundCornersAll, 1.0f);
|
||||
text_left = icon_max.x + 18.0f * screen_scale;
|
||||
}
|
||||
|
||||
constexpr std::array GRADE_COLORS {IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255),
|
||||
IM_COL32(211, 220, 232, 255),
|
||||
IM_COL32(211, 142, 91, 255)};
|
||||
const int grade = trophy_notification->grade;
|
||||
const float title_y = top_left.y + height * 0.5f - 25.0f * screen_scale;
|
||||
float title_x = text_left;
|
||||
if (grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size())) {
|
||||
const float title_y = top_left.y + (height - block_height) * 0.5f;
|
||||
const float title_x = top_left.x + title_offset;
|
||||
if (has_grade) {
|
||||
// Small cup glyph tinted with the trophy grade.
|
||||
const ImU32 cup = ImGui::GetColorU32(GRADE_COLORS[grade - 1], alpha);
|
||||
const float u = screen_scale;
|
||||
@@ -1155,12 +1198,15 @@ struct SystemOverlay::Impl {
|
||||
draw->AddRectFilled({cx - 1.5f * u, cy + 2.0f * u}, {cx + 1.5f * u, cy + 7.0f * u}, cup);
|
||||
draw->AddRectFilled({cx - 5.0f * u, cy + 7.0f * u}, {cx + 5.0f * u, cy + 10.0f * u}, cup,
|
||||
1.5f * u);
|
||||
title_x += 30.0f * u;
|
||||
}
|
||||
draw->PushClipRect(top_left, {bottom_right.x - 16.0f * screen_scale, bottom_right.y}, true);
|
||||
draw->AddText(nullptr, 26.0f * screen_scale, {title_x, title_y},
|
||||
fade(255, 255, 255, 255), trophy_notification->name.c_str());
|
||||
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, title_y + 38.0f * screen_scale},
|
||||
draw->PushClipRect(top_left, {bottom_right.x - text_padding, bottom_right.y}, true);
|
||||
float line_y = title_y;
|
||||
for (const auto line : title_lines) {
|
||||
draw->AddText(nullptr, title_size, {title_x, line_y}, fade(255, 255, 255, 255),
|
||||
line.data(), line.data() + line.size());
|
||||
line_y += line_height;
|
||||
}
|
||||
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, line_y + 8.0f * screen_scale},
|
||||
fade(176, 180, 192, 255), "Trophy earned!");
|
||||
draw->PopClipRect();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translate.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
|
||||
@@ -670,20 +669,17 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
|
||||
IR::CollectShaderInfo(ir, options.input_info);
|
||||
IR::AllocateBindings(ir, push_data_start_dword,
|
||||
ir.stage == ShaderType::Compute && options.input_info.compute != nullptr &&
|
||||
options.input_info.compute->lds_storage);
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(translated.cfg_dump, ir);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
LOGF("%s native IR (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash,
|
||||
|
||||
@@ -171,10 +171,12 @@ void Builder::AddFunction(std::span<const uint32_t> words) {
|
||||
}
|
||||
|
||||
DeferredPhi Builder::AddDeferredPhi(uint32_t type, uint32_t result, size_t incoming_count) {
|
||||
std::vector<uint32_t> words {spv::OpPhi, type, result};
|
||||
words.resize(words.size() + incoming_count * 2u);
|
||||
const DeferredPhi phi {m_functions.size()};
|
||||
AddFunction(words);
|
||||
const auto word_count = static_cast<uint32_t>(3u + incoming_count * 2u);
|
||||
m_functions.push_back((word_count << spv::WordCountShift) | spv::OpPhi);
|
||||
m_functions.push_back(type);
|
||||
m_functions.push_back(result);
|
||||
m_functions.resize(m_functions.size() + incoming_count * 2u);
|
||||
m_unpatched_phi_incomings += incoming_count;
|
||||
return phi;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -19,92 +18,14 @@ namespace {
|
||||
}
|
||||
|
||||
void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
constexpr auto KindCount = static_cast<size_t>(Kind::Count);
|
||||
std::array<std::vector<uint32_t>, KindCount> expected;
|
||||
std::array<bool, KindCount> present {};
|
||||
const auto Dense = [](size_t size) {
|
||||
std::vector<uint32_t> values(size);
|
||||
for (uint32_t i = 0; i < values.size(); i++) {
|
||||
values[i] = i;
|
||||
}
|
||||
return values;
|
||||
uint64_t live_buffers = 0;
|
||||
bool uses_lds = false;
|
||||
bool uses_gds = false;
|
||||
const auto uses_mapping = [](const auto& resource) {
|
||||
return resource.indirect_root != UINT32_MAX;
|
||||
};
|
||||
auto Expect = [&](Kind kind, std::vector<uint32_t> resources = {}) {
|
||||
const auto index = static_cast<size_t>(kind);
|
||||
present[index] = true;
|
||||
expected[index] = std::move(resources);
|
||||
};
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = IR::DescriptorBindingForImage(program.info.images[i]);
|
||||
if (!kind.has_value()) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
present[static_cast<size_t>(*kind)] = true;
|
||||
const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::Dynamic;
|
||||
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
}
|
||||
expected[static_cast<size_t>(*kind)].insert(expected[static_cast<size_t>(*kind)].end(),
|
||||
count, i);
|
||||
}
|
||||
if (!program.info.samplers.empty()) {
|
||||
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
|
||||
}
|
||||
auto& buffers = expected[static_cast<size_t>(Kind::Buffers)];
|
||||
const auto shared = IR::CollectMemoryResources(program, buffers);
|
||||
present[static_cast<size_t>(Kind::Buffers)] = !buffers.empty();
|
||||
if (shared.gds) {
|
||||
Expect(Kind::Gds);
|
||||
}
|
||||
if (shared.lds && lds_storage) {
|
||||
Expect(Kind::SharedMemory);
|
||||
}
|
||||
if (program.info.uses_dma) {
|
||||
Expect(Kind::BdaPagetable);
|
||||
Expect(Kind::FaultBuffer);
|
||||
}
|
||||
if (IR::UsesFlattenedSrt(program)) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData()) {
|
||||
Expect(Kind::ShaderData);
|
||||
}
|
||||
|
||||
std::array<bool, KindCount> seen {};
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
const auto kind = static_cast<size_t>(binding.kind);
|
||||
if (kind >= KindCount || seen[kind] || !present[kind] ||
|
||||
binding.resources != expected[kind]) {
|
||||
Fail(program, "native descriptor groups do not match shader topology");
|
||||
}
|
||||
seen[kind] = true;
|
||||
}
|
||||
for (size_t i = 0; i < KindCount; i++) {
|
||||
if (present[i] != seen[i]) {
|
||||
Fail(program, "native shader plan is missing a required descriptor group");
|
||||
}
|
||||
}
|
||||
const auto has_shader_data_storage = present[static_cast<size_t>(Kind::ShaderData)];
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
const auto user_data_dwords = program.bindings.user_data_registers.size();
|
||||
const bool has_dispatch_threads = program.bindings.dispatch_thread_dword != IR::PushData::NoStart;
|
||||
if ((program.bindings.UsesPushData() &&
|
||||
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
|
||||
(has_dispatch_threads && (program.stage != ShaderType::Compute ||
|
||||
program.bindings.dispatch_thread_dword != user_data_dwords)) ||
|
||||
program.bindings.memory_offset_dword != user_data_dwords + (has_dispatch_threads ? 3u : 0u) ||
|
||||
program.bindings.memory_offset_count != buffers.size() ||
|
||||
has_shader_data_storage != (shader_data_dwords != 0 && !program.bindings.UsesPushData()) ||
|
||||
!std::is_sorted(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) ||
|
||||
std::adjacent_find(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) !=
|
||||
program.bindings.user_data_registers.end()) {
|
||||
Fail(program, "native shader-data layout is inconsistent");
|
||||
}
|
||||
|
||||
bool uses_flattened_srt = std::ranges::any_of(program.info.buffers, uses_mapping) ||
|
||||
std::ranges::any_of(program.info.images, uses_mapping);
|
||||
const auto planning_only_handle = [&](const IR::Inst& handle) {
|
||||
return !handle.Uses().empty() &&
|
||||
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
|
||||
@@ -141,6 +62,39 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto op = inst.GetOpcode();
|
||||
uses_flattened_srt |= op == IR::ValueOpcode::ReadConst;
|
||||
if (IR::BufferAccessOf(op) != IR::BufferAccess::None ||
|
||||
IR::SharedAccessOf(op) != IR::SharedAccess::None ||
|
||||
IR::AddressOpcodeInfoOf(op).access != IR::AddressAccess::None) {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
Fail(program, "typed shader contains invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[index];
|
||||
if (!memory.planning_only) {
|
||||
uses_lds |= memory.kind == IR::ResourceKind::FlatLocal;
|
||||
if (IR::SharedAccessOf(op) != IR::SharedAccess::None) {
|
||||
if (memory.kind != IR::ResourceKind::Lds && memory.kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "typed shader contains invalid shared-memory metadata");
|
||||
}
|
||||
uses_gds |= memory.kind == IR::ResourceKind::Gds;
|
||||
uses_lds |= memory.kind == IR::ResourceKind::Lds;
|
||||
} else if (memory.kind == IR::ResourceKind::Buffer ||
|
||||
memory.kind == IR::ResourceKind::ScalarBuffer) {
|
||||
if (memory.resource >= program.info.buffers.size()) {
|
||||
Fail(program, "typed shader contains an invalid buffer resource");
|
||||
}
|
||||
live_buffers |= uint64_t{1} << memory.resource;
|
||||
for (const auto child: program.info.buffers[memory.resource].indirect_resources) {
|
||||
if (child >= program.info.buffers.size()) {
|
||||
Fail(program, "typed shader contains an invalid indirect buffer resource");
|
||||
}
|
||||
live_buffers |= uint64_t{1} << child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto dense = inst.Flags<uint32_t>();
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::GetBufferResource:
|
||||
@@ -186,159 +140,64 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
}
|
||||
}
|
||||
}
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
std::array<uint32_t, static_cast<size_t>(Kind::Count)> expected {};
|
||||
auto& buffer_count = expected[static_cast<size_t>(Kind::Buffers)];
|
||||
for (uint32_t index = 0; index < program.info.buffers.size(); ++index) {
|
||||
const auto slot = (live_buffers & (uint64_t{1} << index)) != 0 ? buffer_count++ : UINT32_MAX;
|
||||
if (program.info.buffers[index].descriptor_index != slot) {
|
||||
Fail(program, "native buffer descriptor index does not match shader topology");
|
||||
}
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
if (!kind.has_value()) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
if (image.mip_count == 0u ||
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
}
|
||||
auto& count = expected[static_cast<size_t>(*kind)];
|
||||
if (image.descriptor_index != count) {
|
||||
Fail(program, "native image descriptor index does not match shader topology");
|
||||
}
|
||||
count += image.mip_count;
|
||||
}
|
||||
expected[static_cast<size_t>(Kind::Samplers)] = static_cast<uint32_t>(program.info.samplers.size());
|
||||
expected[static_cast<size_t>(Kind::Gds)] = uses_gds;
|
||||
expected[static_cast<size_t>(Kind::SharedMemory)] = uses_lds && lds_storage;
|
||||
expected[static_cast<size_t>(Kind::BdaPagetable)] = program.info.uses_dma;
|
||||
expected[static_cast<size_t>(Kind::FaultBuffer)] = program.info.uses_dma;
|
||||
expected[static_cast<size_t>(Kind::FlattenedSrt)] = uses_flattened_srt;
|
||||
expected[static_cast<size_t>(Kind::ShaderData)] =
|
||||
program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData();
|
||||
uint64_t descriptor_mask = 0;
|
||||
for (uint32_t index = 0; index < expected.size(); ++index) {
|
||||
if (expected[index] != 0) descriptor_mask |= uint64_t{1} << index;
|
||||
}
|
||||
if (program.bindings.descriptor_counts != expected ||
|
||||
program.bindings.descriptor_mask != descriptor_mask) {
|
||||
Fail(program, "native descriptor counts do not match shader topology");
|
||||
}
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
const auto& registers = program.info.user_data_registers;
|
||||
const bool has_dispatch_threads = program.bindings.dispatch_thread_dword != IR::PushData::NoStart;
|
||||
if ((program.bindings.UsesPushData() &&
|
||||
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
|
||||
(has_dispatch_threads && (program.stage != ShaderType::Compute ||
|
||||
program.bindings.dispatch_thread_dword != registers.size())) ||
|
||||
program.bindings.memory_offset_dword != registers.size() + (has_dispatch_threads ? 3u : 0u) ||
|
||||
!std::is_sorted(registers.begin(), registers.end()) ||
|
||||
std::adjacent_find(registers.begin(), registers.end()) != registers.end()) {
|
||||
Fail(program, "native shader-data layout is inconsistent");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program& program) {
|
||||
SpirvRequirements requirements {};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
requirements.float64 |= inst.GetType() == IR::Type::F64;
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::Atomic &&
|
||||
inst.GetType() == IR::Type::U64) {
|
||||
requirements.buffer_int64_atomics = true;
|
||||
}
|
||||
const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access;
|
||||
if (address_access != IR::AddressAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
Fail(program, "address operation has invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
const auto kind = memory.kind;
|
||||
if (kind == IR::ResourceKind::Scratch || kind == IR::ResourceKind::FlatLocal) {
|
||||
if (program.scratch_dwords == 0) {
|
||||
Fail(program, "scratch operation has no per-thread storage");
|
||||
}
|
||||
requirements.function_scratch = true;
|
||||
if (kind == IR::ResourceKind::FlatLocal && program.stage != ShaderType::Compute &&
|
||||
program.stage != ShaderType::Mesh) {
|
||||
requirements.function_lds = true;
|
||||
}
|
||||
} else if (address_access == IR::AddressAccess::Write) {
|
||||
Fail(program, "writable FLAT/GLOBAL addresses require GPU ownership tracking");
|
||||
} else {
|
||||
// Descriptor stores can alias coherent physical-address loads.
|
||||
requirements.coherent_buffers |= memory.coherent;
|
||||
}
|
||||
}
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) != IR::BufferAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
Fail(program, "buffer operation has invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind == IR::ResourceKind::IndirectBuffer &&
|
||||
inst.GetOpcode() != IR::ValueOpcode::ReadConstBuffer) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
if (memory.kind == IR::ResourceKind::Buffer) {
|
||||
requirements.coherent_buffers |= memory.coherent;
|
||||
if (memory.resource >= program.info.buffers.size()) {
|
||||
Fail(program, "buffer operation has invalid resource metadata");
|
||||
}
|
||||
const auto bits = StorageBufferElementBits(program, memory);
|
||||
requirements.buffer_u8 |= bits == 8u;
|
||||
requirements.buffer_u16 |= bits == 16u;
|
||||
if ((program.info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) {
|
||||
if (program.stage != ShaderType::Compute) {
|
||||
Fail(program, "buffer ADD_TID is only valid for compute shaders");
|
||||
}
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto shared_access = IR::SharedAccessOf(inst.GetOpcode());
|
||||
if (shared_access != IR::SharedAccess::None) {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
Fail(program, "shared operation has invalid memory metadata");
|
||||
}
|
||||
const auto kind = program.memory_info[index].kind;
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
if (shared_access == IR::SharedAccess::Atomic &&
|
||||
IR::SharedComponentCount(inst.GetOpcode()) == 2u) {
|
||||
if (kind != IR::ResourceKind::Lds || program.stage != ShaderType::Compute) {
|
||||
Fail(program, "64-bit shared atomics require compute LDS");
|
||||
}
|
||||
requirements.shared_int64_atomics = true;
|
||||
}
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh &&
|
||||
kind == IR::ResourceKind::Lds) {
|
||||
requirements.function_lds = true;
|
||||
}
|
||||
if (shared_access == IR::SharedAccess::Append ||
|
||||
shared_access == IR::SharedAccess::Consume) {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::StoreCompletion: requirements.subgroup_barrier = true; break;
|
||||
case IR::ValueOpcode::BvhIntersect: requirements.bvh = true; break;
|
||||
case IR::ValueOpcode::Ballot: requirements.subgroup_ballot = true; break;
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
case IR::ValueOpcode::ReadLane: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::DppMoveU32) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::DppUpdateU32:
|
||||
case IR::ValueOpcode::WriteLane: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::Permlane16U32: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::SwizzleU32:
|
||||
case IR::ValueOpcode::PermuteU32:
|
||||
case IR::ValueOpcode::BpermuteU32: {
|
||||
requirements.subgroup_ballot = true;
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::LaneId:
|
||||
requirements.subgroup_local_invocation_id |=
|
||||
program.stage != ShaderType::TessellationControl;
|
||||
break;
|
||||
case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break;
|
||||
case IR::ValueOpcode::ImageGatherRaw:
|
||||
requirements.image_gather_extended = true;
|
||||
break;
|
||||
case IR::ValueOpcode::SetAttribute: {
|
||||
const auto index = inst.Flags<IR::ExportFlags>().index;
|
||||
if (index >= program.export_info.size()) {
|
||||
Fail(program, "attribute export has invalid metadata");
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel &&
|
||||
program.export_info[index].vm) {
|
||||
requirements.pixel_valid_mask = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return requirements;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
ShaderStageInputInfo input_info) {
|
||||
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
|
||||
uint32_t push_data_start_dword) {
|
||||
using namespace Emitter;
|
||||
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
|
||||
@@ -348,12 +207,14 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
Fail(program, "binary SPIR-V emitter received an unsupported shader stage");
|
||||
}
|
||||
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
|
||||
!program.shader_info_complete || !program.binding_layout_complete) {
|
||||
!program.shader_info_complete) {
|
||||
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
|
||||
}
|
||||
ValidateNativeProgram(program, program.stage == ShaderType::Compute &&
|
||||
input_info.compute != nullptr && input_info.compute->lds_storage);
|
||||
if (program.info.buffers.size() > IR::ShaderInfo::MaxBuffers) {
|
||||
Fail(program, "native shader exceeds the buffer resource limit");
|
||||
}
|
||||
IR::ValidateProgram(program, true);
|
||||
program.bindings = {.push_data_start_dword = push_data_start_dword};
|
||||
EmitterState state(program, input_info);
|
||||
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
|
||||
state.lane_count =
|
||||
@@ -361,6 +222,8 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
? 2u
|
||||
: 1u;
|
||||
DefineModule(state);
|
||||
ValidateNativeProgram(program, program.stage == ShaderType::Compute &&
|
||||
input_info.compute != nullptr && input_info.compute->lds_storage);
|
||||
EmitProgram(state);
|
||||
state.builder.AddEntryPoint(ExecutionModelForStage(state.program.stage), state.main_func,
|
||||
"main", state.interface_variables);
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
ShaderStageInputInfo input_info);
|
||||
// Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently.
|
||||
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
|
||||
uint32_t push_data_start_dword = 0);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
|
||||
@@ -56,33 +56,14 @@ uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signe
|
||||
return signed_value ? Unary(state, spv::OpBitcast, TypeU32(state), high) : high;
|
||||
}
|
||||
|
||||
uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value,
|
||||
bool max_value) {
|
||||
const auto ab = signed_value ? EmitMinMaxI32Value(state, a, b, max_value)
|
||||
: EmitMinMaxU32Value(state, a, b, max_value);
|
||||
return signed_value ? EmitMinMaxI32Value(state, ab, c, max_value)
|
||||
: EmitMinMaxU32Value(state, ab, c, max_value);
|
||||
}
|
||||
|
||||
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 = Binary(state, spv::OpIAdd, TypeU32(state), a, b);
|
||||
const auto abc = Binary(state, spv::OpIAdd, TypeU32(state), ab, c);
|
||||
return Binary(state, spv::OpISub, TypeU32(state),
|
||||
Binary(state, spv::OpISub, TypeU32(state), abc, minimum), maximum);
|
||||
}
|
||||
|
||||
uint32_t EmitFMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool max_value) {
|
||||
return EmitMinMaxF32Value(state, EmitMinMaxF32Value(state, a, b, max_value), c, max_value);
|
||||
}
|
||||
|
||||
uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
|
||||
std::initializer_list<uint32_t> args) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {spv::OpExtInst, type, result, GlslStd450(state), opcode};
|
||||
words.insert(words.end(), args.begin(), args.end());
|
||||
state.builder.AddFunction(words);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpExtInst, type, result, GlslStd450(state), opcode, args);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -248,14 +229,6 @@ uint32_t EmitUMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMulHigh(state, arg0, arg1, false);
|
||||
}
|
||||
|
||||
uint32_t EmitIAbs32(EmitterState& state, uint32_t arg0) {
|
||||
const auto value = arg0;
|
||||
const auto neg = Unary(state, spv::OpSNegate, TypeU32(state), value);
|
||||
const auto negative =
|
||||
Binary(state, spv::OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), negative, neg, value);
|
||||
}
|
||||
|
||||
uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0) {
|
||||
// Vulkan bit-count operands remain 32-bit, including when counting a U64.
|
||||
const auto unpacked = Unary(state, spv::OpBitcast, TypeU32Vector(state, 2), arg0);
|
||||
@@ -264,58 +237,30 @@ uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0) {
|
||||
return Binary(state, spv::OpIAdd, TypeU32(state), pair.low, pair.high);
|
||||
}
|
||||
|
||||
uint32_t EmitFindILsb32(EmitterState& state, uint32_t arg0) {
|
||||
const auto value = EmitExt(state, TypeI32(state), GLSLstd450FindILsb, {arg0});
|
||||
return Unary(state, spv::OpBitcast, TypeU32(state), value);
|
||||
}
|
||||
|
||||
uint32_t EmitFindUMsb32(EmitterState& state, uint32_t arg0) {
|
||||
const auto value = EmitExt(state, TypeI32(state), GLSLstd450FindUMsb, {arg0});
|
||||
return Unary(state, spv::OpBitcast, TypeU32(state), value);
|
||||
}
|
||||
|
||||
uint32_t EmitSMin32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxI32Value(state, arg0, arg1, false);
|
||||
}
|
||||
|
||||
uint32_t EmitSMax32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxI32Value(state, arg0, arg1, true);
|
||||
}
|
||||
|
||||
uint32_t EmitUMin32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxU32Value(state, arg0, arg1, false);
|
||||
}
|
||||
|
||||
uint32_t EmitUMax32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitMinMaxU32Value(state, arg0, arg1, true);
|
||||
}
|
||||
|
||||
uint32_t EmitSMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, true, false);
|
||||
return EmitSMin32(state, arg0, EmitSMin32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitSMaxTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, true, true);
|
||||
return EmitSMax32(state, arg0, EmitSMax32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitUMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, false, false);
|
||||
return EmitUMin32(state, arg0, EmitUMin32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitUMaxTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMinMax3(state, arg0, arg1, arg2, false, true);
|
||||
return EmitUMax32(state, arg0, EmitUMax32(state, arg1, arg2));
|
||||
}
|
||||
|
||||
uint32_t EmitSMedTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMed3(state, arg0, arg1, arg2, true);
|
||||
const auto high_min = EmitSMin32(state, EmitSMax32(state, arg0, arg1), arg2);
|
||||
return EmitSMax32(state, EmitSMin32(state, arg0, arg1), high_min);
|
||||
}
|
||||
|
||||
uint32_t EmitUMedTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
|
||||
return EmitMed3(state, arg0, arg1, arg2, false);
|
||||
}
|
||||
|
||||
uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0) {
|
||||
return EmitNative<spv::OpFUnordNotEqual, IR::Type::U1>(state, arg0, arg0);
|
||||
const auto high_min = EmitUMin32(state, EmitUMax32(state, arg0, arg1), arg2);
|
||||
return EmitUMax32(state, EmitUMin32(state, arg0, arg1), high_min);
|
||||
}
|
||||
|
||||
uint32_t EmitFPMin32(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
|
||||
@@ -82,35 +82,12 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
|
||||
return EmitAndConstant(state, EmitSelectValueU32(state, exp_eq_255, special, finite2), 0xffffu);
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto cond = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
|
||||
cond, lhs, rhs);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto cond = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(max_value ? spv::OpSGreaterThan : spv::OpSLessThan, TypeBool(state),
|
||||
cond, lhs, rhs);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits) {
|
||||
F32Class cls;
|
||||
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);
|
||||
const auto exponent_max =
|
||||
EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, 0x7f800000u);
|
||||
const auto mantissa_nonzero = EmitCompareU32Constant(state, spv::OpINotEqual, mantissa_bits, 0);
|
||||
cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero);
|
||||
cls.zero = EmitCompareU32Constant(state, spv::OpIEqual, abs_bits, 0);
|
||||
cls.bits = bits;
|
||||
const auto abs_bits = EmitAndConstant(state, cls.bits, 0x7fffffffu);
|
||||
cls.nan = EmitCompareU32Constant(state, spv::OpUGreaterThan, abs_bits, 0x7f800000u);
|
||||
cls.zero = EmitCompareU32Constant(state, spv::OpIEqual, abs_bits, 0);
|
||||
return cls;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,21 +59,6 @@ uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
uint32_t ResourceForDescriptor(const EmitterState& state, IR::DescriptorBindingKind kind,
|
||||
uint32_t resource) {
|
||||
const auto* descriptor = IR::FindBinding(state.program.bindings, kind);
|
||||
if (descriptor == nullptr) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource, "descriptor group was not allocated");
|
||||
}
|
||||
const auto found =
|
||||
std::find(descriptor->resources.begin(), descriptor->resources.end(), resource);
|
||||
if (found == descriptor->resources.end()) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource,
|
||||
"resource is absent from descriptor group");
|
||||
}
|
||||
return static_cast<uint32_t>(found - descriptor->resources.begin());
|
||||
}
|
||||
|
||||
uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
uint32_t variable_id, uint32_t array_index,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
@@ -156,22 +141,22 @@ uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mi
|
||||
uint32_t array_index) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip, array_index);
|
||||
const auto image = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, ImageType(state, state.program.info.images.at(resource)),
|
||||
const auto kind = *IR::DescriptorBindingForImage(state.program.info.images[resource]);
|
||||
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
|
||||
image, pointer);
|
||||
return image;
|
||||
}
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
const auto sampler_type = state.builder.Type(spv::OpTypeSampler);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, sampler_type);
|
||||
if (sampler >= state.program.info.samplers.size()) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Samplers, sampler,
|
||||
"sampler resource index is out of range");
|
||||
}
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, pointer_type, state.sampler_variable, ConstantU32(state, array_index),
|
||||
state, state.sampler_pointer_type, state.sampler_variable, ConstantU32(state, sampler),
|
||||
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
|
||||
const auto sampler_id = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, sampler_type, sampler_id, pointer);
|
||||
state.builder.AddFunction(spv::OpLoad, state.sampler_type, sampler_id, pointer);
|
||||
return sampler_id;
|
||||
}
|
||||
|
||||
@@ -181,8 +166,12 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto sampled_type =
|
||||
state.builder.Type(spv::OpTypeSampledImage, ImageType(state, image_resource));
|
||||
const auto kind = *IR::DescriptorBindingForImage(image_resource);
|
||||
auto& sampled_type = state.images[IR::ImageBindingIndex(kind)].sampled_type;
|
||||
if (sampled_type == 0) {
|
||||
sampled_type = state.builder.Type(
|
||||
spv::OpTypeSampledImage, state.images[IR::ImageBindingIndex(kind)].type);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
|
||||
if (array_index != 0u) {
|
||||
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
|
||||
@@ -200,12 +189,11 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
if (array_index == 0u) {
|
||||
array_index = ConstantU32(state, ResourceForDescriptor(state, *kind, resource) + mip);
|
||||
array_index = ConstantU32(state, image.descriptor_index + mip);
|
||||
}
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image));
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
const auto& definition = state.images[IR::ImageBindingIndex(*kind)];
|
||||
return DescriptorElementPointer(state, definition.pointer_type, definition.variable,
|
||||
array_index, *kind, resource,
|
||||
"image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ uint32_t Boxes(EmitterState& s, uint32_t first, uint32_t second, uint32_t half,
|
||||
} // namespace
|
||||
|
||||
void DefineBvhIntersect(EmitterState& s) {
|
||||
if (!s.requirements.bvh) return;
|
||||
if (!s.program.info.bvh) return;
|
||||
const auto u = TypeU32(s), f = TypeF32(s), b = TypeBool(s), wide = TypeU64(s);
|
||||
const auto vec4 = TypeU32Vector(s, 4), vec3 = TypeF32Vector(s, 3);
|
||||
const auto signature = s.builder.Type(spv::OpTypeFunction, vec4, vec4, wide, f, vec3, vec3, vec3);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace {
|
||||
|
||||
bool UserDataDwordIndex(const EmitterState& state, IR::ScalarReg reg, uint32_t& dword_index) {
|
||||
const auto register_index = IR::RegIndex(reg);
|
||||
const auto& registers = state.program.bindings.user_data_registers;
|
||||
const auto& registers = state.program.info.user_data_registers;
|
||||
const auto found = std::lower_bound(registers.begin(), registers.end(), register_index);
|
||||
if (found == registers.end() || *found != register_index) {
|
||||
return false;
|
||||
@@ -42,8 +42,9 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
EmitBinaryU32(state, spv::OpIMul, group, ConstantU32(state, size)));
|
||||
}
|
||||
const bool centroid = kind == IR::StageInputKind::BaryCoordSmoothCentroid;
|
||||
const bool sample = kind == IR::StageInputKind::BaryCoordSmoothSample;
|
||||
const auto variable = InputVariableForKind(
|
||||
state, centroid ? IR::StageInputKind::BaryCoordSmooth : kind);
|
||||
state, centroid || sample ? IR::StageInputKind::BaryCoordSmooth : kind);
|
||||
if (variable == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
@@ -76,15 +77,24 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
if (centroid || kind == IR::StageInputKind::BaryCoordSmooth ||
|
||||
if (centroid || sample || kind == IR::StageInputKind::BaryCoordSmooth ||
|
||||
kind == IR::StageInputKind::BaryCoordNoPerspective) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
if (centroid) {
|
||||
if (centroid || sample) {
|
||||
const auto coordinates = state.builder.AllocateId();
|
||||
state.builder.RequireCapability(spv::CapabilityInterpolationFunction);
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
|
||||
GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable);
|
||||
if (sample) {
|
||||
const auto sample_id = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), sample_id,
|
||||
InputVariableForKind(state, IR::StageInputKind::SampleId));
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
|
||||
GlslStd450(state), GLSLstd450InterpolateAtSample,
|
||||
variable, sample_id);
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
|
||||
GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), value,
|
||||
coordinates, component + 1u);
|
||||
} else {
|
||||
@@ -167,6 +177,13 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
|
||||
return value;
|
||||
};
|
||||
if (input->per_vertex) {
|
||||
if (PixelParameterIsFlat(state, attr)) {
|
||||
const auto value = load_per_vertex(
|
||||
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u);
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
|
||||
? IR::StageInputKind::BaryCoordNoPerspective
|
||||
: IR::StageInputKind::BaryCoordSmooth;
|
||||
@@ -221,8 +238,12 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
|
||||
return value;
|
||||
};
|
||||
|
||||
const auto selected_vertex = (mode + 1u) % 3u;
|
||||
uint32_t value = load_vertex(selected_vertex);
|
||||
uint32_t selected_vertex = (mode + 1u) % 3u;
|
||||
if (mode == 2u && PixelParameterIsFlat(state, attr)) {
|
||||
selected_vertex =
|
||||
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
|
||||
}
|
||||
uint32_t value = load_vertex(selected_vertex);
|
||||
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
|
||||
const auto delta = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
|
||||
@@ -252,8 +273,12 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
bool uint_output) {
|
||||
auto& state = ctx.state;
|
||||
if (exp.compr && !uint_output) {
|
||||
const auto unpack =
|
||||
MrtOutputMode(state, exp) == 5u ? GLSLstd450UnpackUnorm2x16 : GLSLstd450UnpackHalf2x16;
|
||||
auto unpack = GLSLstd450UnpackHalf2x16;
|
||||
switch (MrtOutputMode(state, exp)) {
|
||||
case 5: unpack = GLSLstd450UnpackUnorm2x16; break;
|
||||
case 6: unpack = GLSLstd450UnpackSnorm2x16; break;
|
||||
default: break;
|
||||
}
|
||||
uint32_t f32[4] = {ConstantF32(state, 0), ConstantF32(state, 0), ConstantF32(state, 0),
|
||||
ConstantF32(state, 0x3f800000u)};
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
@@ -279,50 +304,46 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
f32[0], f32[1], f32[2], f32[3]);
|
||||
return vector;
|
||||
}
|
||||
uint32_t raw[4] = {
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
|
||||
};
|
||||
if (exp.compr) {
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||
continue;
|
||||
}
|
||||
const auto packed = ExportRawComponent(ctx, data, pair);
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair * 2u + lane;
|
||||
if (((exp.en >> component) & 1u) == 0u) {
|
||||
if (exp.compr || exp.en != 0xfu) {
|
||||
uint32_t raw[4] = {
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
|
||||
};
|
||||
if (exp.compr) {
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||
continue;
|
||||
}
|
||||
raw[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16));
|
||||
const auto packed = ExportRawComponent(ctx, data, pair);
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair * 2u + lane;
|
||||
if (((exp.en >> component) & 1u) == 0u) {
|
||||
continue;
|
||||
}
|
||||
raw[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
if (((exp.en >> component) & 1u) != 0u) {
|
||||
raw[component] = ExportRawComponent(ctx, data, component);
|
||||
} else {
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
if (((exp.en >> component) & 1u) != 0u) {
|
||||
raw[component] = ExportRawComponent(ctx, data, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), data,
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
}
|
||||
if (uint_output) {
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector,
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
return vector;
|
||||
}
|
||||
uint32_t f32[4] {};
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
f32[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32[component], raw[component]);
|
||||
return data;
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector, f32[0],
|
||||
f32[1], f32[2], f32[3]);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32Vector(state, 4), vector, data);
|
||||
return vector;
|
||||
}
|
||||
|
||||
@@ -447,7 +468,7 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& exp = ctx.Export(inst);
|
||||
const auto exec = ctx.Arg(inst, 1);
|
||||
if (state.program.stage == ShaderType::Pixel && exp.vm && state.requirements.pixel_valid_mask &&
|
||||
if (state.program.stage == ShaderType::Pixel && exp.vm && state.program.info.pixel_valid_mask &&
|
||||
state.pixel_valid_mask_variable != 0) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), value, exec, ConstantU32(state, 1),
|
||||
@@ -699,26 +720,18 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (ctx.other_half == nullptr) return ctx.Arg(inst, 0);
|
||||
// A native scalar branch makes one decision for both emulated wave halves.
|
||||
if (ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
|
||||
// Scalar branches include lanes disabled by EXEC, which the branch body may restore.
|
||||
if (ctx.other_half != nullptr && ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
|
||||
const auto kind = inst.Flags<CFG::BranchCondition>();
|
||||
if (kind == CFG::BranchCondition::ScalarInstruction) return ctx.Arg(inst, 0);
|
||||
const auto ballot = ctx.Ballot(inst.Arg(0));
|
||||
const auto low = ctx.state.builder.AllocateId();
|
||||
const auto high = ctx.state.builder.AllocateId();
|
||||
const auto combined = ctx.state.builder.AllocateId();
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1);
|
||||
const bool zero = kind == CFG::BranchCondition::ExecZero ||
|
||||
kind == CFG::BranchCondition::VccZero || kind == CFG::BranchCondition::SccZero;
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpBitwiseAnd : spv::OpBitwiseOr,
|
||||
TypeU32(ctx.state), combined, low, high);
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpIEqual : spv::OpINotEqual,
|
||||
TypeBool(ctx.state), result, combined,
|
||||
ConstantU32(ctx.state, zero ? ~0u : 0u));
|
||||
return result;
|
||||
const auto ballot = ctx.Ballot(inst.Arg(0));
|
||||
const auto reference = zero ? ctx.Ballot(IR::Value(true))
|
||||
: ConstantU32CompositeZero(ctx.state, 4);
|
||||
const auto compare = Binary(ctx.state, zero ? spv::OpIEqual : spv::OpINotEqual,
|
||||
TypeBoolVector(ctx.state, 4), ballot, reference);
|
||||
return Unary(ctx.state, zero ? spv::OpAll : spv::OpAny, TypeBool(ctx.state), compare);
|
||||
}
|
||||
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
|
||||
|
||||
@@ -752,14 +752,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto EmitGather = [&](uint32_t mip) {
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, sampler_id, mip);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {
|
||||
static_cast<uint32_t>(dref ? spv::OpImageDrefGather : spv::OpImageGather), result_type,
|
||||
sample, sampled, coord, component_or_dref};
|
||||
if (operand_mask != 0u) {
|
||||
words.push_back(operand_mask);
|
||||
words.push_back(offset);
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
const std::array operands {operand_mask, offset};
|
||||
state.builder.AddFunction(
|
||||
dref ? spv::OpImageDrefGather : spv::OpImageGather, result_type,
|
||||
sample, sampled, coord, component_or_dref,
|
||||
std::span(operands).first(operand_mask != 0u ? 2u : 0u));
|
||||
return sample;
|
||||
};
|
||||
const auto sample = image.mip_mode == IR::ImageMipMode::Dynamic && image.mip_count > 1u
|
||||
@@ -791,24 +788,25 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
}
|
||||
uint32_t operand_mask = 0;
|
||||
std::vector<uint32_t> operands;
|
||||
std::array<uint32_t, 3> operands {};
|
||||
size_t operand_count = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
operand_mask |= spv::ImageOperandsGradMask;
|
||||
operands.push_back(
|
||||
CoordF32(ctx, mem, *address, layout.grad_x, dimension_info.spatial_components));
|
||||
operands.push_back(
|
||||
CoordF32(ctx, mem, *address, layout.grad_y, dimension_info.spatial_components));
|
||||
operands[0] = spv::ImageOperandsGradMask;
|
||||
operands[1] = CoordF32(ctx, mem, *address, layout.grad_x, dimension_info.spatial_components);
|
||||
operands[2] = CoordF32(ctx, mem, *address, layout.grad_y, dimension_info.spatial_components);
|
||||
operand_count = 3;
|
||||
} else if (explicit_lod) {
|
||||
operand_mask |= spv::ImageOperandsLodMask;
|
||||
auto lod = ZeroF32(state);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||
lod = AddressF32(ctx, mem, *address, layout.lod);
|
||||
}
|
||||
operands.push_back(lod);
|
||||
operands[0] = spv::ImageOperandsLodMask;
|
||||
operands[1] = lod;
|
||||
operand_count = 2;
|
||||
} else if (layout.bias != NoImageComponent) {
|
||||
operand_mask |= spv::ImageOperandsBiasMask;
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
operands[0] = spv::ImageOperandsBiasMask;
|
||||
operands[1] = AddressF32(ctx, mem, *address, layout.bias);
|
||||
operand_count = 2;
|
||||
}
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto EmitSample = [&](uint32_t resource, uint32_t array_index = 0u) {
|
||||
@@ -818,16 +816,10 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
candidate.cube);
|
||||
const auto sampled = MakeSampledImage(state, resource, sampler_id, 0u, array_index);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
sample_operands.push_back(dref_value);
|
||||
}
|
||||
if (operand_mask != 0u) {
|
||||
sample_operands.push_back(operand_mask);
|
||||
sample_operands.insert(sample_operands.end(), operands.begin(), operands.end());
|
||||
}
|
||||
state.builder.AddFunction(opcode, sample_operands);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
state.builder.AddFunction(opcode, result_type, sample, sampled, coord,
|
||||
std::span(&dref_value, dref ? 1u : 0u),
|
||||
std::span<const uint32_t>(operands).first(operand_count));
|
||||
return sample;
|
||||
};
|
||||
if (image.indirect_root != mem.resource) {
|
||||
@@ -867,7 +859,6 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto resource = image.indirect_resources[ordinal];
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto kind = *IR::DescriptorBindingForImage(candidate);
|
||||
const auto& binding = *IR::FindBinding(state.program.bindings, kind);
|
||||
if (!runs.empty()) {
|
||||
auto& run = runs.back();
|
||||
const auto& first = state.program.info.images[run.resource];
|
||||
@@ -875,17 +866,16 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (candidate.dimension == first.dimension && candidate.cube == first.cube &&
|
||||
IR::DescriptorBindingForImage(first) == kind &&
|
||||
candidate.mip_count == 1u && first.mip_count == 1u &&
|
||||
(ordinal == 1u || (next_slot < binding.resources.size() &&
|
||||
binding.resources[next_slot] == resource))) {
|
||||
(ordinal == 1u || candidate.descriptor_index == next_slot)) {
|
||||
// Native roots precede appended children; only the root may have a slot gap.
|
||||
if (ordinal == 1u)
|
||||
run.slot_bias = ResourceForDescriptor(state, kind, resource) - ordinal;
|
||||
run.slot_bias = candidate.descriptor_index - ordinal;
|
||||
++run.count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
runs.push_back({ordinal, 1u, resource,
|
||||
ResourceForDescriptor(state, kind, resource) - ordinal});
|
||||
candidate.descriptor_index - ordinal});
|
||||
}
|
||||
const auto EmitRun = [&](uint32_t index) {
|
||||
const auto& run = runs[index];
|
||||
@@ -893,8 +883,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto slot = Binary(state, spv::OpIAdd, TypeU32(state), selected,
|
||||
ConstantU32(state, run.slot_bias));
|
||||
if (run.first == 0u) {
|
||||
const auto kind = *IR::DescriptorBindingForImage(image);
|
||||
const auto root_slot = ResourceForDescriptor(state, kind, mem.resource);
|
||||
const auto root_slot = image.descriptor_index;
|
||||
if (root_slot != run.slot_bias) {
|
||||
const auto is_root = Binary(state, spv::OpIEqual, TypeBool(state), selected,
|
||||
ConstantU32(state, 0u));
|
||||
|
||||
@@ -23,13 +23,8 @@ uint32_t EmitConvertF16F32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitConvertF32F16 = EmitF16BitsToF32;
|
||||
uint32_t EmitConvertS32F32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertU32F32(EmitterState& state, uint32_t arg0);
|
||||
template <IR::Type type>
|
||||
uint32_t EmitConvertSigned32ToFloat(EmitterState& state, uint32_t arg0) {
|
||||
const auto signed_value = Unary(state, spv::OpBitcast, TypeI32(state), arg0);
|
||||
return EmitNative<spv::OpConvertSToF, type>(state, signed_value);
|
||||
}
|
||||
inline constexpr auto EmitConvertF32S32 = EmitConvertSigned32ToFloat<IR::Type::F32>;
|
||||
inline constexpr auto EmitConvertF64S32 = EmitConvertSigned32ToFloat<IR::Type::F64>;
|
||||
EMIT_NATIVE(ConvertF32S32, OpConvertSToF, F32, uint32_t)
|
||||
EMIT_NATIVE(ConvertF64S32, OpConvertSToF, F64, uint32_t)
|
||||
uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertF64F32(EmitterState& state, uint32_t arg0);
|
||||
EMIT_NATIVE(ConvertF32U32, OpConvertUToF, F32, uint32_t)
|
||||
@@ -69,7 +64,7 @@ EMIT_NATIVE(IMul64, OpIMul, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(IAddCarry32, OpIAddCarry, U32x2, uint32_t, uint32_t)
|
||||
uint32_t EmitSMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitIAbs32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitIAbs32 = EmitGlsl<GLSLstd450SAbs, IR::Type::U32, uint32_t>;
|
||||
EMIT_NATIVE(ShiftLeftLogical32, OpShiftLeftLogical, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ShiftRightLogical32, OpShiftRightLogical, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ShiftRightArithmetic32, OpShiftRightArithmetic, U32, uint32_t, uint32_t)
|
||||
@@ -96,13 +91,13 @@ EMIT_NATIVE(BitwiseAnd64, OpBitwiseAnd, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(BitReverse32, OpBitReverse, U32, uint32_t)
|
||||
EMIT_NATIVE(BitCount32, OpBitCount, U32, uint32_t)
|
||||
uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitFindILsb32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitFindUMsb32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitFindILsb32 = EmitGlsl<GLSLstd450FindILsb, IR::Type::U32, uint32_t>;
|
||||
inline constexpr auto EmitFindUMsb32 = EmitGlsl<GLSLstd450FindUMsb, IR::Type::U32, uint32_t>;
|
||||
uint32_t EmitFindUMsb64(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitSMin32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitSMax32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUMin32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUMax32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
inline constexpr auto EmitSMin32 = EmitGlsl<GLSLstd450SMin, IR::Type::U32, uint32_t, uint32_t>;
|
||||
inline constexpr auto EmitSMax32 = EmitGlsl<GLSLstd450SMax, IR::Type::U32, uint32_t, uint32_t>;
|
||||
inline constexpr auto EmitUMin32 = EmitGlsl<GLSLstd450UMin, IR::Type::U32, uint32_t, uint32_t>;
|
||||
inline constexpr auto EmitUMax32 = EmitGlsl<GLSLstd450UMax, IR::Type::U32, uint32_t, uint32_t>;
|
||||
uint32_t EmitSMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
|
||||
uint32_t EmitSMaxTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
|
||||
uint32_t EmitUMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
|
||||
@@ -162,7 +157,7 @@ inline constexpr auto EmitFPOrdLessThanEqual32 = EmitFloatCompare32<spv::OpFOrdL
|
||||
inline constexpr auto EmitFPUnordLessThanEqual32 = EmitFloatCompare32<spv::OpFUnordLessThanEqual>;
|
||||
inline constexpr auto EmitFPOrdGreaterThanEqual32 = EmitFloatCompare32<spv::OpFOrdGreaterThanEqual>;
|
||||
inline constexpr auto EmitFPUnordGreaterThanEqual32 = EmitFloatCompare32<spv::OpFUnordGreaterThanEqual>;
|
||||
uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0);
|
||||
EMIT_NATIVE(FPIsNan32, OpIsNan, U1, uint32_t)
|
||||
inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32;
|
||||
inline constexpr auto EmitFPCmpClass16 = EmitClassMaskF16;
|
||||
EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -58,42 +57,36 @@ constexpr std::array<ImageDimensionInfo, 7> ImageDimensions {{
|
||||
|
||||
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension);
|
||||
|
||||
struct SpirvRequirements {
|
||||
bool bvh = false;
|
||||
bool subgroup_ballot = false;
|
||||
bool subgroup_barrier = false;
|
||||
bool subgroup_shuffle = false;
|
||||
bool subgroup_local_invocation_id = false;
|
||||
bool compute_derivatives = false;
|
||||
bool image_gather_extended = false;
|
||||
bool function_lds = false;
|
||||
bool function_scratch = false;
|
||||
bool pixel_valid_mask = false;
|
||||
bool buffer_int64_atomics = false;
|
||||
bool buffer_u8 = false;
|
||||
bool buffer_u16 = false;
|
||||
bool shared_int64_atomics = false;
|
||||
bool coherent_buffers = false;
|
||||
bool float64 = false;
|
||||
struct BufferDefinition {
|
||||
uint32_t variable = 0;
|
||||
uint32_t type = 0;
|
||||
uint32_t element_type = 0;
|
||||
uint32_t pointer_type = 0;
|
||||
uint32_t element_pointer_type = 0;
|
||||
};
|
||||
|
||||
SpirvRequirements AnalyzeProgramRequirements(const IR::Program& program);
|
||||
struct ImageDefinition {
|
||||
uint32_t variable = 0;
|
||||
uint32_t type = 0;
|
||||
uint32_t pointer_type = 0;
|
||||
uint32_t sampled_type = 0;
|
||||
};
|
||||
|
||||
struct EmitterState {
|
||||
EmitterState(const IR::Program& program_, ShaderStageInputInfo input_info_)
|
||||
EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_)
|
||||
: builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u),
|
||||
program(program_), input_info(input_info_),
|
||||
requirements(AnalyzeProgramRequirements(program_)) {
|
||||
program(program_), input_info(input_info_) {
|
||||
if (ShaderWorkgroupInput(program.stage, input_info) != nullptr) {
|
||||
lds_storage_class = spv::StorageClassWorkgroup;
|
||||
}
|
||||
if (IR::FindBinding(program.bindings, IR::DescriptorBindingKind::SharedMemory) != nullptr) {
|
||||
if (program.info.uses_lds && program.stage == ShaderType::Compute &&
|
||||
input_info.compute != nullptr && input_info.compute->lds_storage) {
|
||||
lds_storage_class = spv::StorageClassStorageBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
IR::Program& program;
|
||||
ShaderStageInputInfo input_info;
|
||||
uint32_t void_type = 0;
|
||||
uint32_t bool_type = 0;
|
||||
@@ -113,13 +106,9 @@ struct EmitterState {
|
||||
uint32_t tess_inner_variable = 0;
|
||||
uint32_t tess_patch_base = 0;
|
||||
|
||||
const SpirvRequirements requirements;
|
||||
uint32_t lane_count = 1;
|
||||
uint32_t lane_half = 0;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
uint32_t storage_buffer_u8_variable = 0;
|
||||
uint32_t storage_buffer_u16_variable = 0;
|
||||
uint32_t storage_buffer_u64_variable = 0;
|
||||
std::array<BufferDefinition, 4> storage_buffers {};
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
@@ -135,8 +124,10 @@ struct EmitterState {
|
||||
uint32_t lds_variable = 0;
|
||||
uint32_t lds_u64_variable = 0;
|
||||
std::array<uint32_t, 2> scratch_variable {};
|
||||
std::array<uint32_t, IR::ImageBindingCount> image_variables {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> images {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t sampler_type = 0;
|
||||
uint32_t sampler_pointer_type = 0;
|
||||
uint32_t main_func = 0;
|
||||
uint32_t mesh_guest_func = 0;
|
||||
uint32_t mesh_allocation = 0;
|
||||
@@ -160,7 +151,6 @@ 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);
|
||||
@@ -180,8 +170,6 @@ uint32_t TypeI32Vector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeF32Vector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypePointer(EmitterState& state, spv::StorageClass storage_class, uint32_t pointee);
|
||||
uint32_t TypeFunction(EmitterState& state);
|
||||
uint32_t TypeStorageBufferElement(EmitterState& state, uint32_t bits);
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state, uint32_t bits = 32);
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state, uint32_t bits = 32);
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state);
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state);
|
||||
@@ -255,12 +243,10 @@ struct ValueEmitContext {
|
||||
const IR::Inst* ImageAddress(IR::Value value);
|
||||
const IR::MemoryInfo& Memory(const IR::Inst& inst) const;
|
||||
const IR::ExportInfo& Export(const IR::Inst& inst) const;
|
||||
uint32_t Label(const IR::Block* block) const;
|
||||
[[noreturn]] void Fail(const char* reason) const;
|
||||
[[noreturn]] void Fail(const IR::Inst& inst, const char* reason) const;
|
||||
|
||||
EmitterState& state;
|
||||
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
||||
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;
|
||||
uint32_t scratch_u32_variable = 0;
|
||||
@@ -316,9 +302,6 @@ uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
const char* reason);
|
||||
|
||||
uint32_t ResourceForDescriptor(const EmitterState& state, IR::DescriptorBindingKind kind,
|
||||
uint32_t resource);
|
||||
|
||||
uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
uint32_t variable_id, uint32_t array_index,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
@@ -413,7 +396,6 @@ uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index);
|
||||
uint32_t StorageBufferPackedStride(const EmitterState& state, const IR::MemoryInfo& mem);
|
||||
|
||||
Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::MemoryInfo& mem);
|
||||
uint32_t StorageBufferElementBits(const IR::Program& program, const IR::MemoryInfo& mem);
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state);
|
||||
|
||||
@@ -435,7 +417,7 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable, uint32_t pointer_type);
|
||||
BufferDefinition& buffer);
|
||||
|
||||
uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t index);
|
||||
@@ -489,10 +471,6 @@ uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_
|
||||
|
||||
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32);
|
||||
|
||||
uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value);
|
||||
|
||||
uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value);
|
||||
|
||||
inline constexpr auto EmitBitcastF32ToU32 = EmitNative<spv::OpBitcast, IR::Type::U32, uint32_t>;
|
||||
|
||||
inline constexpr auto EmitBitcastU32ToF32 = EmitNative<spv::OpBitcast, IR::Type::F32, uint32_t>;
|
||||
|
||||
@@ -251,8 +251,7 @@ uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint3
|
||||
|
||||
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
if (mem.kind == IR::ResourceKind::Buffer) {
|
||||
const auto resource = ResourceForDescriptor(ctx.state, IR::DescriptorBindingKind::Buffers,
|
||||
mem.resource);
|
||||
const auto resource = ctx.state.program.info.buffers.at(mem.resource).descriptor_index;
|
||||
const auto prefix = ctx.state.memory_byte_offsets[resource];
|
||||
const auto address = Binary(ctx.state, spv::OpIAdd, TypeU32(ctx.state),
|
||||
BufferByteAddress(ctx, inst, mem), prefix);
|
||||
@@ -333,7 +332,8 @@ uint32_t LoadSubwordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess&
|
||||
uint32_t value;
|
||||
if (resource.element_bits < 32u) {
|
||||
const auto loaded = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpLoad, TypeStorageBufferElement(ctx.state, bits), loaded,
|
||||
ctx.state.builder.AddFunction(spv::OpLoad,
|
||||
ctx.state.storage_buffers[bits == 8u ? 0 : 1].element_type, loaded,
|
||||
EmitMemoryElementPointer(ctx.state, resource, index),
|
||||
resource.memory_access);
|
||||
value = Unary(ctx.state, spv::OpUConvert, TypeU32(ctx.state), loaded);
|
||||
@@ -438,7 +438,8 @@ void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
if (resource.element_bits < 32u) {
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpStore, pointer,
|
||||
Unary(ctx.state, spv::OpUConvert, TypeStorageBufferElement(ctx.state, bits), data),
|
||||
Unary(ctx.state, spv::OpUConvert,
|
||||
ctx.state.storage_buffers[bits == 8u ? 0 : 1].element_type, data),
|
||||
resource.memory_access);
|
||||
return;
|
||||
}
|
||||
@@ -716,18 +717,38 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
|
||||
return LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource);
|
||||
}
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
const auto value = EmitValueOrDefaultIfCondition(
|
||||
ctx.state, plan.in_bounds, TypeU32(ctx.state),
|
||||
FormattedOutOfBoundsValue(ctx, mem, plan, 1u),
|
||||
[&]() { return LoadFormattedInBounds(ctx, mem, plan, 0u); });
|
||||
if (mem.data_bits != 16u) {
|
||||
return value;
|
||||
}
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
return EmitUMin32(ctx.state, value, ConstantU32(ctx.state, 0xffffu));
|
||||
}
|
||||
if (info.type == Format::ComponentType::Sint) {
|
||||
const auto lower = EmitSMax32(ctx.state, value, ConstantU32(ctx.state, 0xffff8000u));
|
||||
return EmitSMin32(ctx.state, lower, ConstantU32(ctx.state, 0x7fffu));
|
||||
}
|
||||
return EmitConvertF16F32(ctx.state, EmitBitCastF32U32(ctx.state, value));
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
|
||||
const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data) {
|
||||
uint32_t component, uint32_t data, uint32_t data_bits) {
|
||||
auto& state = ctx.state;
|
||||
const auto bits = info.component_bits[component];
|
||||
if (data_bits == 16u) {
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
} else if (info.type == Format::ComponentType::Sint) {
|
||||
data = EmitBitFieldSExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
} else {
|
||||
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
|
||||
}
|
||||
}
|
||||
const bool normalized = info.type == Format::ComponentType::Unorm ||
|
||||
info.type == Format::ComponentType::Snorm;
|
||||
const bool scaled = info.type == Format::ComponentType::Uscaled ||
|
||||
@@ -779,7 +800,7 @@ void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
value, data, component);
|
||||
}
|
||||
}
|
||||
value = EncodeFormattedStoreComponent(ctx, info, component, value);
|
||||
value = EncodeFormattedStoreComponent(ctx, info, component, value, mem.data_bits);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.packed_bitfield) {
|
||||
packed = EmitBitFieldInsert(
|
||||
@@ -1180,8 +1201,8 @@ uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU64(state), ConstantU64(state, 0), [&]() {
|
||||
const auto resource = PrepareStorageBufferResourceAccess(
|
||||
state, mem, state.storage_buffer_u64_variable, TypeStorageBufferPointer(state, 64));
|
||||
const auto resource =
|
||||
PrepareStorageBufferResourceAccess(state, mem, state.storage_buffers[3]);
|
||||
const auto byte_address = ByteAddress(ctx, inst, mem);
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), byte_address,
|
||||
ConstantU32(state, 3u));
|
||||
|
||||
@@ -102,23 +102,15 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
|
||||
return state.program.info.buffers[mem.resource].descriptor_format;
|
||||
}
|
||||
|
||||
uint32_t StorageBufferElementBits(const IR::Program& program, const IR::MemoryInfo& mem) {
|
||||
if (!mem.formatted) return mem.data_bits;
|
||||
const auto format = mem.typed ? Format::DecodeTBufferFormat(mem.data_format, mem.number_format)
|
||||
: program.info.buffers[mem.resource].descriptor_format;
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
return info.type == Format::ComponentType::Unknown || info.packed_bitfield
|
||||
? 32u : info.component_bits[0];
|
||||
}
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state) {
|
||||
for (uint32_t i = 0; i < state.program.bindings.memory_offset_count; i++) {
|
||||
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
|
||||
for (uint32_t i = 0; i < count; i += 4u) {
|
||||
const auto word =
|
||||
EmitShaderDataDwordLoad(state, state.program.bindings.memory_offset_dword + i / 4u);
|
||||
const auto shift = ConstantU32(state, (i % 4u) * 8u);
|
||||
state.memory_byte_offsets[i] = EmitBinaryU32(
|
||||
state, spv::OpBitwiseAnd, EmitBinaryU32(state, spv::OpShiftRightLogical, word, shift),
|
||||
ConstantU32(state, 0xffu));
|
||||
for (uint32_t lane = 0; lane < std::min(4u, count - i); ++lane) {
|
||||
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +138,7 @@ void EnsureLdsStorage(EmitterState& state) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationAliased);
|
||||
return variable;
|
||||
};
|
||||
if (state.requirements.shared_int64_atomics) {
|
||||
if (state.program.info.shared_int64_atomics) {
|
||||
state.lds_variable = define(TypeU32(state), 4u);
|
||||
state.lds_u64_variable = define(TypeU64(state), 8u);
|
||||
state.builder.AddName(state.lds_u64_variable, "lds_qwords");
|
||||
@@ -160,20 +152,22 @@ void EnsureLdsStorage(EmitterState& state) {
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable,
|
||||
uint32_t pointer_type) {
|
||||
if (variable == 0) {
|
||||
BufferDefinition& buffer) {
|
||||
if (buffer.variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
|
||||
"storage buffer descriptor array was not emitted");
|
||||
}
|
||||
if (buffer.pointer_type == 0) {
|
||||
buffer.pointer_type = TypePointer(state, spv::StorageClassStorageBuffer, buffer.type);
|
||||
}
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
|
||||
state.program.info.buffers.at(mem.resource).descriptor_index;
|
||||
MemoryResourceAccess access {
|
||||
.kind = mem.kind,
|
||||
.memory_access = mem.coherent ? spv::MemoryAccessVolatileMask : spv::MemoryAccessMaskNone};
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, pointer_type, access.object_pointer, variable,
|
||||
ConstantU32(state, array_index));
|
||||
state.builder.AddFunction(spv::OpAccessChain, buffer.pointer_type, access.object_pointer,
|
||||
buffer.variable, ConstantU32(state, array_index));
|
||||
access.byte_offset = state.memory_byte_offsets[array_index];
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
|
||||
@@ -214,12 +208,14 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
case IR::ResourceKind::ScalarBuffer:
|
||||
case IR::ResourceKind::Buffer: {
|
||||
const auto bits = mem.kind == IR::ResourceKind::Buffer
|
||||
? StorageBufferElementBits(state.program, mem) : 32u;
|
||||
const auto variable = bits == 8u ? state.storage_buffer_u8_variable
|
||||
: bits == 16u ? state.storage_buffer_u16_variable
|
||||
: state.storage_buffer_variable;
|
||||
access = PrepareStorageBufferResourceAccess(
|
||||
state, mem, variable, TypeStorageBufferPointer(state, bits));
|
||||
? IR::StorageBufferElementBits(state.program, mem) : 32u;
|
||||
auto* buffer = &state.storage_buffers[2];
|
||||
if (bits == 8u) {
|
||||
buffer = &state.storage_buffers[0];
|
||||
} else if (bits == 16u) {
|
||||
buffer = &state.storage_buffers[1];
|
||||
}
|
||||
access = PrepareStorageBufferResourceAccess(state, mem, *buffer);
|
||||
access.element_bits = bits;
|
||||
return access;
|
||||
}
|
||||
@@ -245,7 +241,7 @@ uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAcces
|
||||
TypeStorageBufferElementPointer(state, 32));
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
if (access.kind == IR::ResourceKind::Lds && state.requirements.shared_int64_atomics) {
|
||||
if (access.kind == IR::ResourceKind::Lds && state.program.info.shared_int64_atomics) {
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, ConstantU32(state, 0), index);
|
||||
return pointer;
|
||||
|
||||
@@ -120,32 +120,39 @@ uint32_t TypeFunction(EmitterState& state) {
|
||||
return state.function_type;
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferElement(EmitterState& state, uint32_t bits) {
|
||||
return bits == 32u ? TypeU32(state)
|
||||
: bits == 64u ? TypeU64(state)
|
||||
: state.builder.Type(spv::OpTypeInt, bits, 0);
|
||||
}
|
||||
|
||||
uint32_t StorageRuntimeArrayType(EmitterState& state, uint32_t bits) {
|
||||
return state.builder.DecoratedType(
|
||||
spv::OpTypeRuntimeArray,
|
||||
{{spv::OpDecorate, {spv::DecorationArrayStride, bits / 8u}}},
|
||||
TypeStorageBufferElement(state, bits));
|
||||
}
|
||||
|
||||
uint32_t StorageBufferType(EmitterState& state, uint32_t bits = 32) {
|
||||
return state.builder.DecoratedType(spv::OpTypeStruct,
|
||||
{{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
|
||||
{spv::OpDecorate, {spv::DecorationBlock}}},
|
||||
StorageRuntimeArrayType(state, bits));
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state, uint32_t bits) {
|
||||
return TypePointer(state, spv::StorageClassStorageBuffer, StorageBufferType(state, bits));
|
||||
uint32_t StorageBufferType(EmitterState& state, BufferDefinition& buffer, uint32_t bits = 32) {
|
||||
if (buffer.type == 0) {
|
||||
if (bits == 32u) {
|
||||
buffer.element_type = TypeU32(state);
|
||||
} else if (bits == 64u) {
|
||||
buffer.element_type = TypeU64(state);
|
||||
} else {
|
||||
buffer.element_type = state.builder.Type(spv::OpTypeInt, bits, 0);
|
||||
}
|
||||
const auto array = state.builder.DecoratedType(
|
||||
spv::OpTypeRuntimeArray,
|
||||
{{spv::OpDecorate, {spv::DecorationArrayStride, bits / 8u}}}, buffer.element_type);
|
||||
buffer.type = state.builder.DecoratedType(
|
||||
spv::OpTypeStruct, {{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
|
||||
{spv::OpDecorate, {spv::DecorationBlock}}}, array);
|
||||
}
|
||||
return buffer.type;
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state, uint32_t bits) {
|
||||
return TypePointer(state, spv::StorageClassStorageBuffer, TypeStorageBufferElement(state, bits));
|
||||
auto* buffer = &state.storage_buffers[2];
|
||||
if (bits == 8u) {
|
||||
buffer = &state.storage_buffers[0];
|
||||
} else if (bits == 16u) {
|
||||
buffer = &state.storage_buffers[1];
|
||||
} else if (bits == 64u) {
|
||||
buffer = &state.storage_buffers[3];
|
||||
}
|
||||
if (buffer->element_pointer_type == 0) {
|
||||
buffer->element_pointer_type =
|
||||
TypePointer(state, spv::StorageClassStorageBuffer, buffer->element_type);
|
||||
}
|
||||
return buffer->element_pointer_type;
|
||||
}
|
||||
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state) {
|
||||
@@ -200,6 +207,43 @@ uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
|
||||
}
|
||||
|
||||
void DefineDescriptors(EmitterState& state) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
auto& info = state.program.info;
|
||||
auto& bindings = state.program.bindings;
|
||||
auto& counts = bindings.descriptor_counts;
|
||||
auto& buffer_count = counts[static_cast<size_t>(Kind::Buffers)];
|
||||
for (uint32_t index = 0; index < info.buffers.size(); ++index) {
|
||||
info.buffers[index].descriptor_index = (info.live_buffers & (uint64_t{1} << index)) != 0
|
||||
? buffer_count++ : UINT32_MAX;
|
||||
}
|
||||
std::array<const IR::ImageResource*, IR::ImageBindingCount> image_types {};
|
||||
for (auto& image: info.images) {
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
if (!kind.has_value() || image.mip_count == 0u ||
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
EXIT("shader image has an invalid descriptor type or mip count");
|
||||
}
|
||||
auto& count = counts[static_cast<size_t>(*kind)];
|
||||
image.descriptor_index = count;
|
||||
if (count == 0) image_types[IR::ImageBindingIndex(*kind)] = ℑ
|
||||
count += image.mip_count;
|
||||
}
|
||||
counts[static_cast<size_t>(Kind::Samplers)] = static_cast<uint32_t>(info.samplers.size());
|
||||
counts[static_cast<size_t>(Kind::Gds)] = info.uses_gds;
|
||||
counts[static_cast<size_t>(Kind::SharedMemory)] =
|
||||
info.uses_lds && state.lds_storage_class == spv::StorageClassStorageBuffer;
|
||||
counts[static_cast<size_t>(Kind::BdaPagetable)] = info.uses_dma;
|
||||
counts[static_cast<size_t>(Kind::FaultBuffer)] = info.uses_dma;
|
||||
counts[static_cast<size_t>(Kind::FlattenedSrt)] = info.uses_flattened_srt;
|
||||
bindings.memory_offset_dword = static_cast<uint32_t>(info.user_data_registers.size());
|
||||
if (info.uses_dispatch_threads) {
|
||||
bindings.dispatch_thread_dword = bindings.memory_offset_dword;
|
||||
bindings.memory_offset_dword += 3u;
|
||||
}
|
||||
bindings.push_data_start_dword =
|
||||
IR::PushData::StartFor(bindings.push_data_start_dword, bindings.ShaderDataDwords());
|
||||
counts[static_cast<size_t>(Kind::ShaderData)] =
|
||||
bindings.ShaderDataDwords() != 0 && !bindings.UsesPushData();
|
||||
if (state.program.bindings.UsesPushData() || state.program.stage == ShaderType::Mesh) {
|
||||
const auto type = PushConstantBlockType(state);
|
||||
state.push_constant_variable = state.builder.DefineGlobalVariable(
|
||||
@@ -207,7 +251,10 @@ void DefineDescriptors(EmitterState& state) {
|
||||
state.builder.AddName(type, "BufferResource");
|
||||
state.builder.AddName(state.push_constant_variable, "vsharp");
|
||||
}
|
||||
for (const auto& binding: state.program.bindings.descriptors) {
|
||||
for (uint32_t index = 0; index < counts.size(); ++index) {
|
||||
if (counts[index] == 0) continue;
|
||||
bindings.descriptor_mask |= uint64_t{1} << index;
|
||||
const auto kind = static_cast<Kind>(index);
|
||||
const auto Define = [&](uint32_t type, const char* name,
|
||||
spv::StorageClass storage = spv::StorageClassStorageBuffer) {
|
||||
const auto variable =
|
||||
@@ -215,69 +262,66 @@ void DefineDescriptors(EmitterState& state) {
|
||||
state.builder.AddName(variable, name);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, 0);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBinding,
|
||||
IR::NativeBinding(state.program.stage, binding.kind));
|
||||
IR::NativeBinding(state.program.stage, kind));
|
||||
return variable;
|
||||
};
|
||||
const auto ArrayType = [&](uint32_t type) {
|
||||
return state.builder.Type(
|
||||
spv::OpTypeArray, type,
|
||||
ConstantU32(state, static_cast<uint32_t>(binding.resources.size())));
|
||||
ConstantU32(state, counts[index]));
|
||||
};
|
||||
switch (binding.kind) {
|
||||
case IR::DescriptorBindingKind::Buffers:
|
||||
state.storage_buffer_variable =
|
||||
Define(ArrayType(StorageBufferType(state)), "buffers");
|
||||
if (state.requirements.buffer_u8) {
|
||||
state.storage_buffer_u8_variable =
|
||||
Define(ArrayType(StorageBufferType(state, 8)), "buffers_u8");
|
||||
}
|
||||
if (state.requirements.buffer_u16) {
|
||||
state.storage_buffer_u16_variable =
|
||||
Define(ArrayType(StorageBufferType(state, 16)), "buffers_u16");
|
||||
}
|
||||
if (state.requirements.buffer_int64_atomics) {
|
||||
state.storage_buffer_u64_variable =
|
||||
Define(ArrayType(StorageBufferType(state, 64)), "buffers_u64");
|
||||
}
|
||||
for (const auto variable: {state.storage_buffer_variable, state.storage_buffer_u8_variable,
|
||||
state.storage_buffer_u16_variable, state.storage_buffer_u64_variable}) {
|
||||
if (variable == 0) continue;
|
||||
if (state.storage_buffer_u8_variable != 0 || state.storage_buffer_u16_variable != 0 ||
|
||||
state.storage_buffer_u64_variable != 0) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationAliased);
|
||||
switch (kind) {
|
||||
case IR::DescriptorBindingKind::Buffers: {
|
||||
const std::array used {info.buffer_u8, info.buffer_u16, true, info.buffer_int64_atomics};
|
||||
constexpr std::array names {"buffers_u8", "buffers_u16", "buffers", "buffers_u64"};
|
||||
for (uint32_t alias = 0; alias < used.size(); ++alias) {
|
||||
if (!used[alias]) continue;
|
||||
auto& buffer = state.storage_buffers[alias];
|
||||
const auto type = StorageBufferType(state, buffer, 8u << alias);
|
||||
buffer.variable = Define(ArrayType(type), names[alias]);
|
||||
if (info.buffer_u8 || info.buffer_u16 || info.buffer_int64_atomics) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, buffer.variable, spv::DecorationAliased);
|
||||
}
|
||||
// RDNA2 stores publish to L2 even without GLC; every alias of the buffer
|
||||
// must participate in visibility for cache-bypassing polling loads.
|
||||
if (state.requirements.coherent_buffers) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationCoherent);
|
||||
if (info.coherent_buffers) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, buffer.variable, spv::DecorationCoherent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::DescriptorBindingKind::BdaPagetable:
|
||||
state.bda_pagetable_variable = Define(StorageBufferType(state, 64), "bda_pagetable");
|
||||
state.bda_pagetable_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[3], 64), "bda_pagetable");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::FaultBuffer:
|
||||
state.fault_buffer_variable = Define(StorageBufferType(state), "fault_buffer");
|
||||
state.fault_buffer_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[2]), "fault_buffer");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::ShaderData:
|
||||
state.shader_data_storage_variable =
|
||||
Define(StorageBufferType(state), "shader_data");
|
||||
Define(StorageBufferType(state, state.storage_buffers[2]), "shader_data");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::FlattenedSrt:
|
||||
state.flattened_srt_variable = Define(StorageBufferType(state), "flattened_srt");
|
||||
state.flattened_srt_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[2]), "flattened_srt");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::Samplers:
|
||||
state.sampler_variable = Define(ArrayType(state.builder.Type(spv::OpTypeSampler)),
|
||||
"samplers", spv::StorageClassUniformConstant);
|
||||
state.sampler_type = state.builder.Type(spv::OpTypeSampler);
|
||||
state.sampler_pointer_type = TypePointer(
|
||||
state, spv::StorageClassUniformConstant, state.sampler_type);
|
||||
state.sampler_variable = Define(ArrayType(state.sampler_type), "samplers",
|
||||
spv::StorageClassUniformConstant);
|
||||
break;
|
||||
case IR::DescriptorBindingKind::Gds:
|
||||
state.gds_variable = Define(StorageBufferType(state), "gds");
|
||||
state.gds_variable = Define(StorageBufferType(state, state.storage_buffers[2]), "gds");
|
||||
break;
|
||||
case IR::DescriptorBindingKind::SharedMemory:
|
||||
state.lds_variable = Define(StorageBufferType(state), "lds_dwords");
|
||||
state.lds_variable = Define(StorageBufferType(state, state.storage_buffers[2]), "lds_dwords");
|
||||
state.builder.AddAnnotation(spv::OpDecorate, state.lds_variable, spv::DecorationCoherent);
|
||||
if (state.requirements.shared_int64_atomics) {
|
||||
state.lds_u64_variable = Define(StorageBufferType(state, 64), "lds_qwords");
|
||||
if (state.program.info.shared_int64_atomics) {
|
||||
state.lds_u64_variable =
|
||||
Define(StorageBufferType(state, state.storage_buffers[3], 64), "lds_qwords");
|
||||
state.builder.AddAnnotation(spv::OpDecorate, state.lds_u64_variable, spv::DecorationCoherent);
|
||||
for (const auto variable: {state.lds_variable, state.lds_u64_variable}) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationAliased);
|
||||
@@ -285,13 +329,16 @@ void DefineDescriptors(EmitterState& state) {
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
EXIT_IF(IR::ImageBindingResourceClass(binding.kind) ==
|
||||
EXIT_IF(IR::ImageBindingResourceClass(kind) ==
|
||||
IR::ImageResourceClass::None);
|
||||
const auto& image = state.program.info.images.at(binding.resources.front());
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(binding.kind));
|
||||
state.image_variables[IR::ImageBindingIndex(binding.kind)] =
|
||||
Define(ArrayType(ImageType(state, image)), name.c_str(),
|
||||
spv::StorageClassUniformConstant);
|
||||
const auto& image = *image_types[IR::ImageBindingIndex(kind)];
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(kind));
|
||||
auto& definition = state.images[IR::ImageBindingIndex(kind)];
|
||||
definition.type = ImageType(state, image);
|
||||
definition.variable = Define(ArrayType(definition.type), name.c_str(),
|
||||
spv::StorageClassUniformConstant);
|
||||
definition.pointer_type = TypePointer(
|
||||
state, spv::StorageClassUniformConstant, definition.type);
|
||||
if (image.dimension == ImageDimension::Dim1D ||
|
||||
image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(image.resource_class ==
|
||||
@@ -335,11 +382,11 @@ uint32_t ConstantU64(EmitterState& state, uint64_t value) {
|
||||
|
||||
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components) {
|
||||
EXIT_IF(components < 1u || components > 4u);
|
||||
const auto zero = ConstantU32(state, 0);
|
||||
const auto zero = ConstantU32(state, 0);
|
||||
if (components == 1u) return zero;
|
||||
std::vector<uint32_t> values(components, zero);
|
||||
const std::array values {zero, zero, zero, zero};
|
||||
return state.builder.Constant(spv::OpConstantComposite, TypeU32Composite(state, components),
|
||||
values);
|
||||
std::span(values).first(components));
|
||||
}
|
||||
|
||||
uint32_t GlslStd450(EmitterState& state) {
|
||||
@@ -437,7 +484,7 @@ void DefineInputs(EmitterState& state) {
|
||||
state.inputs.push_back({{kind, 0, components, name}});
|
||||
}
|
||||
};
|
||||
if (IR::FindBinding(state.program.bindings, IR::DescriptorBindingKind::SharedMemory) != nullptr) {
|
||||
if (state.program.info.uses_lds && state.lds_storage_class == spv::StorageClassStorageBuffer) {
|
||||
add_builtin(IR::StageInputKind::WorkgroupId, 3, "gl_WorkGroupID");
|
||||
add_builtin(IR::StageInputKind::NumWorkgroups, 3, "gl_NumWorkGroups");
|
||||
}
|
||||
@@ -524,7 +571,7 @@ void DefineInputs(EmitterState& state) {
|
||||
builtin);
|
||||
}
|
||||
}
|
||||
if (state.requirements.subgroup_local_invocation_id) {
|
||||
if (state.program.info.subgroup_local_invocation_id) {
|
||||
const auto variable = DefineInterfaceVariable(state, TypeU32(state), spv::StorageClassInput,
|
||||
"gl_SubgroupInvocationID");
|
||||
state.subgroup_local_invocation_id_variable = variable;
|
||||
@@ -647,10 +694,10 @@ void DefineModule(EmitterState& state) {
|
||||
DefineOutputs(state);
|
||||
DefineTessellationInterfaces(state);
|
||||
DefineDescriptors(state);
|
||||
if (state.requirements.function_lds) {
|
||||
if (state.program.info.function_lds) {
|
||||
state.lds_variable = state.builder.AllocateId();
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
if (state.program.info.function_scratch) {
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.scratch_variable[half] = state.builder.AllocateId();
|
||||
}
|
||||
@@ -675,11 +722,11 @@ void DefineModule(EmitterState& state) {
|
||||
|
||||
state.builder.RequireCapability(spv::CapabilityShader);
|
||||
state.builder.RequireCapability(spv::CapabilitySignedZeroInfNanPreserve);
|
||||
if (state.requirements.buffer_u8) {
|
||||
if (state.program.info.buffer_u8) {
|
||||
state.builder.RequireExtension("SPV_KHR_8bit_storage");
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBuffer8BitAccess);
|
||||
}
|
||||
if (state.requirements.buffer_u16) {
|
||||
if (state.program.info.buffer_u16) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBuffer16BitAccess);
|
||||
}
|
||||
if (state.program.info.uses_dma) {
|
||||
@@ -687,11 +734,11 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireCapability(spv::CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
}
|
||||
if (state.requirements.buffer_int64_atomics || state.requirements.shared_int64_atomics) {
|
||||
if (state.program.info.buffer_int64_atomics || state.program.info.shared_int64_atomics) {
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.builder.RequireCapability(spv::CapabilityInt64Atomics);
|
||||
}
|
||||
if (state.requirements.shared_int64_atomics &&
|
||||
if (state.program.info.shared_int64_atomics &&
|
||||
state.lds_storage_class == spv::StorageClassWorkgroup) {
|
||||
state.builder.RequireVersion(0x00010400u);
|
||||
state.builder.RequireExtension("SPV_KHR_workgroup_memory_explicit_layout");
|
||||
@@ -714,21 +761,21 @@ void DefineModule(EmitterState& state) {
|
||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
||||
}
|
||||
if (state.requirements.image_gather_extended) {
|
||||
if (state.program.info.image_gather_extended) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||
}
|
||||
if (state.lane_count == 2 || state.requirements.subgroup_barrier ||
|
||||
state.requirements.subgroup_ballot || state.requirements.subgroup_shuffle ||
|
||||
state.requirements.subgroup_local_invocation_id) {
|
||||
if (state.lane_count == 2 || state.program.info.subgroup_barrier ||
|
||||
state.program.info.subgroup_ballot || state.program.info.subgroup_shuffle ||
|
||||
state.program.info.subgroup_local_invocation_id) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniform);
|
||||
}
|
||||
if (state.lane_count == 2 || state.requirements.subgroup_ballot) {
|
||||
if (state.lane_count == 2 || state.program.info.subgroup_ballot) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniformBallot);
|
||||
}
|
||||
if (state.requirements.subgroup_shuffle) {
|
||||
if (state.program.info.subgroup_shuffle) {
|
||||
state.builder.RequireCapability(spv::CapabilityGroupNonUniformShuffle);
|
||||
}
|
||||
if (state.requirements.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
if (state.program.info.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
state.builder.RequireCapability(spv::CapabilityComputeDerivativeGroupQuadsKHR);
|
||||
state.builder.RequireExtension("SPV_KHR_compute_shader_derivatives");
|
||||
}
|
||||
@@ -751,7 +798,7 @@ void DefineModule(EmitterState& state) {
|
||||
// contract prevents host compilers from treating synthesized IEEE values as finite.
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve,
|
||||
32u);
|
||||
if (state.requirements.float64) {
|
||||
if (state.program.info.float64) {
|
||||
EXIT_NOT_IMPLEMENTED(state.program.stage == ShaderType::Compute &&
|
||||
state.input_info.compute->float_mode != 0xc0);
|
||||
// Use native rounding for MODE=0xc0, consistent with ordinary FP32 arithmetic.
|
||||
@@ -763,8 +810,8 @@ void DefineModule(EmitterState& state) {
|
||||
// state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDenormPreserve, 64u);
|
||||
}
|
||||
if (const auto* cs = ShaderWorkgroupInput(state.program.stage, state.input_info)) {
|
||||
uint32_t local_x = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_y = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_x = state.program.info.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_y = state.program.info.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_z = 1u;
|
||||
local_x = cs->threads_num[0] != 0u ? cs->threads_num[0] : local_x;
|
||||
local_y = cs->threads_num[1] != 0u ? cs->threads_num[1] : local_y;
|
||||
@@ -772,7 +819,7 @@ void DefineModule(EmitterState& state) {
|
||||
if (state.lane_count == 2) {
|
||||
local_x = ((local_x * local_y * local_z + 63u) / 64u) * 32u;
|
||||
local_y = local_z = 1u;
|
||||
if (state.requirements.compute_derivatives) {
|
||||
if (state.program.info.compute_derivatives) {
|
||||
local_y = local_x / 2u;
|
||||
local_x = 2u;
|
||||
}
|
||||
@@ -791,14 +838,14 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeEarlyFragmentTests);
|
||||
}
|
||||
}
|
||||
if (state.requirements.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
if (state.program.info.compute_derivatives && state.program.stage == ShaderType::Compute) {
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDerivativeGroupQuadsKHR);
|
||||
}
|
||||
state.builder.AddName(state.main_func, "main");
|
||||
if (state.requirements.function_lds) {
|
||||
if (state.program.info.function_lds) {
|
||||
state.builder.AddName(state.lds_variable, "lds_dwords");
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
if (state.program.info.function_scratch) {
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.builder.AddName(state.scratch_variable[half], "scratch_dwords");
|
||||
}
|
||||
|
||||
@@ -83,63 +83,50 @@ void StoreDispatcherPhiEdge(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
}
|
||||
}
|
||||
|
||||
const IR::Block* TargetBlock(const IR::Program& program, uint32_t id) {
|
||||
const auto found = std::ranges::find_if(
|
||||
program.block_info, [&](const IR::BlockInfo& info) { return info.id == id; });
|
||||
if (found == program.block_info.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return program.blocks[static_cast<size_t>(found - program.block_info.begin())];
|
||||
}
|
||||
|
||||
void EmitReturn(ValueEmitContext& ctx) {
|
||||
EmitKillIfPixelValidMaskInactive(ctx.state);
|
||||
ctx.state.builder.AddFunction(spv::OpReturn);
|
||||
}
|
||||
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
const IR::BlockInfo& info) {
|
||||
const auto& program = ctx.state.program;
|
||||
const auto& term = info.terminator;
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block) {
|
||||
const auto& term = block->terminator;
|
||||
const auto emit_merge = [&]() {
|
||||
if (term.loop_header) {
|
||||
const auto* merge = TargetBlock(program, term.merge_block);
|
||||
const auto* cont = TargetBlock(program, term.continue_block);
|
||||
const auto* merge = term.merge_block;
|
||||
const auto* cont = term.continue_block;
|
||||
if (merge != nullptr && cont != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpLoopMerge, ctx.Label(merge), ctx.Label(cont),
|
||||
spv::LoopControlMaskNone);
|
||||
ctx.state.builder.AddFunction(spv::OpLoopMerge, merge->Definition(),
|
||||
cont->Definition(), spv::LoopControlMaskNone);
|
||||
}
|
||||
} else if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
|
||||
term.merge_block != UINT32_MAX) {
|
||||
if (const auto* merge = TargetBlock(program, term.merge_block); merge != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpSelectionMerge, ctx.Label(merge),
|
||||
spv::SelectionControlMaskNone);
|
||||
}
|
||||
term.merge_block != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpSelectionMerge, term.merge_block->Definition(),
|
||||
spv::SelectionControlMaskNone);
|
||||
}
|
||||
};
|
||||
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch: {
|
||||
const auto* target = TargetBlock(program, term.true_block);
|
||||
const auto* target = term.true_block;
|
||||
if (target == nullptr) {
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
}
|
||||
emit_merge();
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, ctx.Label(target));
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, target->Definition());
|
||||
return;
|
||||
}
|
||||
case CFG::TerminatorKind::ConditionalBranch: {
|
||||
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()) {
|
||||
const auto* true_block = term.true_block;
|
||||
const auto* false_block = term.false_block;
|
||||
if (true_block == nullptr || false_block == nullptr || block->condition.IsEmpty()) {
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
}
|
||||
const auto condition = ctx.Def(info.condition);
|
||||
const auto condition = ctx.Def(block->condition);
|
||||
emit_merge();
|
||||
ctx.state.builder.AddFunction(spv::OpBranchConditional, condition,
|
||||
ctx.Label(true_block), ctx.Label(false_block));
|
||||
true_block->Definition(), false_block->Definition());
|
||||
return;
|
||||
}
|
||||
default: EmitReturn(ctx); return;
|
||||
@@ -147,8 +134,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.state.program, target);
|
||||
const IR::Block* from, const IR::Block* block) {
|
||||
if (block != nullptr) {
|
||||
StoreDispatcherPhiEdge(ctx, dispatcher, from, block);
|
||||
if (ctx.other_half != nullptr) {
|
||||
@@ -158,41 +144,37 @@ void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState&
|
||||
}
|
||||
|
||||
uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* block, const IR::BlockInfo& info) {
|
||||
const auto& term = info.terminator;
|
||||
const IR::Block* block) {
|
||||
const auto& term = block->terminator;
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch:
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
return ConstantU32(ctx.state, term.true_block);
|
||||
return ConstantU32(ctx.state, term.true_block->id);
|
||||
case CFG::TerminatorKind::ConditionalBranch: {
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
||||
const auto selected = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpSelect, TypeU32(ctx.state), selected, ctx.Def(info.condition),
|
||||
ConstantU32(ctx.state, term.true_block), ConstantU32(ctx.state, term.false_block));
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), selected,
|
||||
ctx.Def(block->condition),
|
||||
ConstantU32(ctx.state, term.true_block->id),
|
||||
ConstantU32(ctx.state, term.false_block->id));
|
||||
return selected;
|
||||
}
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
for (const auto target: term.indirect_targets) {
|
||||
for (const auto* target: block->ImmSuccessors()) {
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, target);
|
||||
}
|
||||
uint32_t selected = ConstantU32(ctx.state, UINT32_MAX);
|
||||
if (!info.indirect_target.IsEmpty()) {
|
||||
const auto selector = ctx.Def(info.indirect_target);
|
||||
const auto& values = term.indirect_selector_code != UINT32_MAX
|
||||
? term.indirect_selector_values
|
||||
: term.indirect_target_pcs;
|
||||
const auto& targets = term.indirect_selector_code != UINT32_MAX
|
||||
? term.indirect_selector_targets
|
||||
: term.indirect_targets;
|
||||
for (size_t index = 0; index < std::min(values.size(), targets.size()); index++) {
|
||||
if (!block->indirect_target.IsEmpty()) {
|
||||
const auto selector = ctx.Def(block->indirect_target);
|
||||
for (const auto& branch: term.cases) {
|
||||
const auto match = ctx.state.builder.AllocateId();
|
||||
const auto next = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpIEqual, TypeBool(ctx.state), match,
|
||||
selector, ConstantU32(ctx.state, values[index]));
|
||||
selector, ConstantU32(ctx.state, branch.value));
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), next, match,
|
||||
ConstantU32(ctx.state, targets[index]), selected);
|
||||
ConstantU32(ctx.state, branch.target->id),
|
||||
selected);
|
||||
selected = next;
|
||||
}
|
||||
}
|
||||
@@ -267,7 +249,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.state.labels.contains(predecessor)) {
|
||||
if (predecessor == nullptr || predecessor->Definition() == 0) {
|
||||
ctx.Fail(inst, "has a predecessor outside the structured function");
|
||||
}
|
||||
}
|
||||
@@ -301,7 +283,7 @@ void EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionSt
|
||||
template <typename EmitInstruction>
|
||||
void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&& emit_instruction) {
|
||||
ctx.state.current_block = block;
|
||||
EmitLabel(ctx.state, ctx.Label(block));
|
||||
EmitLabel(ctx.state, block->Definition());
|
||||
bool emitted_non_phi = false;
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::Phi) {
|
||||
@@ -342,14 +324,14 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu
|
||||
void EmitStructuredFunction(ValueEmitContext& ctx) {
|
||||
const auto& program = ctx.state.program;
|
||||
StructuredFunctionState structured;
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, ctx.Label(program.blocks.front()));
|
||||
ctx.state.builder.AddFunction(spv::OpBranch, program.blocks.front()->Definition());
|
||||
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, program.block_info[index]);
|
||||
EmitStructuredTerminator(ctx, block);
|
||||
}
|
||||
PatchStructuredPhis(ctx, structured);
|
||||
}
|
||||
@@ -357,12 +339,11 @@ void EmitStructuredFunction(ValueEmitContext& ctx) {
|
||||
void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher) {
|
||||
auto& state = ctx.state;
|
||||
const auto* entry = state.program.blocks.front();
|
||||
state.builder.AddFunction(spv::OpBranch, ctx.Label(entry));
|
||||
state.builder.AddFunction(spv::OpBranch, entry->Definition());
|
||||
EmitBlock(ctx, entry, [&](ValueEmitContext& lane, const IR::Inst& inst) {
|
||||
EmitDispatcherInstruction(lane, dispatcher, inst);
|
||||
});
|
||||
const auto initial_pc =
|
||||
EmitDispatcherNextPc(ctx, dispatcher, entry, state.program.block_info.front());
|
||||
const auto initial_pc = EmitDispatcherNextPc(ctx, dispatcher, entry);
|
||||
const auto initial_parent = state.current_label;
|
||||
state.builder.AddFunction(spv::OpBranch, dispatcher.header_label);
|
||||
|
||||
@@ -384,8 +365,8 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
spv::SelectionControlMaskNone);
|
||||
std::vector<uint32_t> words {spv::OpSwitch, pc, dispatcher.after_switch_label};
|
||||
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]));
|
||||
words.push_back(state.program.blocks[index]->id);
|
||||
words.push_back(state.program.blocks[index]->Definition());
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
std::vector<uint32_t> next_pc_words {spv::OpPhi, TypeU32(state), next_pc,
|
||||
@@ -396,8 +377,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
[&](ValueEmitContext& lane, const IR::Inst& inst) {
|
||||
EmitDispatcherInstruction(lane, dispatcher, inst);
|
||||
});
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, state.program.blocks[index],
|
||||
state.program.block_info[index]);
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, state.program.blocks[index]);
|
||||
next_pc_words.push_back(selected);
|
||||
next_pc_words.push_back(state.current_label);
|
||||
state.builder.AddFunction(spv::OpBranch, dispatcher.after_switch_label);
|
||||
@@ -551,23 +531,23 @@ uint32_t ValueEmitContext::Shuffle(const IR::Inst& inst, size_t index, uint32_t
|
||||
}
|
||||
|
||||
uint32_t ValueEmitContext::Result(const IR::Inst& inst) {
|
||||
if (const auto found = definitions.find(&inst); found != definitions.end()) {
|
||||
return found->second;
|
||||
if (const auto id = inst.Definition(half); id != 0) {
|
||||
return id;
|
||||
}
|
||||
const auto id = state.builder.AllocateId();
|
||||
definitions.emplace(&inst, id);
|
||||
inst.SetDefinition(half, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) {
|
||||
if (const auto found = definitions.find(&inst); found != definitions.end()) {
|
||||
if (found->second != value) {
|
||||
if (const auto id = inst.Definition(half); id != 0) {
|
||||
if (id != value) {
|
||||
state.builder.AddFunction(spv::OpCopyObject, TypeId(state, inst.GetType()),
|
||||
found->second, value);
|
||||
id, value);
|
||||
}
|
||||
return found->second;
|
||||
return id;
|
||||
}
|
||||
definitions.emplace(&inst, value);
|
||||
inst.SetDefinition(half, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -595,10 +575,6 @@ const IR::ExportInfo& ValueEmitContext::Export(const IR::Inst& inst) const {
|
||||
return state.program.export_info.at(inst.Flags<IR::ExportFlags>().index);
|
||||
}
|
||||
|
||||
uint32_t ValueEmitContext::Label(const IR::Block* block) const {
|
||||
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",
|
||||
state.program.shader_hash, static_cast<unsigned>(state.program.stage), reason);
|
||||
@@ -622,13 +598,16 @@ void EmitProgram(EmitterState& state) {
|
||||
high.half = 1;
|
||||
}
|
||||
std::optional<DispatcherFunctionState> dispatcher;
|
||||
if (state.program.stage == ShaderType::Pixel && state.requirements.pixel_valid_mask) {
|
||||
if (state.program.stage == ShaderType::Pixel && state.program.info.pixel_valid_mask) {
|
||||
state.pixel_valid_mask_variable = state.builder.AllocateId();
|
||||
state.builder.AddName(state.pixel_valid_mask_variable, "pixel_valid_mask_active");
|
||||
}
|
||||
for (const auto* block: program.blocks) {
|
||||
const auto label = state.builder.AllocateId();
|
||||
state.labels.emplace(block, label);
|
||||
// Re-emission may use a different host subgroup width and ID allocation order.
|
||||
for (const auto& inst: *block) {
|
||||
inst.ResetDefinitions();
|
||||
}
|
||||
block->SetDefinition(state.builder.AllocateId());
|
||||
}
|
||||
if (state.program.dispatcher_fallback) {
|
||||
auto& dispatch = dispatcher.emplace();
|
||||
@@ -669,8 +648,8 @@ void EmitProgram(EmitterState& state) {
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < program.blocks.size(); index++) {
|
||||
mark_cross_block(program.block_info[index].condition, program.blocks[index]);
|
||||
mark_cross_block(program.block_info[index].indirect_target, program.blocks[index]);
|
||||
mark_cross_block(program.blocks[index]->condition, program.blocks[index]);
|
||||
mark_cross_block(program.blocks[index]->indirect_target, program.blocks[index]);
|
||||
}
|
||||
dispatch.header_label = state.builder.AllocateId();
|
||||
dispatch.select_label = state.builder.AllocateId();
|
||||
@@ -687,28 +666,23 @@ void EmitProgram(EmitterState& state) {
|
||||
}
|
||||
DefineGetBdaPointer(state);
|
||||
DefineBvhIntersect(state);
|
||||
for (const auto* block: program.blocks) {
|
||||
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
|
||||
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32;
|
||||
})) {
|
||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||
if (state.lane_count == 2) {
|
||||
high.scratch_u32_variable = state.builder.AllocateId();
|
||||
}
|
||||
break;
|
||||
if (program.info.uses_swizzle) {
|
||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||
if (state.lane_count == 2) {
|
||||
high.scratch_u32_variable = state.builder.AllocateId();
|
||||
}
|
||||
}
|
||||
state.builder.AddFunction(spv::OpFunction, TypeVoid(state),
|
||||
state.mesh_guest_func != 0 ? state.mesh_guest_func : state.main_func,
|
||||
spv::FunctionControlMaskNone, TypeFunction(state));
|
||||
EmitLabel(state, state.entry_label);
|
||||
if (state.requirements.function_lds) {
|
||||
if (state.program.info.function_lds) {
|
||||
state.builder.AddFunction(
|
||||
spv::OpVariable,
|
||||
TypeU32ArrayPointer(state, spv::StorageClassFunction, LdsDwordCount(state)),
|
||||
state.lds_variable, spv::StorageClassFunction);
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
if (state.program.info.function_scratch) {
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.builder.AddFunction(
|
||||
spv::OpVariable,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::CFG {
|
||||
@@ -568,9 +567,8 @@ bool ResolveSetpcTargets(const Decoder::Program& program, uint32_t setpc_index,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidTarget(uint32_t target, const std::set<uint32_t>& instruction_pcs, uint32_t first_pc,
|
||||
uint32_t end_pc) {
|
||||
return target == end_pc || (target >= first_pc && instruction_pcs.contains(target));
|
||||
bool IsValidTarget(uint32_t target, std::span<const Instruction> instructions, uint32_t end_pc) {
|
||||
return target == end_pc || std::ranges::binary_search(instructions, target, {}, &Instruction::pc);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> AllBlockIds(uint32_t count) {
|
||||
@@ -1455,9 +1453,7 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
const auto first_pc = program.instructions.front().pc;
|
||||
const auto end_pc = ProgramEndPc(program);
|
||||
|
||||
std::set<uint32_t> instruction_pcs;
|
||||
for (const auto& inst: program.instructions) {
|
||||
instruction_pcs.insert(inst.pc);
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::UnsupportedInstruction, UINT32_MAX,
|
||||
@@ -1466,23 +1462,21 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
}
|
||||
}
|
||||
|
||||
std::set<uint32_t> labels;
|
||||
labels.insert(first_pc);
|
||||
labels.insert(end_pc);
|
||||
std::vector<uint32_t> labels {first_pc, end_pc};
|
||||
|
||||
std::map<uint32_t, SetpcTargetInfo> setpc_targets;
|
||||
for (uint32_t i = 0; i < program.instructions.size(); i++) {
|
||||
const auto& inst = program.instructions[i];
|
||||
const auto next_pc = InstructionEndPc(inst);
|
||||
if (Decoder::IsDirectBranch(inst.opcode)) {
|
||||
if (!IsValidTarget(inst.branch_target, instruction_pcs, first_pc, end_pc)) {
|
||||
if (!IsValidTarget(inst.branch_target, program.instructions, end_pc)) {
|
||||
ExitBuildFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}",
|
||||
inst.pc, inst.branch_target));
|
||||
}
|
||||
labels.insert(inst.branch_target);
|
||||
labels.push_back(inst.branch_target);
|
||||
if (next_pc <= end_pc) {
|
||||
labels.insert(next_pc);
|
||||
labels.push_back(next_pc);
|
||||
}
|
||||
} else if (inst.opcode == Opcode::S_SETPC_B64) {
|
||||
SetpcTargetInfo target_info;
|
||||
@@ -1495,30 +1489,32 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
? 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)) {
|
||||
if (!IsValidTarget(target, program.instructions, end_pc)) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}",
|
||||
inst.pc, target));
|
||||
}
|
||||
labels.insert(target);
|
||||
labels.push_back(target);
|
||||
}
|
||||
setpc_targets.emplace(inst.pc, std::move(target_info));
|
||||
if (next_pc <= end_pc) {
|
||||
labels.insert(next_pc);
|
||||
labels.push_back(next_pc);
|
||||
}
|
||||
} else if (inst.opcode == Opcode::S_ENDPGM) {
|
||||
labels.insert(next_pc);
|
||||
labels.push_back(next_pc);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> sorted_labels(labels.begin(), labels.end());
|
||||
for (uint32_t i = 0; i < sorted_labels.size(); i++) {
|
||||
const auto start = sorted_labels[i];
|
||||
SortUnique(labels);
|
||||
graph.blocks.reserve(labels.size());
|
||||
for (uint32_t i = 0; i < labels.size(); i++) {
|
||||
const auto start = labels[i];
|
||||
if (start > end_pc) {
|
||||
continue;
|
||||
}
|
||||
if (start != end_pc && !instruction_pcs.contains(start)) {
|
||||
const auto begin = std::ranges::lower_bound(program.instructions, start, {}, &Instruction::pc);
|
||||
if (start != end_pc && (begin == program.instructions.end() || begin->pc != start)) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::InvalidLabel, UINT32_MAX,
|
||||
fmt::format("CFG label does not start on an instruction: 0x{:08x}", start));
|
||||
@@ -1527,11 +1523,8 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
BasicBlock block;
|
||||
block.id = static_cast<uint32_t>(graph.blocks.size());
|
||||
block.start_pc = start;
|
||||
block.end_pc = i + 1u < sorted_labels.size() ? sorted_labels[i + 1u] : end_pc;
|
||||
block.inst_begin = static_cast<uint32_t>(
|
||||
std::lower_bound(program.instructions.begin(), program.instructions.end(), start,
|
||||
[](const Instruction& inst, uint32_t pc) { return inst.pc < pc; }) -
|
||||
program.instructions.begin());
|
||||
block.end_pc = i + 1u < labels.size() ? labels[i + 1u] : end_pc;
|
||||
block.inst_begin = static_cast<uint32_t>(begin - program.instructions.begin());
|
||||
block.inst_end = static_cast<uint32_t>(
|
||||
std::lower_bound(program.instructions.begin(), program.instructions.end(), block.end_pc,
|
||||
[](const Instruction& inst, uint32_t pc) { return inst.pc < pc; }) -
|
||||
@@ -1541,10 +1534,10 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
|
||||
graph.entry_block = 0;
|
||||
|
||||
std::map<uint32_t, uint32_t> pc_to_block;
|
||||
for (const auto& block: graph.blocks) {
|
||||
pc_to_block.emplace(block.start_pc, block.id);
|
||||
}
|
||||
const auto block_at_pc = [&](uint32_t pc) {
|
||||
const auto found = std::ranges::lower_bound(graph.blocks, pc, {}, &BasicBlock::start_pc);
|
||||
return found != graph.blocks.end() && found->start_pc == pc ? found->id : UINT32_MAX;
|
||||
};
|
||||
|
||||
for (auto& block: graph.blocks) {
|
||||
block.terminator = {};
|
||||
@@ -1566,7 +1559,7 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
block.terminator.indirect_selector_code = target_info.selector_code;
|
||||
for (const auto target_pc: target_info.target_pcs) {
|
||||
block.terminator.indirect_target_pcs.push_back(target_pc);
|
||||
block.terminator.indirect_targets.push_back(pc_to_block.at(target_pc));
|
||||
block.terminator.indirect_targets.push_back(block_at_pc(target_pc));
|
||||
}
|
||||
const auto selector_count = std::min(target_info.selector_values.size(),
|
||||
target_info.selector_target_pcs.size());
|
||||
@@ -1574,35 +1567,35 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
block.terminator.indirect_selector_values.push_back(
|
||||
target_info.selector_values[i]);
|
||||
block.terminator.indirect_selector_targets.push_back(
|
||||
pc_to_block.at(target_info.selector_target_pcs[i]));
|
||||
block_at_pc(target_info.selector_target_pcs[i]));
|
||||
}
|
||||
} else {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.true_block = pc_to_block.at(target_info.target);
|
||||
block.terminator.true_block = block_at_pc(target_info.target);
|
||||
}
|
||||
} 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);
|
||||
block.terminator.true_block = block_at_pc(last.branch_target);
|
||||
} 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);
|
||||
const auto fallthrough = pc_to_block.find(next_pc);
|
||||
if (fallthrough == pc_to_block.end()) {
|
||||
block.terminator.true_block = block_at_pc(last.branch_target);
|
||||
const auto fallthrough = block_at_pc(next_pc);
|
||||
if (fallthrough == UINT32_MAX) {
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::MissingFallthrough, block.id,
|
||||
fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block",
|
||||
last.pc));
|
||||
}
|
||||
block.terminator.false_block = fallthrough->second;
|
||||
block.terminator.false_block = fallthrough;
|
||||
} else {
|
||||
auto next = pc_to_block.find(block.end_pc);
|
||||
if (next != pc_to_block.end() && block.end_pc != block.start_pc) {
|
||||
const auto next = block_at_pc(block.end_pc);
|
||||
if (next != UINT32_MAX && block.end_pc != block.start_pc) {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.true_block = next->second;
|
||||
block.terminator.true_block = next;
|
||||
} else {
|
||||
block.terminator.kind = TerminatorKind::Return;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32},
|
||||
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
|
||||
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
|
||||
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
||||
@@ -296,6 +298,9 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X) {
|
||||
inst.dst.sdwa_sel = 4u;
|
||||
}
|
||||
DecodeVectorGpr(vaddr, inst.src0);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
||||
DecodeScalarSource(soffset, pc, inst.src2);
|
||||
|
||||
@@ -53,6 +53,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
|
||||
{0x14u, Opcode::S_FF1_I32_B64},
|
||||
{0x15u, Opcode::S_FLBIT_I32_B32},
|
||||
{0x16u, Opcode::S_FLBIT_I32_B64},
|
||||
{0x19u, Opcode::S_SEXT_I32_I8},
|
||||
{0x1au, Opcode::S_SEXT_I32_I16},
|
||||
{0x1bu, Opcode::S_BITSET0_B32},
|
||||
{0x1cu, Opcode::S_BITSET0_B64},
|
||||
|
||||
@@ -503,6 +503,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
OperandToString(inst.dst).c_str(),
|
||||
OperandToString(inst.src0).c_str()));
|
||||
case Opcode::S_ABS_I32:
|
||||
case Opcode::S_SEXT_I32_I8:
|
||||
case Opcode::S_SEXT_I32_I16:
|
||||
case Opcode::S_BREV_B32:
|
||||
case Opcode::S_BREV_B64:
|
||||
@@ -618,10 +619,12 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XY:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_LOAD_UBYTE:
|
||||
case Opcode::BUFFER_LOAD_USHORT:
|
||||
case Opcode::BUFFER_LOAD_DWORD:
|
||||
|
||||
@@ -45,6 +45,7 @@ enum class Opcode {
|
||||
S_CMOV_B64,
|
||||
S_MOVK_I32,
|
||||
S_ABS_I32,
|
||||
S_SEXT_I32_I8,
|
||||
S_SEXT_I32_I16,
|
||||
S_ABSDIFF_I32,
|
||||
S_BREV_B32,
|
||||
@@ -269,6 +270,7 @@ enum class Opcode {
|
||||
V_MIN3_I32,
|
||||
V_MIN3_U32,
|
||||
V_MIN3_F16,
|
||||
V_MIN3_U16,
|
||||
V_MAX3_F32,
|
||||
V_MAX3_I32,
|
||||
V_MAX3_U32,
|
||||
@@ -399,6 +401,7 @@ enum class Opcode {
|
||||
V_CMP_T_I32,
|
||||
V_CMP_CLASS_F32,
|
||||
V_CMPX_CLASS_F32,
|
||||
V_CMP_CLASS_F16,
|
||||
V_CMPX_CLASS_F16,
|
||||
V_CMP_LT_I16,
|
||||
V_CMP_EQ_I16,
|
||||
@@ -455,19 +458,34 @@ enum class Opcode {
|
||||
V_CMP_NE_U32,
|
||||
V_CMP_GE_U32,
|
||||
V_CMP_T_U32,
|
||||
V_CMP_F_I64,
|
||||
V_CMP_EQ_I64,
|
||||
V_CMP_LT_I64,
|
||||
V_CMP_LE_I64,
|
||||
V_CMP_NE_I64,
|
||||
V_CMP_T_I64,
|
||||
V_CMP_F_U64,
|
||||
V_CMP_LT_U64,
|
||||
V_CMP_EQ_U64,
|
||||
V_CMP_LE_U64,
|
||||
V_CMP_GT_U64,
|
||||
V_CMP_NE_U64,
|
||||
V_CMP_GE_U64,
|
||||
V_CMP_T_U64,
|
||||
V_CMPX_F_I64,
|
||||
V_CMPX_LT_I64,
|
||||
V_CMPX_EQ_I64,
|
||||
V_CMPX_LE_I64,
|
||||
V_CMPX_NE_I64,
|
||||
V_CMPX_T_I64,
|
||||
V_CMPX_F_U64,
|
||||
V_CMPX_LT_U64,
|
||||
V_CMPX_EQ_U64,
|
||||
V_CMPX_LE_U64,
|
||||
V_CMPX_GT_U64,
|
||||
V_CMPX_NE_U64,
|
||||
V_CMPX_GE_U64,
|
||||
V_CMPX_T_U64,
|
||||
V_CMPX_LT_U32,
|
||||
V_CMPX_EQ_U32,
|
||||
V_CMPX_LE_U32,
|
||||
@@ -490,10 +508,12 @@ enum class Opcode {
|
||||
BUFFER_LOAD_FORMAT_XY,
|
||||
BUFFER_LOAD_FORMAT_XYZ,
|
||||
BUFFER_LOAD_FORMAT_XYZW,
|
||||
BUFFER_LOAD_FORMAT_D16_X,
|
||||
BUFFER_STORE_FORMAT_X,
|
||||
BUFFER_STORE_FORMAT_XY,
|
||||
BUFFER_STORE_FORMAT_XYZ,
|
||||
BUFFER_STORE_FORMAT_XYZW,
|
||||
BUFFER_STORE_FORMAT_D16_X,
|
||||
BUFFER_LOAD_UBYTE,
|
||||
BUFFER_LOAD_SBYTE,
|
||||
BUFFER_LOAD_USHORT,
|
||||
|
||||
@@ -36,7 +36,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
|
||||
{0x05u, Opcode::V_SUBREV_F32},
|
||||
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
|
||||
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerFullDestination},
|
||||
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0fu, Opcode::V_MIN_F32},
|
||||
{0x10u, Opcode::V_MAX_F32},
|
||||
{0x11u, Opcode::V_MIN_I32},
|
||||
@@ -245,7 +245,8 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0x88u, Opcode::V_CMP_CLASS_F32}, {0x89u, Opcode::V_CMP_LT_I16},
|
||||
{0x8au, Opcode::V_CMP_EQ_I16}, {0x8bu, Opcode::V_CMP_LE_I16},
|
||||
{0x8cu, Opcode::V_CMP_GT_I16}, {0x8du, Opcode::V_CMP_NE_I16},
|
||||
{0x8eu, Opcode::V_CMP_GE_I16}, {0x91u, Opcode::V_CMPX_LT_I32},
|
||||
{0x8eu, Opcode::V_CMP_GE_I16}, {0x8fu, Opcode::V_CMP_CLASS_F16, false},
|
||||
{0x91u, Opcode::V_CMPX_LT_I32},
|
||||
{0x92u, Opcode::V_CMPX_EQ_I32}, {0x93u, Opcode::V_CMPX_LE_I32},
|
||||
{0x94u, Opcode::V_CMPX_GT_I32}, {0x95u, Opcode::V_CMPX_NE_I32},
|
||||
{0x96u, Opcode::V_CMPX_GE_I32}, {0x98u, Opcode::V_CMPX_CLASS_F32},
|
||||
@@ -265,10 +266,18 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false},
|
||||
{0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa3u, Opcode::V_CMP_LE_I64, false},
|
||||
{0xa5u, Opcode::V_CMP_NE_I64, false},
|
||||
{0xa0u, Opcode::V_CMP_F_I64, false}, {0xa7u, Opcode::V_CMP_T_I64, false},
|
||||
{0xb0u, Opcode::V_CMPX_F_I64, false}, {0xb1u, Opcode::V_CMPX_LT_I64, false},
|
||||
{0xb2u, Opcode::V_CMPX_EQ_I64, false}, {0xb3u, Opcode::V_CMPX_LE_I64, false},
|
||||
{0xb7u, Opcode::V_CMPX_T_I64, false},
|
||||
{0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xd1u, Opcode::V_CMPX_LT_U32},
|
||||
{0xd2u, Opcode::V_CMPX_EQ_U32}, {0xd3u, Opcode::V_CMPX_LE_U32},
|
||||
{0xd4u, Opcode::V_CMPX_GT_U32}, {0xd5u, Opcode::V_CMPX_NE_U32},
|
||||
{0xd6u, Opcode::V_CMPX_GE_U32}, {0xe1u, Opcode::V_CMP_LT_U64, false},
|
||||
{0xe0u, Opcode::V_CMP_F_U64, false}, {0xe7u, Opcode::V_CMP_T_U64, false},
|
||||
{0xf0u, Opcode::V_CMPX_F_U64, false}, {0xf1u, Opcode::V_CMPX_LT_U64, false},
|
||||
{0xf2u, Opcode::V_CMPX_EQ_U64, false}, {0xf4u, Opcode::V_CMPX_GT_U64, false},
|
||||
{0xf6u, Opcode::V_CMPX_GE_U64, false}, {0xf7u, Opcode::V_CMPX_T_U64, false},
|
||||
{0xe2u, Opcode::V_CMP_EQ_U64, false}, {0xe3u, Opcode::V_CMP_LE_U64, false},
|
||||
{0xe4u, Opcode::V_CMP_GT_U64, false}, {0xe5u, Opcode::V_CMP_NE_U64, false},
|
||||
{0xe6u, Opcode::V_CMP_GE_U64, false}, {0xf3u, Opcode::V_CMPX_LE_U64, false},
|
||||
@@ -310,6 +319,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x152u, Opcode::V_MIN3_I32},
|
||||
{0x153u, Opcode::V_MIN3_U32},
|
||||
{0x351u, Opcode::V_MIN3_F16},
|
||||
{0x353u, Opcode::V_MIN3_U16},
|
||||
{0x154u, Opcode::V_MAX3_F32},
|
||||
{0x155u, Opcode::V_MAX3_I32},
|
||||
{0x156u, Opcode::V_MAX3_U32},
|
||||
@@ -460,8 +470,9 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
|
||||
opcode == Opcode::V_MED3_F16 || opcode == Opcode::V_FMA_F16;
|
||||
}
|
||||
|
||||
bool IsNativeVop3I16TernaryOpcode(Opcode opcode) {
|
||||
return opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MAD_I16;
|
||||
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
|
||||
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 ||
|
||||
opcode == Opcode::V_MAD_I16;
|
||||
}
|
||||
|
||||
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
|
||||
@@ -568,7 +579,7 @@ struct Vop1SdwaRule {
|
||||
|
||||
constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
|
||||
{Opcode::V_MOV_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(),
|
||||
SdwaSelWords() | SdwaSelFull(), false},
|
||||
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false},
|
||||
{Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_CVT_F32_UBYTE0, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
@@ -919,6 +930,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMPX_NEQ_F16:
|
||||
case Opcode::V_CMPX_NLT_F16:
|
||||
case Opcode::V_CMP_CLASS_F32:
|
||||
case Opcode::V_CMP_CLASS_F16:
|
||||
case Opcode::V_CMPX_CLASS_F16:
|
||||
case Opcode::V_CMPX_CLASS_F32: return true;
|
||||
default: return false;
|
||||
@@ -985,7 +997,8 @@ constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
|
||||
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
|
||||
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
|
||||
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
|
||||
{SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
|
||||
// Bitwise: WriteRawU32 inserts a byte or word destination like any other partial write.
|
||||
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
|
||||
};
|
||||
static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) ==
|
||||
static_cast<size_t>(Vop2SdwaProfile::Count));
|
||||
@@ -1367,7 +1380,7 @@ void ApplyNativeVop3TernaryModifiers(Instruction& inst, uint32_t op_sel, uint32_
|
||||
inst.dst.sdwa_sel = ((op_sel & 0x8u) != 0) ? 5u : 4u;
|
||||
}
|
||||
|
||||
void ApplyNativeVop3I16TernarySelectors(Instruction& inst, uint32_t op_sel) {
|
||||
void ApplyNativeVop3B16TernarySelectors(Instruction& inst, uint32_t op_sel) {
|
||||
Operand* sources[] = {&inst.src0, &inst.src1, &inst.src2};
|
||||
for (uint32_t i = 0; i < 3u; i++) {
|
||||
sources[i]->op_sel = ((op_sel >> i) & 1u) != 0;
|
||||
@@ -1486,7 +1499,7 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
|
||||
// Clamp is applied by WriteF16 for every f16 ternary; omod only for V_FMA_F16.
|
||||
return opcode != Opcode::V_FMA_F16 && omod != 0u;
|
||||
}
|
||||
if (IsNativeVop3I16TernaryOpcode(opcode)) {
|
||||
if (IsNativeVop3B16TernaryOpcode(opcode)) {
|
||||
return abs != 0u || (clamp != 0u && !clamp_modifier) || omod != 0u || neg != 0u;
|
||||
}
|
||||
if (IsNativeVop3B16BinaryOpcode(opcode)) {
|
||||
@@ -1555,9 +1568,20 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GT_U32:
|
||||
case Opcode::V_CMPX_NE_U32:
|
||||
case Opcode::V_CMPX_GE_U32:
|
||||
case Opcode::V_CMPX_F_I64:
|
||||
case Opcode::V_CMPX_LT_I64:
|
||||
case Opcode::V_CMPX_EQ_I64:
|
||||
case Opcode::V_CMPX_LE_I64:
|
||||
case Opcode::V_CMPX_NE_I64:
|
||||
case Opcode::V_CMPX_T_I64:
|
||||
case Opcode::V_CMPX_F_U64:
|
||||
case Opcode::V_CMPX_LT_U64:
|
||||
case Opcode::V_CMPX_EQ_U64:
|
||||
case Opcode::V_CMPX_LE_U64:
|
||||
case Opcode::V_CMPX_GT_U64:
|
||||
case Opcode::V_CMPX_NE_U64:
|
||||
case Opcode::V_CMPX_GE_U64:
|
||||
case Opcode::V_CMPX_T_U64:
|
||||
case Opcode::V_CMPX_LT_I16:
|
||||
case Opcode::V_CMPX_EQ_I16:
|
||||
case Opcode::V_CMPX_LE_I16:
|
||||
@@ -1712,7 +1736,7 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
const bool vop3b_uses_sdst = carry_in_out || vop3b_carry_out || vop3b_mad_u64;
|
||||
const bool mad_mix = false;
|
||||
const bool f16_ternary = IsNativeVop3F16TernaryOpcode(inst.opcode);
|
||||
const bool i16_ternary = IsNativeVop3I16TernaryOpcode(inst.opcode);
|
||||
const bool b16_ternary = IsNativeVop3B16TernaryOpcode(inst.opcode);
|
||||
const bool b16_binary = IsNativeVop3B16BinaryOpcode(inst.opcode);
|
||||
const bool pack_b32_f16 = inst.opcode == Opcode::V_PACK_B32_F16;
|
||||
const bool permlane = IsPermlaneOpcode(inst.opcode);
|
||||
@@ -1823,8 +1847,8 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
ApplyNativeVop3TernaryModifiers(inst, op_sel, abs, neg);
|
||||
} else if (f16_ternary) {
|
||||
ApplyNativeVop3TernaryModifiers(inst, op_sel, abs, neg);
|
||||
} else if (i16_ternary) {
|
||||
ApplyNativeVop3I16TernarySelectors(inst, op_sel);
|
||||
} else if (b16_ternary) {
|
||||
ApplyNativeVop3B16TernarySelectors(inst, op_sel);
|
||||
} else if (b16_binary) {
|
||||
ApplyNativeVop3B16BinaryModifiers(inst, op_sel);
|
||||
} else if (pack_b32_f16) {
|
||||
|
||||
@@ -31,6 +31,7 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
|
||||
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
||||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMP_EQ_I64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMPX_EQ_I64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMP_NE_I64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMPX_NE_I64;
|
||||
const auto read = [&](const Decoder::Operand& operand) {
|
||||
|
||||
@@ -31,11 +31,12 @@ void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
}
|
||||
|
||||
void Translator::V_MED3_I16(const Decoder::Instruction& inst) {
|
||||
void Translator::Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool sign) {
|
||||
const auto result = IR::U32(ir.Emit(
|
||||
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
|
||||
ReadU16AsU32(inst.src2, true)}));
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
opcode, {ReadU16AsU32(inst.src0, sign), ReadU16AsU32(inst.src1, sign),
|
||||
ReadU16AsU32(inst.src2, sign)}));
|
||||
Write16Bits(DestinationOperand(inst), result);
|
||||
}
|
||||
|
||||
void Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
|
||||
@@ -187,7 +187,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
|
||||
if (decoded.family == Decoder::Family::MUBUF || decoded.family == Decoder::Family::MTBUF) {
|
||||
const bool store_or_atomic =
|
||||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_FORMAT_X &&
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW) ||
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X) ||
|
||||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_BYTE &&
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_DWORDX4) ||
|
||||
(decoded.opcode >= Decoder::Opcode::TBUFFER_STORE_FORMAT_X &&
|
||||
@@ -427,7 +427,7 @@ void Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
|
||||
void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
IR::ValueOpcode opcode;
|
||||
const auto bits = memory.data_bits;
|
||||
const auto bits = memory.formatted ? 32u : memory.data_bits;
|
||||
const auto sign = memory.data_signed;
|
||||
switch (bits) {
|
||||
case 8u: opcode = IR::ValueOpcode::LoadBufferU8; break;
|
||||
@@ -473,7 +473,8 @@ void Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
|
||||
const auto data = ReadU32(data_src);
|
||||
IR::ValueOpcode opcode;
|
||||
IR::Value value;
|
||||
switch (memory.data_bits) {
|
||||
// Formatted stores carry packed VGPR bits; the format determines their conversion.
|
||||
switch (memory.formatted ? 32u : memory.data_bits) {
|
||||
case 8u:
|
||||
opcode = IR::ValueOpcode::StoreBufferU8;
|
||||
value = NarrowSubdword(data, 8u);
|
||||
@@ -980,6 +981,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XY:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XYZ:
|
||||
@@ -995,6 +997,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XYZ:
|
||||
|
||||
@@ -134,10 +134,14 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
|
||||
case O::S_ABS_I32:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::IAbs32, IR::Type::U32, false, false, true);
|
||||
case O::S_SEXT_I32_I16:
|
||||
case O::S_SEXT_I32_I8:
|
||||
case O::S_SEXT_I32_I16: {
|
||||
// Sign extension leaves SCC unchanged.
|
||||
const auto bits = inst.opcode == O::S_SEXT_I32_I8 ? 8u : 16u;
|
||||
WriteOperand(inst.dst, IR::U32(ir.Emit(IR::ValueOpcode::BitFieldSExtract,
|
||||
{ReadU32(inst.src0), IR::Value(0u), IR::Value(16u)})));
|
||||
{ReadU32(inst.src0), IR::Value(0u), IR::Value(bits)})));
|
||||
return;
|
||||
}
|
||||
case O::S_MUL_I32:
|
||||
case O::S_MULK_I32:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::IMul32, IR::Type::U32, false, false, false);
|
||||
|
||||
@@ -735,7 +735,7 @@ void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 valu
|
||||
}
|
||||
|
||||
void Translator::AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||
IR::BlockInfo& info) {
|
||||
IR::Block& info) {
|
||||
const auto native_condition = [&](CFG::BranchCondition kind) -> IR::U1 {
|
||||
IR::U1 condition;
|
||||
switch (kind) {
|
||||
@@ -889,9 +889,16 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_
|
||||
case Decoder::Opcode::V_CMP_GT_U64:
|
||||
case Decoder::Opcode::V_CMP_NE_U64:
|
||||
case Decoder::Opcode::V_CMP_GE_U64:
|
||||
case Decoder::Opcode::V_CMPX_LT_I64:
|
||||
case Decoder::Opcode::V_CMPX_EQ_I64:
|
||||
case Decoder::Opcode::V_CMPX_LE_I64:
|
||||
case Decoder::Opcode::V_CMPX_NE_I64:
|
||||
case Decoder::Opcode::V_CMPX_LT_U64:
|
||||
case Decoder::Opcode::V_CMPX_EQ_U64:
|
||||
case Decoder::Opcode::V_CMPX_LE_U64:
|
||||
case Decoder::Opcode::V_CMPX_GT_U64:
|
||||
case Decoder::Opcode::V_CMPX_NE_U64:
|
||||
case Decoder::Opcode::V_CMPX_GE_U64:
|
||||
include_vector(inst.src0, 2u);
|
||||
include_vector(inst.src1, 2u);
|
||||
break;
|
||||
@@ -1016,45 +1023,67 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
|
||||
result.block_storage.reserve(cfg.blocks.size() + 1u);
|
||||
result.blocks.reserve(cfg.blocks.size() + 1u);
|
||||
result.block_info.reserve(cfg.blocks.size() + 1u);
|
||||
const auto max_id = std::ranges::max_element(cfg.blocks, {}, [](const CFG::BasicBlock& block) {
|
||||
return block.id;
|
||||
})->id;
|
||||
if (max_id == UINT32_MAX) {
|
||||
EXIT("cannot allocate a typed entry block id");
|
||||
}
|
||||
CFG::Terminator terminator;
|
||||
terminator.kind = CFG::TerminatorKind::Branch;
|
||||
terminator.true_block = cfg.blocks.front().id;
|
||||
result.block_storage.push_back(std::make_unique<IR::Block>());
|
||||
result.blocks.push_back(result.block_storage.back().get());
|
||||
result.block_info.push_back({max_id + 1u, cfg.blocks.front().start_pc,
|
||||
cfg.blocks.front().start_pc, std::move(terminator)});
|
||||
auto& entry = *result.blocks.front();
|
||||
entry.id = max_id + 1u;
|
||||
entry.start_pc = entry.end_pc = cfg.blocks.front().start_pc;
|
||||
entry.terminator.kind = CFG::TerminatorKind::Branch;
|
||||
|
||||
std::unordered_map<uint32_t, size_t> block_indices;
|
||||
block_indices.reserve(cfg.blocks.size());
|
||||
for (const auto& source_block: cfg.blocks) {
|
||||
if (!block_indices.emplace(source_block.id, result.blocks.size()).second) {
|
||||
EXIT("CFG contains duplicate block id %u", source_block.id);
|
||||
std::unordered_map<uint32_t, IR::Block*> blocks_by_id;
|
||||
blocks_by_id.reserve(cfg.blocks.size());
|
||||
for (const auto& source: cfg.blocks) {
|
||||
auto& block = *result.block_storage.emplace_back(std::make_unique<IR::Block>());
|
||||
if (!blocks_by_id.emplace(source.id, &block).second) {
|
||||
EXIT("CFG contains duplicate block id %u", source.id);
|
||||
}
|
||||
result.block_storage.push_back(std::make_unique<IR::Block>());
|
||||
result.blocks.push_back(result.block_storage.back().get());
|
||||
result.block_info.push_back(
|
||||
{source_block.id, source_block.start_pc, source_block.end_pc, source_block.terminator});
|
||||
result.blocks.push_back(&block);
|
||||
block.id = source.id;
|
||||
block.start_pc = source.start_pc;
|
||||
block.end_pc = source.end_pc;
|
||||
}
|
||||
for (const auto& source_block: cfg.blocks) {
|
||||
const auto source_index = block_indices.at(source_block.id);
|
||||
for (const auto successor: source_block.successors) {
|
||||
const auto target = block_indices.find(successor);
|
||||
if (target == block_indices.end()) {
|
||||
EXIT("CFG block %u has unknown successor %u", source_block.id, successor);
|
||||
const auto target_block = [&](uint32_t id) -> IR::Block* {
|
||||
return id == UINT32_MAX ? nullptr : blocks_by_id.at(id);
|
||||
};
|
||||
entry.terminator.true_block = target_block(cfg.blocks.front().id);
|
||||
for (const auto& source: cfg.blocks) {
|
||||
auto& block = *blocks_by_id.at(source.id);
|
||||
const auto& term = source.terminator;
|
||||
block.terminator.kind = term.kind;
|
||||
block.terminator.true_block = target_block(term.true_block);
|
||||
block.terminator.false_block = target_block(term.false_block);
|
||||
block.terminator.merge_block = target_block(term.merge_block);
|
||||
block.terminator.continue_block = target_block(term.continue_block);
|
||||
block.terminator.loop_header = term.loop_header;
|
||||
block.terminator.indexed = term.indirect_selector_code != UINT32_MAX;
|
||||
const auto& values =
|
||||
block.terminator.indexed ? term.indirect_selector_values : term.indirect_target_pcs;
|
||||
const auto& targets =
|
||||
block.terminator.indexed ? term.indirect_selector_targets : term.indirect_targets;
|
||||
if (values.size() != targets.size()) {
|
||||
EXIT("CFG block %u has inconsistent indirect targets", source.id);
|
||||
}
|
||||
block.terminator.cases.reserve(values.size());
|
||||
for (size_t index = 0; index < values.size(); ++index) {
|
||||
block.terminator.cases.push_back({values[index], target_block(targets[index])});
|
||||
}
|
||||
for (const auto successor: source.successors) {
|
||||
const auto target = blocks_by_id.find(successor);
|
||||
if (target == blocks_by_id.end()) {
|
||||
EXIT("CFG block %u has unknown successor %u", source.id, successor);
|
||||
}
|
||||
result.blocks[source_index]->AddBranch(result.blocks[target->second]);
|
||||
block.AddBranch(target->second);
|
||||
}
|
||||
}
|
||||
{
|
||||
result.blocks.front()->AddBranch(result.blocks.at(block_indices.at(cfg.blocks.front().id)));
|
||||
IR::IREmitter entry_ir(result.blocks.front());
|
||||
entry.AddBranch(entry.terminator.true_block);
|
||||
IR::IREmitter entry_ir(&entry);
|
||||
const auto builtin = [&](IR::StageInputKind kind, uint32_t component = 0u) {
|
||||
return IR::U32(
|
||||
entry_ir.Emit(IR::ValueOpcode::GetBuiltin,
|
||||
@@ -1130,7 +1159,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
} else if (options.stage == ShaderType::Mesh) {
|
||||
const auto& mesh = options.input_info.vertex->mesh;
|
||||
EXIT_NOT_IMPLEMENTED(mesh.primitives_per_group == 0u || mesh.vertices_per_group > 64u ||
|
||||
EXIT_NOT_IMPLEMENTED(mesh.primitives_per_group == 0u || mesh.vertices_per_group > 0x1ffu ||
|
||||
mesh.primitives_per_group > 0x1ffu ||
|
||||
total_threads > 15u * options.wave_size);
|
||||
const auto u32 = [](uint32_t value) { return IR::U32(IR::Value(value)); };
|
||||
const auto draw = [&](uint32_t index) {
|
||||
@@ -1166,6 +1196,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
const auto wave_info = entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(wave, u32(24)),
|
||||
u32(((total_threads + options.wave_size - 1u) /
|
||||
options.wave_size) << 28u));
|
||||
entry_ir.SetScalarReg(
|
||||
static_cast<IR::ScalarReg>(2),
|
||||
entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(vertices, u32(12)),
|
||||
entry_ir.ShiftLeftLogical(primitives, u32(22))));
|
||||
entry_ir.SetScalarReg(
|
||||
static_cast<IR::ScalarReg>(3),
|
||||
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
||||
@@ -1274,6 +1308,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
};
|
||||
barycentric_pair(ps->ps_perspective_center_vgpr, IR::StageInputKind::BaryCoordSmooth);
|
||||
barycentric_pair(ps->ps_perspective_sample_vgpr,
|
||||
IR::StageInputKind::BaryCoordSmoothSample);
|
||||
barycentric_pair(ps->ps_perspective_centroid_vgpr,
|
||||
IR::StageInputKind::BaryCoordSmoothCentroid);
|
||||
uint32_t reg = ps->ps_system_input_base;
|
||||
@@ -1317,10 +1353,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
}
|
||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||
(options.input_info.compute->float_mode & 0x10u) == 0;
|
||||
(options.input_info.compute->float_mode & 0x10u) == 0;
|
||||
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, flush_f32_inputs);
|
||||
auto* block = blocks_by_id.at(cfg_block.id);
|
||||
Translator translator(result, block, vector_limit, flush_f32_inputs);
|
||||
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)) {
|
||||
@@ -1343,7 +1379,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
translator.TranslateInstruction(instruction);
|
||||
}
|
||||
translator.AddBranchCondition(cfg, cfg_block, result.block_info[typed_index]);
|
||||
translator.AddBranchCondition(cfg, cfg_block, *block);
|
||||
}
|
||||
IR::ValidateProgram(result, false);
|
||||
return result;
|
||||
|
||||
@@ -17,7 +17,8 @@ public:
|
||||
void TranslateInstruction(const Decoder::Instruction& inst);
|
||||
void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
uint32_t component_count, const ShaderBufferResource& resource);
|
||||
void AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source, IR::BlockInfo& info);
|
||||
void AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||
IR::Block& info);
|
||||
|
||||
private:
|
||||
const Decoder::Operand& SourceAt(const Decoder::Instruction& inst, uint32_t index);
|
||||
@@ -166,8 +167,8 @@ private:
|
||||
void FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
|
||||
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void V_MAD_I16(const Decoder::Instruction& inst);
|
||||
void V_MED3_I16(const Decoder::Instruction& inst);
|
||||
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic);
|
||||
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
|
||||
@@ -96,16 +96,34 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMPX_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
|
||||
return;
|
||||
case O::V_CMP_F_I64:
|
||||
case O::V_CMP_F_U64: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_T_I64:
|
||||
case O::V_CMP_T_U64: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMPX_F_I64:
|
||||
case O::V_CMPX_F_U64: EmitCompareConstant(inst, false, false, true); return;
|
||||
case O::V_CMPX_T_I64:
|
||||
case O::V_CMPX_T_U64: EmitCompareConstant(inst, true, false, true); return;
|
||||
case O::V_CMP_EQ_I64:
|
||||
case O::V_CMP_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_I64:
|
||||
case O::V_CMPX_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_GT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_NE_I64:
|
||||
case O::V_CMP_NE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false);
|
||||
@@ -118,9 +136,15 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMP_LT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
@@ -130,6 +154,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMP_GE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, true);
|
||||
return;
|
||||
|
||||
case O::V_CMP_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, false);
|
||||
@@ -347,6 +374,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false, true); return;
|
||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
|
||||
case O::V_CMP_CLASS_F16: EmitFloatClassCompare(inst, false, true); return;
|
||||
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true, true); return;
|
||||
|
||||
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return;
|
||||
@@ -395,7 +423,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
|
||||
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
|
||||
case O::V_MAD_I16: return V_MAD_I16(inst);
|
||||
case O::V_MED3_I16: return V_MED3_I16(inst);
|
||||
case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
|
||||
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
|
||||
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
|
||||
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
|
||||
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/ir/Reg.h"
|
||||
#include "graphics/shader/recompiler/ir/Value.h"
|
||||
|
||||
@@ -37,6 +38,8 @@ public:
|
||||
[[nodiscard]] const InstructionList& Instructions() const;
|
||||
[[nodiscard]] std::span<Block* const> ImmPredecessors() const;
|
||||
[[nodiscard]] std::span<Block* const> ImmSuccessors() const;
|
||||
[[nodiscard]] uint32_t Definition() const { return definition; }
|
||||
void SetDefinition(uint32_t value) const { definition = value; }
|
||||
|
||||
void SsaSeal();
|
||||
[[nodiscard]] bool IsSsaSealed() const;
|
||||
@@ -47,6 +50,27 @@ public:
|
||||
const_iterator end() const;
|
||||
bool empty() const;
|
||||
|
||||
struct SwitchCase {
|
||||
uint32_t value;
|
||||
Block* target;
|
||||
};
|
||||
struct Terminator {
|
||||
CFG::TerminatorKind kind = CFG::TerminatorKind::Return;
|
||||
Block* true_block = nullptr;
|
||||
Block* false_block = nullptr;
|
||||
Block* merge_block = nullptr;
|
||||
Block* continue_block = nullptr;
|
||||
std::vector<SwitchCase> cases;
|
||||
bool indexed = false;
|
||||
bool loop_header = false;
|
||||
};
|
||||
uint32_t id = 0;
|
||||
uint32_t start_pc = 0;
|
||||
uint32_t end_pc = 0;
|
||||
Terminator terminator;
|
||||
Value condition;
|
||||
Value indirect_target;
|
||||
|
||||
std::array<Value, NumScalarRegs> ssa_sreg_values {};
|
||||
std::array<Value, NumScalarRegs> ssa_thread_bit_sreg_values {};
|
||||
std::array<Value, NumScalarRegs> ssa_sreg_mask_tags {};
|
||||
@@ -57,6 +81,7 @@ private:
|
||||
std::vector<Block*> predecessors;
|
||||
std::vector<Block*> successors;
|
||||
bool ssa_sealed = false;
|
||||
mutable uint32_t definition = 0;
|
||||
};
|
||||
|
||||
using BlockList = std::vector<Block*>;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
@@ -202,6 +203,16 @@ Value ResolveActiveU32(Value value, Value active) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t StorageBufferElementBits(const Program& program, const MemoryInfo& memory) {
|
||||
if (!memory.formatted) return memory.data_bits;
|
||||
const auto format = memory.typed
|
||||
? Format::DecodeTBufferFormat(memory.data_format, memory.number_format)
|
||||
: program.info.buffers[memory.resource].descriptor_format;
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
return info.type == Format::ComponentType::Unknown || info.packed_bitfield
|
||||
? 32u : info.component_bits[0];
|
||||
}
|
||||
|
||||
bool HasShaderMemoryWrites(const Program& program) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
@@ -219,12 +230,11 @@ bool HasShaderMemoryWrites(const Program& program) {
|
||||
}
|
||||
|
||||
void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
if (program.blocks.size() != program.block_info.size() ||
|
||||
program.blocks.size() != program.block_storage.size()) {
|
||||
if (program.blocks.size() != program.block_storage.size()) {
|
||||
return Fail("value IR block storage is inconsistent");
|
||||
}
|
||||
std::unordered_map<const Block*, size_t> block_indices;
|
||||
std::unordered_map<uint32_t, const Block*> blocks_by_id;
|
||||
std::unordered_set<uint32_t> block_ids;
|
||||
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];
|
||||
@@ -235,10 +245,10 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
if (!block_indices.emplace(block, block_index).second) {
|
||||
return Fail("value IR block pointer is duplicated");
|
||||
}
|
||||
if (program.block_info[block_index].id == UINT32_MAX) {
|
||||
if (block->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) {
|
||||
if (!block_ids.insert(block->id).second) {
|
||||
return Fail("value IR block id is duplicated");
|
||||
}
|
||||
size_t position = 0;
|
||||
@@ -283,15 +293,14 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
}
|
||||
|
||||
std::unordered_set<const Block*> expected_successors;
|
||||
const auto add_target = [&](uint32_t id) {
|
||||
const auto found = blocks_by_id.find(id);
|
||||
if (found == blocks_by_id.end()) {
|
||||
const auto add_target = [&](const Block* target) {
|
||||
if (!block_indices.contains(target)) {
|
||||
return false;
|
||||
}
|
||||
expected_successors.insert(found->second);
|
||||
expected_successors.insert(target);
|
||||
return true;
|
||||
};
|
||||
const auto& terminator = program.block_info[block_index].terminator;
|
||||
const auto& terminator = block->terminator;
|
||||
const auto validate_control_value = [&](Value value, Type type) {
|
||||
if (value.IsEmpty() || value.GetType() != type) {
|
||||
return false;
|
||||
@@ -309,44 +318,32 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
if (!add_target(terminator.true_block) || !add_target(terminator.false_block)) {
|
||||
return Fail("value IR conditional branch target is missing");
|
||||
}
|
||||
if (!validate_control_value(program.block_info[block_index].condition, Type::U1)) {
|
||||
if (!validate_control_value(block->condition, Type::U1)) {
|
||||
return Fail("value IR conditional branch condition is invalid");
|
||||
}
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
if (!validate_control_value(program.block_info[block_index].indirect_target,
|
||||
Type::U32)) {
|
||||
if (!validate_control_value(block->indirect_target, Type::U32)) {
|
||||
return Fail("value IR indirect branch selector is invalid");
|
||||
}
|
||||
std::unordered_set<uint32_t> indirect_targets;
|
||||
for (const auto target: terminator.indirect_targets) {
|
||||
if (!indirect_targets.insert(target).second) {
|
||||
return Fail("value IR indirect branch target is duplicated");
|
||||
std::unordered_set<uint32_t> values;
|
||||
for (const auto& branch: terminator.cases) {
|
||||
if (!values.insert(branch.value).second) {
|
||||
return Fail("value IR indirect selector value is duplicated");
|
||||
}
|
||||
if (!add_target(target)) {
|
||||
if (!add_target(branch.target)) {
|
||||
return Fail("value IR indirect branch target is missing");
|
||||
}
|
||||
}
|
||||
if (terminator.indirect_selector_values.size() !=
|
||||
terminator.indirect_selector_targets.size()) {
|
||||
return Fail("value IR indirect selector table is inconsistent");
|
||||
}
|
||||
for (const auto target: terminator.indirect_selector_targets) {
|
||||
const auto found = blocks_by_id.find(target);
|
||||
if (found == blocks_by_id.end() ||
|
||||
!expected_successors.contains(found->second)) {
|
||||
return Fail("value IR indirect selector target is not a CFG successor");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CFG::TerminatorKind::Return:
|
||||
case CFG::TerminatorKind::Unsupported: break;
|
||||
}
|
||||
if ((terminator.merge_block != UINT32_MAX &&
|
||||
!blocks_by_id.contains(terminator.merge_block)) ||
|
||||
(terminator.continue_block != UINT32_MAX &&
|
||||
!blocks_by_id.contains(terminator.continue_block))) {
|
||||
if ((terminator.merge_block != nullptr &&
|
||||
!block_indices.contains(terminator.merge_block)) ||
|
||||
(terminator.continue_block != nullptr &&
|
||||
!block_indices.contains(terminator.continue_block))) {
|
||||
return Fail("value IR structured control target is missing");
|
||||
}
|
||||
if (successors != expected_successors) {
|
||||
@@ -653,7 +650,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& info = program.block_info[block_index];
|
||||
const auto& info = *program.blocks[block_index];
|
||||
if (!info.condition.IsEmpty() && !control_dominates(info.condition, block)) {
|
||||
return Fail("value IR branch condition definition does not dominate its use");
|
||||
}
|
||||
@@ -664,9 +661,9 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
}
|
||||
|
||||
void ResolveControlFlowIdentities(Program& program) {
|
||||
for (auto& info: program.block_info) {
|
||||
info.condition = info.condition.Resolve();
|
||||
info.indirect_target = info.indirect_target.Resolve();
|
||||
for (auto* block: program.blocks) {
|
||||
block->condition = block->condition.Resolve();
|
||||
block->indirect_target = block->indirect_target.Resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,9 +698,9 @@ std::string ProgramToString(const Program& program) {
|
||||
|
||||
std::string text;
|
||||
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
||||
text += fmt::format("Block ${} pc=0x{:08x}..0x{:08x}\n", block_index,
|
||||
program.block_info[block_index].start_pc,
|
||||
program.block_info[block_index].end_pc);
|
||||
text +=
|
||||
fmt::format("Block ${} pc=0x{:08x}..0x{:08x}\n", block_index,
|
||||
program.blocks[block_index]->start_pc, program.blocks[block_index]->end_pc);
|
||||
for (const auto& inst: *program.blocks[block_index]) {
|
||||
const auto type = inst.GetType();
|
||||
if (type != Type::Void) {
|
||||
|
||||
@@ -104,6 +104,7 @@ struct BufferResource {
|
||||
static constexpr uint32_t NoIndirectBuffer = UINT32_MAX;
|
||||
|
||||
uint32_t source = 0;
|
||||
uint32_t descriptor_index = UINT32_MAX;
|
||||
uint32_t first_use_pc = 0;
|
||||
uint32_t max_byte_extent = 0;
|
||||
uint32_t packed_stride = 0;
|
||||
@@ -131,6 +132,7 @@ struct ImageResource {
|
||||
static constexpr uint32_t NoIndirectImage = UINT32_MAX;
|
||||
|
||||
uint32_t source = 0;
|
||||
uint32_t descriptor_index = UINT32_MAX;
|
||||
uint32_t first_use_pc = 0;
|
||||
ImageResourceClass resource_class = ImageResourceClass::None;
|
||||
Prospero::TextureNumericClass numeric_class = Prospero::TextureNumericClass::Unsupported;
|
||||
@@ -196,6 +198,7 @@ enum class StageInputKind {
|
||||
Layer,
|
||||
SampleId,
|
||||
BaryCoordSmooth,
|
||||
BaryCoordSmoothSample,
|
||||
BaryCoordSmoothCentroid,
|
||||
BaryCoordNoPerspective,
|
||||
WorkgroupId,
|
||||
@@ -423,23 +426,16 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
return static_cast<DescriptorBindingKind>(base + dimension);
|
||||
}
|
||||
|
||||
struct DescriptorBinding {
|
||||
DescriptorBindingKind kind = DescriptorBindingKind::Buffers;
|
||||
std::vector<uint32_t> resources;
|
||||
|
||||
bool operator==(const DescriptorBinding& other) const = default;
|
||||
};
|
||||
|
||||
struct BindingLayout {
|
||||
uint32_t push_data_start_dword = PushData::NoStart;
|
||||
uint32_t dispatch_thread_dword = PushData::NoStart;
|
||||
uint32_t memory_offset_dword = 0;
|
||||
uint32_t memory_offset_count = 0;
|
||||
std::vector<uint32_t> user_data_registers;
|
||||
std::vector<DescriptorBinding> descriptors;
|
||||
struct Bindings {
|
||||
std::array<uint32_t, static_cast<size_t>(DescriptorBindingKind::Count)> descriptor_counts {};
|
||||
uint64_t descriptor_mask = 0;
|
||||
uint32_t push_data_start_dword = PushData::NoStart;
|
||||
uint32_t dispatch_thread_dword = PushData::NoStart;
|
||||
uint32_t memory_offset_dword = 0;
|
||||
|
||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||
return memory_offset_dword + (memory_offset_count + 3u) / 4u;
|
||||
return memory_offset_dword +
|
||||
(descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)] + 3u) / 4u;
|
||||
}
|
||||
[[nodiscard]] bool UsesPushData() const {
|
||||
return push_data_start_dword != PushData::NoStart;
|
||||
@@ -450,7 +446,7 @@ struct BindingLayout {
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const BindingLayout& other) const = default;
|
||||
bool operator==(const Bindings& other) const = default;
|
||||
};
|
||||
|
||||
struct ShaderInfo {
|
||||
@@ -465,24 +461,38 @@ struct ShaderInfo {
|
||||
std::vector<SampledResourcePair> sampled_pairs;
|
||||
std::vector<StageInput> inputs;
|
||||
std::vector<StageOutput> outputs;
|
||||
std::vector<uint32_t> user_data_registers;
|
||||
uint64_t live_buffers = 0;
|
||||
std::array<uint8_t, 32> vertex_fetch_components {};
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
int32_t instance_offset_sgpr = -1;
|
||||
bool has_bitwise_xor = false;
|
||||
bool uses_dma = false;
|
||||
bool has_bitwise_xor = false;
|
||||
bool uses_dma = false;
|
||||
bool uses_swizzle = false;
|
||||
bool uses_lds = false;
|
||||
bool uses_gds = false;
|
||||
bool uses_flattened_srt = false;
|
||||
bool uses_dispatch_threads = false;
|
||||
bool bvh = false;
|
||||
bool subgroup_ballot = false;
|
||||
bool subgroup_barrier = false;
|
||||
bool subgroup_shuffle = false;
|
||||
bool subgroup_local_invocation_id = false;
|
||||
bool compute_derivatives = false;
|
||||
bool image_gather_extended = false;
|
||||
bool function_lds = false;
|
||||
bool function_scratch = false;
|
||||
bool pixel_valid_mask = false;
|
||||
bool buffer_int64_atomics = false;
|
||||
bool buffer_u8 = false;
|
||||
bool buffer_u16 = false;
|
||||
bool shared_int64_atomics = false;
|
||||
bool coherent_buffers = false;
|
||||
bool float64 = false;
|
||||
|
||||
bool operator==(const ShaderInfo& other) const = default;
|
||||
};
|
||||
|
||||
struct BlockInfo {
|
||||
uint32_t id = 0;
|
||||
uint32_t start_pc = 0;
|
||||
uint32_t end_pc = 0;
|
||||
CFG::Terminator terminator;
|
||||
Value condition;
|
||||
Value indirect_target;
|
||||
};
|
||||
|
||||
struct DescriptorSource {
|
||||
struct IndirectDescriptor {
|
||||
struct SelectorRead {
|
||||
@@ -541,7 +551,7 @@ struct CompiledShaderInfo {
|
||||
uint32_t param_export_mask = 0;
|
||||
bool has_address_writes = false;
|
||||
ShaderInfo info;
|
||||
BindingLayout bindings;
|
||||
Bindings bindings;
|
||||
};
|
||||
|
||||
struct UniformFillPlan {
|
||||
@@ -579,7 +589,6 @@ struct ResourcePlan {
|
||||
std::vector<DescriptorSource> descriptor_sources;
|
||||
std::vector<ResourceBlock> control_flow;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
std::vector<uint8_t> clean_flat_slots;
|
||||
bool requires_specialization_memory = false;
|
||||
bool capture_specialization_reads = false;
|
||||
bool srt_plan_complete = false;
|
||||
@@ -613,7 +622,6 @@ struct Program: ResourcePlan {
|
||||
bool dispatcher_fallback = false;
|
||||
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
|
||||
std::string fallback_reason;
|
||||
std::vector<BlockInfo> block_info;
|
||||
struct ScalarWrite { uint32_t pc; ScalarReg reg; };
|
||||
std::vector<ScalarWrite> scalar_writes;
|
||||
// Typed memory and export instructions reference shader-local metadata by dense index.
|
||||
@@ -621,11 +629,12 @@ struct Program: ResourcePlan {
|
||||
std::vector<ExportInfo> export_info;
|
||||
bool has_address_writes = false;
|
||||
bool shader_info_complete = false;
|
||||
BindingLayout bindings;
|
||||
bool binding_layout_complete = false;
|
||||
Bindings bindings;
|
||||
|
||||
};
|
||||
|
||||
uint32_t StorageBufferElementBits(const Program& program, const MemoryInfo& memory);
|
||||
|
||||
std::string ProgramToString(const Program& program);
|
||||
bool HasShaderMemoryWrites(const Program& program);
|
||||
|
||||
|
||||
@@ -129,6 +129,12 @@ public:
|
||||
}
|
||||
return evaluation_index;
|
||||
}
|
||||
// Backend definitions belong to one emission. Wave64 emulation needs one per half.
|
||||
[[nodiscard]] uint32_t Definition(uint32_t half) const { return definitions[half]; }
|
||||
void SetDefinition(uint32_t half, uint32_t definition) const {
|
||||
definitions[half] = definition;
|
||||
}
|
||||
void ResetDefinitions() const { definitions = {}; }
|
||||
|
||||
void SetParent(Block* block);
|
||||
void SetArg(size_t index, Value value);
|
||||
@@ -165,6 +171,7 @@ private:
|
||||
uint8_t num_args;
|
||||
bool live = false;
|
||||
mutable uint32_t evaluation_index = UINT32_MAX;
|
||||
mutable std::array<uint32_t, 2> definitions {};
|
||||
uint64_t flags;
|
||||
Block* parent = nullptr;
|
||||
union {
|
||||
@@ -175,6 +182,6 @@ private:
|
||||
std::vector<Use> uses;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Inst) <= 112, "Inst operand storage unintentionally increased");
|
||||
static_assert(sizeof(Inst) <= 120, "Inst operand and backend storage unintentionally increased");
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -1,197 +0,0 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void BindingFail(const char* message) {
|
||||
EXIT("shader binding layout failed: %s", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void CollectShaderData(const Program& program, BindingLayout& layout) {
|
||||
std::array<bool, NumScalarRegs> registers {};
|
||||
bool uses_dispatch_threads = false;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (!inst.HasUses()) {
|
||||
continue;
|
||||
}
|
||||
uses_dispatch_threads |= inst.GetOpcode() == ValueOpcode::GetDispatchThreadExtent;
|
||||
if (inst.GetOpcode() != ValueOpcode::GetUserData) {
|
||||
continue;
|
||||
}
|
||||
if (inst.Arg(0).GetType() != Type::ScalarReg) {
|
||||
BindingFail("typed shader contains an invalid user-data register");
|
||||
}
|
||||
const auto index = RegIndex(inst.Arg(0).ScalarRegister());
|
||||
if (index >= NumScalarRegs) {
|
||||
BindingFail("typed shader contains an invalid user-data register");
|
||||
}
|
||||
registers[index] = true;
|
||||
}
|
||||
}
|
||||
for (uint32_t index = 0; index < registers.size(); index++) {
|
||||
if (registers[index]) {
|
||||
layout.user_data_registers.push_back(index);
|
||||
}
|
||||
}
|
||||
layout.memory_offset_dword = static_cast<uint32_t>(layout.user_data_registers.size());
|
||||
if (uses_dispatch_threads) {
|
||||
layout.dispatch_thread_dword = layout.memory_offset_dword;
|
||||
layout.memory_offset_dword += 3u;
|
||||
}
|
||||
}
|
||||
|
||||
void AddBinding(BindingLayout& layout, DescriptorBindingKind kind,
|
||||
std::vector<uint32_t> resources = {}) {
|
||||
layout.descriptors.push_back({kind, std::move(resources)});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SharedMemoryResources CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffers) {
|
||||
std::array<bool, ShaderInfo::MaxBuffers> live_buffers {};
|
||||
SharedMemoryResources shared;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (BufferAccessOf(op) == BufferAccess::None &&
|
||||
SharedAccessOf(op) == SharedAccess::None &&
|
||||
AddressOpcodeInfoOf(op).access == AddressAccess::None) {
|
||||
continue;
|
||||
}
|
||||
const auto index = inst.Flags<MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
BindingFail("typed shader contains invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[index];
|
||||
if (memory.planning_only) {
|
||||
continue;
|
||||
}
|
||||
shared.lds |= memory.kind == ResourceKind::FlatLocal;
|
||||
if (SharedAccessOf(op) != SharedAccess::None) {
|
||||
if (memory.kind != ResourceKind::Lds && memory.kind != ResourceKind::Gds) {
|
||||
BindingFail("typed shader contains invalid shared-memory metadata");
|
||||
}
|
||||
shared.gds |= memory.kind == ResourceKind::Gds;
|
||||
shared.lds |= memory.kind == ResourceKind::Lds;
|
||||
} else if (memory.kind == ResourceKind::Buffer || memory.kind == ResourceKind::ScalarBuffer) {
|
||||
EXIT_IF(memory.resource >= program.info.buffers.size());
|
||||
live_buffers.at(memory.resource) = true;
|
||||
for (const auto child: program.info.buffers[memory.resource].indirect_resources) {
|
||||
live_buffers.at(child) = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
if (live_buffers.at(i)) {
|
||||
buffers.push_back(i);
|
||||
}
|
||||
}
|
||||
return shared;
|
||||
}
|
||||
|
||||
bool UsesFlattenedSrt(const Program& program) {
|
||||
const auto uses_mapping = [](const auto& resource) {
|
||||
return resource.indirect_root != UINT32_MAX;
|
||||
};
|
||||
return std::ranges::any_of(program.blocks, [](const Block* block) {
|
||||
return std::ranges::any_of(*block, [](const Inst& inst) {
|
||||
return inst.GetOpcode() == ValueOpcode::ReadConst;
|
||||
});
|
||||
}) || std::ranges::any_of(program.info.buffers, uses_mapping) ||
|
||||
std::ranges::any_of(program.info.images, uses_mapping);
|
||||
}
|
||||
|
||||
void AllocateBindings(Program& program, uint32_t push_data_start_dword, bool lds_storage) {
|
||||
if (!program.shader_info_complete || program.binding_layout_complete) {
|
||||
EXIT("shader binding layout failed: %s", !program.shader_info_complete
|
||||
? "shader info is not ready"
|
||||
: "binding layout already allocated");
|
||||
}
|
||||
BindingLayout next;
|
||||
std::vector<uint32_t> buffers;
|
||||
const auto shared = CollectMemoryResources(program, buffers);
|
||||
CollectShaderData(program, next);
|
||||
next.memory_offset_count = static_cast<uint32_t>(buffers.size());
|
||||
next.push_data_start_dword =
|
||||
PushData::StartFor(push_data_start_dword, next.ShaderDataDwords());
|
||||
|
||||
if (!buffers.empty()) {
|
||||
// Draw binding accesses this first group directly when memory_offset_count is nonzero.
|
||||
AddBinding(next, DescriptorBindingKind::Buffers, std::move(buffers));
|
||||
}
|
||||
|
||||
std::array<std::vector<uint32_t>, ImageBindingCount> image_groups;
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = DescriptorBindingForImage(program.info.images[i]);
|
||||
if (!kind.has_value()) {
|
||||
EXIT("shader binding layout failed: image %u has an invalid binding class", i);
|
||||
}
|
||||
const auto group = ImageBindingIndex(*kind);
|
||||
if (group >= image_groups.size()) {
|
||||
EXIT("shader binding layout failed: image %u has an unmapped binding class", i);
|
||||
}
|
||||
auto& resources = image_groups[group];
|
||||
const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::Dynamic;
|
||||
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
EXIT("shader binding layout failed: image %u has invalid specialized mip count %u", i,
|
||||
program.info.images[i].mip_count);
|
||||
}
|
||||
resources.insert(resources.end(), count, i);
|
||||
}
|
||||
for (uint32_t i = 0; i < image_groups.size(); i++) {
|
||||
if (!image_groups[i].empty()) {
|
||||
AddBinding(next, static_cast<DescriptorBindingKind>(FirstImageBinding + i),
|
||||
std::move(image_groups[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.info.samplers.empty()) {
|
||||
std::vector<uint32_t> resources(program.info.samplers.size());
|
||||
for (uint32_t i = 0; i < resources.size(); i++) {
|
||||
resources[i] = i;
|
||||
}
|
||||
AddBinding(next, DescriptorBindingKind::Samplers, std::move(resources));
|
||||
}
|
||||
if (shared.gds) {
|
||||
AddBinding(next, DescriptorBindingKind::Gds);
|
||||
}
|
||||
if (shared.lds && lds_storage) {
|
||||
AddBinding(next, DescriptorBindingKind::SharedMemory);
|
||||
}
|
||||
if (program.info.uses_dma) {
|
||||
AddBinding(next, DescriptorBindingKind::BdaPagetable);
|
||||
AddBinding(next, DescriptorBindingKind::FaultBuffer);
|
||||
}
|
||||
if (UsesFlattenedSrt(program)) {
|
||||
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
|
||||
}
|
||||
|
||||
if (next.ShaderDataDwords() != 0 && !next.UsesPushData()) {
|
||||
AddBinding(next, DescriptorBindingKind::ShaderData);
|
||||
}
|
||||
|
||||
program.bindings = std::move(next);
|
||||
program.binding_layout_complete = true;
|
||||
}
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind) {
|
||||
for (const auto& binding: layout.descriptors) {
|
||||
if (binding.kind == kind) {
|
||||
return &binding;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
void AllocateBindings(Program& program, uint32_t push_data_start_dword = 0,
|
||||
bool lds_storage = false);
|
||||
|
||||
struct SharedMemoryResources {
|
||||
bool lds = false;
|
||||
bool gds = false;
|
||||
};
|
||||
|
||||
SharedMemoryResources CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffers);
|
||||
bool UsesFlattenedSrt(const Program& program);
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_ */
|
||||
@@ -215,6 +215,24 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
return;
|
||||
case ValueOpcode::SelectU32:
|
||||
case ValueOpcode::SelectF32: FoldSelect(inst); return;
|
||||
case ValueOpcode::FPCmpClass32: {
|
||||
const auto mask = Arg(inst, 1);
|
||||
if (!IsImmediate(mask, Type::U32)) {
|
||||
return;
|
||||
}
|
||||
switch (mask.U32() & 0x3ffu) {
|
||||
case 0u: Replace(inst, Value(false)); break;
|
||||
case 0x3ffu: Replace(inst, Value(true)); break;
|
||||
case 3u: {
|
||||
const auto result = block.PrependNewInst(
|
||||
instruction, ValueOpcode::FPIsNan32, {Arg(inst, 0)});
|
||||
Replace(inst, Value(&*result));
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ValueOpcode::BitFieldInsert: {
|
||||
const auto base = Arg(inst, 0);
|
||||
const auto insert = Arg(inst, 1);
|
||||
|
||||
@@ -173,32 +173,28 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
|
||||
return &program.descriptor_sources[source];
|
||||
}
|
||||
|
||||
void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* source,
|
||||
std::span<uint8_t> slots, Value extra = {}) {
|
||||
void MarkCleanReads(const std::unordered_set<const Inst*>& planned_reads,
|
||||
const DescriptorSource* source, Value extra = {}) {
|
||||
if (source == nullptr && extra.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
std::vector<Value> pending;
|
||||
std::vector<Value> pending;
|
||||
if (source != nullptr) {
|
||||
pending.assign(source->dwords.begin(), source->dwords.begin() + source->dword_count);
|
||||
}
|
||||
if (!extra.IsEmpty()) pending.push_back(extra);
|
||||
std::vector<const Inst*> visited;
|
||||
while (!pending.empty()) {
|
||||
auto value = pending.back().Resolve();
|
||||
auto* inst = pending.back().Resolve().TryInstruction();
|
||||
pending.pop_back();
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr || std::ranges::find(visited, inst) != visited.end()) {
|
||||
continue;
|
||||
}
|
||||
visited.push_back(inst);
|
||||
if (inst->GetOpcode() == ValueOpcode::ReadConst) {
|
||||
const auto slot = inst->Arg(1).Resolve();
|
||||
if (slot.IsImmediate() && slot.GetType() == Type::U32 && slot.U32() < slots.size()) {
|
||||
slots[slot.U32()] = 1u;
|
||||
pending.push_back(program.srt_reads[slot.U32()].value);
|
||||
}
|
||||
continue;
|
||||
if (planned_reads.contains(inst)) {
|
||||
auto flags = inst->Flags<SrtReadFlags>();
|
||||
flags.clean = 1u;
|
||||
inst->SetFlags(flags);
|
||||
}
|
||||
for (size_t arg = 0; arg < inst->NumArgs(); arg++) {
|
||||
pending.push_back(inst->Arg(arg));
|
||||
@@ -684,30 +680,29 @@ bool BuildSamplerPlan(const ShaderInfo& base, SamplerPlan& plan) {
|
||||
template <typename Predicate>
|
||||
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, const Predicate& predicate) {
|
||||
// The renderer checks captured scalar reads against final buffer and image write ranges.
|
||||
if (program.blocks.size() != program.block_info.size() || program.has_address_writes) {
|
||||
if (program.has_address_writes) {
|
||||
return {};
|
||||
}
|
||||
std::unordered_map<uint32_t, uint32_t> indices;
|
||||
for (uint32_t i = 0; i < program.block_info.size(); i++) {
|
||||
if (!indices.emplace(program.block_info[i].id, i).second) {
|
||||
std::unordered_map<const Block*, uint32_t> indices;
|
||||
for (uint32_t i = 0; i < program.blocks.size(); i++) {
|
||||
if (!indices.emplace(program.blocks[i], i).second) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
std::vector<ResourceBlock> blocks(program.blocks.size());
|
||||
for (uint32_t i = 0; i < blocks.size(); i++) {
|
||||
auto& block = blocks[i];
|
||||
const auto& info = program.block_info[i];
|
||||
const auto& info = *program.blocks[i];
|
||||
const auto& terminator = info.terminator;
|
||||
std::vector<uint32_t> successors;
|
||||
const std::array targets {terminator.true_block, terminator.false_block};
|
||||
std::span<Block* const> successors;
|
||||
switch (terminator.kind) {
|
||||
case CFG::TerminatorKind::Branch: successors.push_back(terminator.true_block); break;
|
||||
case CFG::TerminatorKind::Branch: successors = std::span(targets).first(1); break;
|
||||
case CFG::TerminatorKind::ConditionalBranch:
|
||||
successors = {terminator.true_block, terminator.false_block};
|
||||
successors = targets;
|
||||
block.condition = info.condition;
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch:
|
||||
successors = terminator.indirect_targets;
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch: successors = info.ImmSuccessors(); break;
|
||||
case CFG::TerminatorKind::Return: break;
|
||||
default: return {};
|
||||
}
|
||||
@@ -828,17 +823,16 @@ static std::optional<std::array<uint64_t, 3>> FillIndex(Value value, uint32_t ax
|
||||
}
|
||||
|
||||
static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
||||
if (program.stage != ShaderType::Compute || program.blocks.empty() ||
|
||||
program.blocks.size() != program.block_info.size() || program.info.uses_dma ||
|
||||
if (program.stage != ShaderType::Compute || program.blocks.empty() || program.info.uses_dma ||
|
||||
!program.info.samplers.empty()) {
|
||||
return {};
|
||||
}
|
||||
std::unordered_set<uint32_t> visited;
|
||||
uint32_t index = 0;
|
||||
const Inst* store = nullptr;
|
||||
std::unordered_set<const Block*> visited;
|
||||
const auto* block = program.blocks.front();
|
||||
const Inst* store = nullptr;
|
||||
for (;;) {
|
||||
if (!visited.insert(index).second) return {};
|
||||
for (const auto& inst: *program.blocks[index]) {
|
||||
if (block == nullptr || !visited.insert(block).second) return {};
|
||||
for (const auto& inst: *block) {
|
||||
if (AddressOpcodeInfoOf(inst.GetOpcode()).access != AddressAccess::None) return {};
|
||||
if (!inst.MayHaveSideEffects()) continue;
|
||||
if (store != nullptr || (BufferAccessOf(inst.GetOpcode()) != BufferAccess::Write &&
|
||||
@@ -846,12 +840,10 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
||||
return {};
|
||||
store = &inst;
|
||||
}
|
||||
const auto& term = program.block_info[index].terminator;
|
||||
const auto& term = block->terminator;
|
||||
if (term.kind == CFG::TerminatorKind::Return) break;
|
||||
if (term.kind != CFG::TerminatorKind::Branch) return {};
|
||||
const auto next = std::ranges::find(program.block_info, term.true_block, &BlockInfo::id);
|
||||
if (next == program.block_info.end()) return {};
|
||||
index = static_cast<uint32_t>(next - program.block_info.begin());
|
||||
block = term.true_block;
|
||||
}
|
||||
if (store == nullptr || visited.size() != program.blocks.size()) return {};
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
@@ -945,6 +937,12 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
if (source == nullptr) {
|
||||
return value;
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::ReadConst) {
|
||||
const auto slot = source->Arg(1).Resolve();
|
||||
EXIT_IF(!slot.IsImmediate() || slot.GetType() != Type::U32 ||
|
||||
slot.U32() >= program.srt_reads.size());
|
||||
return Clone(program.srt_reads[slot.U32()].value);
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::ReadFirstLane &&
|
||||
ValidateRuntimeValue(program, source->Arg(0))) {
|
||||
// Uniform values need no new EXEC context; preserve their shared evaluation memo.
|
||||
@@ -965,6 +963,11 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
}
|
||||
auto& target =
|
||||
plan.value_storage.emplace_back(source->GetOpcode(), source->Flags<uint64_t>());
|
||||
if (source->GetOpcode() == ValueOpcode::LoadAddressU32 ||
|
||||
source->GetOpcode() == ValueOpcode::ReadConstBuffer ||
|
||||
source->GetOpcode() == ValueOpcode::LoadBufferU32) {
|
||||
target.SetFlags(SrtReadFlags {.index = source->Flags<MemoryFlags>().index});
|
||||
}
|
||||
cloned.emplace(source, &target);
|
||||
if (source->GetOpcode() == ValueOpcode::Phi) {
|
||||
for (size_t index = 0; index < source->NumArgs(); index++) {
|
||||
@@ -978,12 +981,15 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
return Value(&target);
|
||||
};
|
||||
|
||||
bool capture_indirect_reads = false;
|
||||
plan.descriptor_sources.reserve(program.descriptor_sources.size());
|
||||
for (const auto& source: program.descriptor_sources) {
|
||||
auto& target = plan.descriptor_sources.emplace_back();
|
||||
target.dword_count = source.dword_count;
|
||||
target.indirect_descriptor = source.indirect_descriptor;
|
||||
if (target.indirect_descriptor.has_value()) {
|
||||
plan.requires_specialization_memory = true;
|
||||
capture_indirect_reads = true;
|
||||
target.indirect_descriptor->key_count = Clone(target.indirect_descriptor->key_count);
|
||||
target.indirect_descriptor->selector_first =
|
||||
Clone(target.indirect_descriptor->selector_first);
|
||||
@@ -1034,34 +1040,39 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
for (uint32_t i = 0; i < plan.uniform_fill.fill.words; ++i) {
|
||||
plan.uniform_fill.values[i] = Clone(plan.uniform_fill.values[i]);
|
||||
}
|
||||
plan.clean_flat_slots.resize(plan.srt_reads.size());
|
||||
bool capture_indirect_reads = false;
|
||||
for (const auto& source: plan.descriptor_sources) {
|
||||
if (!source.indirect_descriptor.has_value()) continue;
|
||||
const auto& indirect = *source.indirect_descriptor;
|
||||
plan.requires_specialization_memory = true;
|
||||
capture_indirect_reads = true;
|
||||
if (indirect.selector)
|
||||
MarkCleanFlatSlots(plan, Source(plan, indirect.selector->source), plan.clean_flat_slots);
|
||||
MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.selector_mask);
|
||||
MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.selector_first);
|
||||
MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.key_count);
|
||||
if (indirect.sources.empty()) {
|
||||
MarkCleanFlatSlots(plan, Source(plan, indirect.table_source), plan.clean_flat_slots);
|
||||
} else {
|
||||
for (const auto candidate: indirect.sources) {
|
||||
MarkCleanFlatSlots(plan, Source(plan, candidate), plan.clean_flat_slots);
|
||||
}
|
||||
}
|
||||
}
|
||||
plan.capture_specialization_reads |= capture_indirect_reads || !plan.control_flow.empty();
|
||||
if (plan.capture_specialization_reads) {
|
||||
// Every former flat alias now targets its retained raw read. Mark those reads
|
||||
// on the normalized graph, so discarded Phi/EXEC dependencies stay discarded.
|
||||
std::unordered_set<const Inst*> planned_reads;
|
||||
for (const auto& read: plan.srt_reads) {
|
||||
auto* inst = read.value.ResolveInstruction();
|
||||
EXIT_IF(inst == nullptr || (inst->GetOpcode() != ValueOpcode::LoadAddressU32 &&
|
||||
inst->GetOpcode() != ValueOpcode::ReadConstBuffer));
|
||||
planned_reads.insert(inst);
|
||||
}
|
||||
for (const auto& source: plan.descriptor_sources) {
|
||||
if (!source.indirect_descriptor.has_value()) continue;
|
||||
const auto& indirect = *source.indirect_descriptor;
|
||||
if (indirect.selector)
|
||||
MarkCleanReads(planned_reads, Source(plan, indirect.selector->source));
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.selector_mask);
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.selector_first);
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.key_count);
|
||||
if (indirect.sources.empty()) {
|
||||
MarkCleanReads(planned_reads, Source(plan, indirect.table_source));
|
||||
} else {
|
||||
for (const auto candidate: indirect.sources) {
|
||||
MarkCleanReads(planned_reads, Source(plan, candidate));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clean writable addresses prove that shader writes cannot overlap captured reads.
|
||||
for (const auto& buffer: plan.info.buffers) {
|
||||
if (buffer.written) MarkCleanFlatSlots(plan, Source(plan, buffer.source), plan.clean_flat_slots);
|
||||
if (buffer.written) MarkCleanReads(planned_reads, Source(plan, buffer.source));
|
||||
}
|
||||
for (const auto& image: plan.info.images) {
|
||||
if (image.written) MarkCleanFlatSlots(plan, Source(plan, image.source), plan.clean_flat_slots);
|
||||
if (image.written) MarkCleanReads(planned_reads, Source(plan, image.source));
|
||||
}
|
||||
}
|
||||
if (capture_indirect_reads) plan.resource_tracking_complete &= !program.has_address_writes;
|
||||
@@ -1086,7 +1097,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
observed.read_memory = CaptureOrdinaryRead;
|
||||
}
|
||||
SrtWalker clean(program, CleanRuntime(observed));
|
||||
SrtWalker walker(program, observed, program.clean_flat_slots,
|
||||
SrtWalker walker(program, observed,
|
||||
capture_reads || program.requires_specialization_memory ? &clean : nullptr);
|
||||
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
|
||||
return false;
|
||||
@@ -1229,8 +1240,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
}
|
||||
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
|
||||
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete ||
|
||||
program.binding_layout_complete);
|
||||
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete);
|
||||
EXIT_IF(program.info.buffers.size() > specialization.buffers.size() ||
|
||||
program.info.images.size() > specialization.images.size());
|
||||
|
||||
|
||||
@@ -613,8 +613,7 @@ private:
|
||||
value = value.Resolve();
|
||||
const auto* phi = value.TryInstruction();
|
||||
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
|
||||
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u || phi->GetType() != Type::U32 ||
|
||||
m_program.blocks.size() != m_program.block_info.size()) {
|
||||
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u || phi->GetType() != Type::U32) {
|
||||
return value;
|
||||
}
|
||||
const auto* merge = phi->Parent();
|
||||
@@ -637,7 +636,7 @@ private:
|
||||
if (branch == merge || branch->ImmSuccessors().size() != 2u) {
|
||||
return value;
|
||||
}
|
||||
std::array<uint32_t, 2> target_ids;
|
||||
std::array<const Block*, 2> targets;
|
||||
for (uint32_t arm = 0; arm < 2; arm++) {
|
||||
const auto* incoming = phi->PhiBlock(arm);
|
||||
if (incoming == merge ||
|
||||
@@ -648,29 +647,20 @@ private:
|
||||
incoming->ImmSuccessors()[0] != merge))) {
|
||||
return value;
|
||||
}
|
||||
const auto* target = incoming == branch ? merge : incoming;
|
||||
const auto it = std::ranges::find(m_program.blocks, target);
|
||||
if (it == m_program.blocks.end()) {
|
||||
return value;
|
||||
}
|
||||
target_ids[arm] = m_program.block_info[it - m_program.blocks.begin()].id;
|
||||
targets[arm] = incoming == branch ? merge : incoming;
|
||||
}
|
||||
const auto branch_it = std::ranges::find(m_program.blocks, branch);
|
||||
if (branch_it == m_program.blocks.end()) {
|
||||
return value;
|
||||
}
|
||||
const auto& info = m_program.block_info[branch_it - m_program.blocks.begin()];
|
||||
const auto& info = *branch;
|
||||
const auto& term = info.terminator;
|
||||
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
|
||||
!((term.true_block == target_ids[0] && term.false_block == target_ids[1]) ||
|
||||
(term.false_block == target_ids[0] && term.true_block == target_ids[1])) ||
|
||||
!((term.true_block == targets[0] && term.false_block == targets[1]) ||
|
||||
(term.false_block == targets[0] && term.true_block == targets[1])) ||
|
||||
!ValidateRuntimeValue(m_program, info.condition, RuntimeValueType::Integer) ||
|
||||
!ValidateRuntimeValue(m_program, phi->Arg(0)) ||
|
||||
!ValidateRuntimeValue(m_program, phi->Arg(1))) {
|
||||
return value;
|
||||
}
|
||||
// Retain a host expression; replacing the GPU Phi would break SSA dominance.
|
||||
const auto true_arg = term.true_block == target_ids[0] ? 0u : 1u;
|
||||
const auto true_arg = term.true_block == targets[0] ? 0u : 1u;
|
||||
auto& selected = m_program.value_storage.emplace_back(ValueOpcode::SelectU32);
|
||||
selected.SetArg(0, info.condition);
|
||||
selected.SetArg(1, phi->Arg(true_arg));
|
||||
@@ -852,9 +842,9 @@ private:
|
||||
if (entry.source.dwords[word].Resolve() == Value(read))
|
||||
entry.source.dwords[word] = flat;
|
||||
}
|
||||
for (auto& info: m_program.block_info) {
|
||||
if (info.condition.Resolve() == Value(read)) info.condition = flat;
|
||||
if (info.indirect_target.Resolve() == Value(read)) info.indirect_target = flat;
|
||||
for (auto* block: m_program.blocks) {
|
||||
if (block->condition.Resolve() == Value(read)) block->condition = flat;
|
||||
if (block->indirect_target.Resolve() == Value(read)) block->indirect_target = flat;
|
||||
}
|
||||
if (keep) {
|
||||
if (resolved->planning_handle.IsEmpty()) {
|
||||
@@ -1022,9 +1012,6 @@ private:
|
||||
};
|
||||
|
||||
bool NonzeroOnEntry(Value value, const Block* block) const {
|
||||
if (m_program.blocks.size() != m_program.block_info.size()) {
|
||||
return false;
|
||||
}
|
||||
// Each unique predecessor must execute before this use. Stop at joins: an
|
||||
// unrelated comparison is not a bound on FindILsb's zero-input sentinel.
|
||||
for (size_t depth = 0; block != nullptr && depth < m_program.blocks.size(); ++depth) {
|
||||
@@ -1098,15 +1085,11 @@ private:
|
||||
}
|
||||
|
||||
std::optional<EdgePredicate> ConditionalEdge(const Block* from, const Block* to) const {
|
||||
const auto position = std::ranges::find(m_program.blocks, from);
|
||||
const auto target = std::ranges::find(m_program.blocks, to);
|
||||
if (position == m_program.blocks.end() || target == m_program.blocks.end()) return {};
|
||||
const auto& info = m_program.block_info[position - m_program.blocks.begin()];
|
||||
const auto& term = info.terminator;
|
||||
const auto id = m_program.block_info[target - m_program.blocks.begin()].id;
|
||||
const auto& term = from->terminator;
|
||||
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
|
||||
(term.true_block == id) == (term.false_block == id)) return {};
|
||||
EdgePredicate edge {info.condition, term.true_block == id};
|
||||
(term.true_block == to) == (term.false_block == to))
|
||||
return {};
|
||||
EdgePredicate edge {from->condition, term.true_block == to};
|
||||
while (const auto* inst = edge.condition.Resolve().TryInstruction()) {
|
||||
if (inst->GetOpcode() == ValueOpcode::LogicalNot) {
|
||||
edge.positive = !edge.positive;
|
||||
@@ -1470,9 +1453,9 @@ private:
|
||||
|
||||
const Inst* BoundedLoop(Value key, const Block* use, const auto& accepts_bound) const {
|
||||
const auto* phi = key.Resolve().TryInstruction();
|
||||
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
|
||||
phi->GetType() != Type::U32 || phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u ||
|
||||
m_program.blocks.size() != m_program.block_info.size()) return {};
|
||||
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi || phi->GetType() != Type::U32 ||
|
||||
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u)
|
||||
return {};
|
||||
const Block* increment_block = nullptr;
|
||||
uint32_t initial_arm = 0;
|
||||
for (uint32_t initial = 0; initial < 2u; ++initial) {
|
||||
@@ -1731,7 +1714,7 @@ private:
|
||||
indirect.table_immediate = table_immediate;
|
||||
indirect.table_stride = table_stride;
|
||||
indirect.workgroup_axis = WorkgroupAxis(key);
|
||||
if (indirect.workgroup_axis != UINT32_MAX && table_source.dword_count == 2u) {
|
||||
if (indirect.workgroup_axis != UINT32_MAX && !plan.table_indexed) {
|
||||
// The descriptor also supplies dimensions to shader arithmetic. Keep its reads;
|
||||
// only the image handle is projected onto the bounded workgroup key.
|
||||
plan.retain_reads = true;
|
||||
@@ -1904,22 +1887,13 @@ private:
|
||||
}
|
||||
|
||||
bool ImpossibleSwitchEdge(const Block* from, const Block* to) const {
|
||||
const auto from_it = std::ranges::find(m_program.blocks, from);
|
||||
const auto to_it = std::ranges::find(m_program.blocks, to);
|
||||
if (from_it == m_program.blocks.end() || to_it == m_program.blocks.end() ||
|
||||
m_program.block_info.size() != m_program.blocks.size()) return false;
|
||||
const auto& info = m_program.block_info[from_it - m_program.blocks.begin()];
|
||||
const auto& term = info.terminator;
|
||||
if (term.kind != CFG::TerminatorKind::IndirectBranch ||
|
||||
term.indirect_selector_code == UINT32_MAX ||
|
||||
term.indirect_selector_values.empty() ||
|
||||
term.indirect_selector_values.size() != term.indirect_selector_targets.size()) return false;
|
||||
const auto target = m_program.block_info[to_it - m_program.blocks.begin()].id;
|
||||
const auto maximum = SelectorMaximum(info.indirect_target);
|
||||
const auto& term = from->terminator;
|
||||
if (term.kind != CFG::TerminatorKind::IndirectBranch || !term.indexed) return false;
|
||||
const auto maximum = SelectorMaximum(from->indirect_target);
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < term.indirect_selector_values.size(); ++i) {
|
||||
if (term.indirect_selector_targets[i] != target) continue;
|
||||
if (term.indirect_selector_values[i] <= maximum) return false;
|
||||
for (const auto& branch: term.cases) {
|
||||
if (branch.target != to) continue;
|
||||
if (branch.value <= maximum) return false;
|
||||
found = true;
|
||||
}
|
||||
return found;
|
||||
|
||||
@@ -75,127 +75,11 @@ void ValidateOptions(const Program& program, ShaderStageInputInfo input_info) {
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_info) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case ValueOpcode::GetAttribute: {
|
||||
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
|
||||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
|
||||
return Fail("typed attribute reference is not constant");
|
||||
}
|
||||
if ((program.stage == ShaderType::Vertex ||
|
||||
program.stage == ShaderType::Local) &&
|
||||
(inst.Arg(1).U32() >= 4u ||
|
||||
inst.Arg(0).U32() >=
|
||||
static_cast<uint32_t>(input_info.vertex->resources_num))) {
|
||||
return Fail("vertex input reference is out of range");
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel &&
|
||||
(inst.Arg(1).U32() >= 4u ||
|
||||
inst.Arg(0).U32() >= input_info.pixel->input_num)) {
|
||||
return Fail("pixel input reference is out of range");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::GetInterpolationParameter: {
|
||||
if (program.stage != ShaderType::Pixel || !inst.Arg(0).IsImmediate() ||
|
||||
inst.Arg(0).GetType() != Type::U32 || !inst.Arg(1).IsImmediate() ||
|
||||
inst.Arg(1).GetType() != Type::U32 || !inst.Arg(2).IsImmediate() ||
|
||||
inst.Arg(2).GetType() != Type::U32) {
|
||||
return Fail("interpolation parameter reference is invalid");
|
||||
}
|
||||
if (inst.Arg(0).U32() >= input_info.pixel->input_num ||
|
||||
inst.Arg(1).U32() >= 4u || inst.Arg(2).U32() >= 3u) {
|
||||
return Fail("interpolation parameter reference is out of range");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::GetBuiltin: {
|
||||
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
|
||||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
|
||||
return Fail("typed builtin reference is not constant");
|
||||
}
|
||||
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
|
||||
const auto component = inst.Arg(1).U32();
|
||||
switch (kind) {
|
||||
case StageInputKind::PackedAncillary:
|
||||
return Fail("packed pixel ancillary input has an unsupported live use");
|
||||
case StageInputKind::Layer:
|
||||
case StageInputKind::SampleId:
|
||||
if (program.stage != ShaderType::Pixel || component != 0u) {
|
||||
return Fail("typed pixel scalar input is invalid");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::VertexIndex:
|
||||
case StageInputKind::InvocationId:
|
||||
case StageInputKind::PrimitiveId:
|
||||
case StageInputKind::InstanceIndex:
|
||||
case StageInputKind::FrontFacing:
|
||||
case StageInputKind::LocalInvocationIndex:
|
||||
if (component != 0u) {
|
||||
return Fail("typed scalar builtin component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::FragCoord:
|
||||
if (component >= 4u) {
|
||||
return Fail("typed fragment-coordinate component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::BaryCoordSmooth:
|
||||
case StageInputKind::BaryCoordSmoothCentroid:
|
||||
case StageInputKind::BaryCoordNoPerspective:
|
||||
if (component >= 2u) {
|
||||
return Fail("typed barycentric component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::TessCoord:
|
||||
case StageInputKind::WorkgroupId:
|
||||
case StageInputKind::LocalInvocationId:
|
||||
case StageInputKind::GlobalInvocationId:
|
||||
if (component >= 3u) {
|
||||
return Fail("typed invocation builtin component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::Parameter:
|
||||
default: return Fail("typed builtin kind is invalid");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::SetAttribute: {
|
||||
const auto index = inst.Flags<ExportFlags>().index;
|
||||
if (index >= program.export_info.size()) {
|
||||
return Fail("typed export metadata index is out of range");
|
||||
}
|
||||
const auto& exp = program.export_info[index];
|
||||
if (exp.kind == ExportTargetKind::Position && exp.index != 0 && exp.en != 0 &&
|
||||
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh &&
|
||||
program.stage != ShaderType::TessellationEvaluation) {
|
||||
return Fail("auxiliary position export requires a vertex, mesh, or "
|
||||
"tessellation evaluation shader");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex,
|
||||
ShaderInfo& info) {
|
||||
void CollectVertexInputs(const ShaderVertexInputInfo* vertex, ShaderInfo& info,
|
||||
const std::array<uint8_t, 32>& used_components) {
|
||||
AddInput(info, StageInputKind::VertexIndex, 0, 1, "gl_VertexIndex");
|
||||
AddInput(info, StageInputKind::InstanceIndex, 0, 1, "gl_InstanceIndex");
|
||||
uint32_t used_components[ShaderVertexInputInfo::RES_MAX] = {};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() == ValueOpcode::GetAttribute) {
|
||||
const auto attr = inst.Arg(0).U32();
|
||||
const auto chan = inst.Arg(1).U32();
|
||||
used_components[attr] = std::max(used_components[attr], chan + 1u);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t attr = 0; attr < static_cast<uint32_t>(vertex->resources_num); attr++) {
|
||||
if (used_components[attr] != 0) {
|
||||
AddInput(info, StageInputKind::Parameter, attr, used_components[attr],
|
||||
@@ -204,37 +88,25 @@ void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* ve
|
||||
}
|
||||
}
|
||||
|
||||
void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixel,
|
||||
ShaderInfo& info) {
|
||||
void CollectPixelInputs(const ShaderPixelInputInfo* pixel, ShaderInfo& info,
|
||||
std::array<bool, 32>& per_vertex,
|
||||
const std::array<uint8_t, 32>& used_components) {
|
||||
if (pixel->HasPositionInput()) {
|
||||
AddInput(info, StageInputKind::FragCoord, 0, 4, "gl_FragCoord");
|
||||
}
|
||||
if (pixel->ps_front_face) {
|
||||
AddInput(info, StageInputKind::FrontFacing, 0, 1, "gl_FrontFacing");
|
||||
}
|
||||
std::array<bool, 32> per_vertex {};
|
||||
std::array<bool, 32> interpolated {};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() == ValueOpcode::GetAttribute) {
|
||||
interpolated[inst.Arg(0).U32()] = true;
|
||||
} else if (inst.GetOpcode() == ValueOpcode::GetInterpolationParameter) {
|
||||
const auto input = inst.Arg(0).U32();
|
||||
const auto mode = inst.Arg(2).U32();
|
||||
per_vertex[input] =
|
||||
per_vertex[input] || mode < 2u || !ShaderPixelParameterIsFlat(*pixel, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Aliases of a vertex output share one SPIR-V interface variable. If any
|
||||
// alias reads raw vertices, interpolate the other aliases from those too.
|
||||
// Mixed interpolation modes share raw vertices at their guest export slot.
|
||||
// Rectangle expansion alone supplies separate flat and smooth outputs.
|
||||
for (uint32_t input = 0; input < pixel->input_num; input++) {
|
||||
for (uint32_t alias = 0; alias < pixel->input_num; alias++) {
|
||||
const bool same_mode = ShaderPixelParameterIsFlat(*pixel, input) ==
|
||||
ShaderPixelParameterIsFlat(*pixel, alias);
|
||||
if (ShaderPixelParameterMappedLocation(*pixel, input) ==
|
||||
ShaderPixelParameterMappedLocation(*pixel, alias) &&
|
||||
ShaderPixelParameterIsFlat(*pixel, input) ==
|
||||
ShaderPixelParameterIsFlat(*pixel, alias)) {
|
||||
per_vertex[input] = per_vertex[input] || per_vertex[alias];
|
||||
(pixel->parameter_mode != ShaderPixelParameterMode::Rectangle || same_mode)) {
|
||||
per_vertex[input] = per_vertex[input] || per_vertex[alias] || !same_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +115,8 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe
|
||||
per_vertex[input]);
|
||||
}
|
||||
for (uint32_t input = 0; input < pixel->input_num; input++) {
|
||||
if (interpolated[input] && per_vertex[input]) {
|
||||
if (used_components[input] != 0 && per_vertex[input] &&
|
||||
!ShaderPixelParameterIsFlat(*pixel, input)) {
|
||||
const auto kind = pixel->ps_no_perspective ? StageInputKind::BaryCoordNoPerspective
|
||||
: StageInputKind::BaryCoordSmooth;
|
||||
AddInput(info, kind, 0, 3,
|
||||
@@ -268,137 +141,411 @@ void CollectComputeInputs(const ShaderComputeInputInfo* compute, ShaderInfo& inf
|
||||
}
|
||||
}
|
||||
|
||||
void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (program.stage == ShaderType::TessellationControl &&
|
||||
inst.GetOpcode() == ValueOpcode::LaneId) {
|
||||
AddInput(info, StageInputKind::InvocationId, 0, 1, "gl_InvocationID");
|
||||
}
|
||||
if (inst.GetOpcode() != ValueOpcode::GetBuiltin) {
|
||||
continue;
|
||||
}
|
||||
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
|
||||
switch (kind) {
|
||||
case StageInputKind::VertexIndex:
|
||||
AddInput(info, kind, 0, 1, "gl_VertexIndex");
|
||||
break;
|
||||
case StageInputKind::InstanceIndex:
|
||||
AddInput(info, kind, 0, 1, "gl_InstanceIndex");
|
||||
break;
|
||||
case StageInputKind::InvocationId:
|
||||
AddInput(info, kind, 0, 1, "gl_InvocationID");
|
||||
break;
|
||||
case StageInputKind::PrimitiveId:
|
||||
AddInput(info, kind, 0, 1, "gl_PrimitiveID");
|
||||
break;
|
||||
case StageInputKind::TessCoord: AddInput(info, kind, 0, 3, "gl_TessCoord"); break;
|
||||
case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break;
|
||||
case StageInputKind::FrontFacing:
|
||||
AddInput(info, kind, 0, 1, "gl_FrontFacing");
|
||||
break;
|
||||
case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break;
|
||||
case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break;
|
||||
case StageInputKind::BaryCoordSmooth:
|
||||
case StageInputKind::BaryCoordSmoothCentroid:
|
||||
AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR");
|
||||
break;
|
||||
case StageInputKind::BaryCoordNoPerspective:
|
||||
AddInput(info, kind, 0, 3, "gl_BaryCoordNoPerspKHR");
|
||||
break;
|
||||
case StageInputKind::WorkgroupId:
|
||||
AddInput(info, kind, 0, 3, "gl_WorkGroupID");
|
||||
break;
|
||||
case StageInputKind::LocalInvocationId:
|
||||
AddInput(info, kind, 0, 3, "gl_LocalInvocationID");
|
||||
break;
|
||||
case StageInputKind::LocalInvocationIndex:
|
||||
AddInput(info, kind, 0, 1, "gl_LocalInvocationIndex");
|
||||
break;
|
||||
case StageInputKind::GlobalInvocationId:
|
||||
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
||||
break;
|
||||
case StageInputKind::PackedAncillary:
|
||||
case StageInputKind::Parameter: break;
|
||||
}
|
||||
}
|
||||
void AddBuiltinInput(ShaderInfo& info, StageInputKind kind) {
|
||||
switch (kind) {
|
||||
case StageInputKind::VertexIndex:
|
||||
AddInput(info, kind, 0, 1, "gl_VertexIndex");
|
||||
break;
|
||||
case StageInputKind::InstanceIndex:
|
||||
AddInput(info, kind, 0, 1, "gl_InstanceIndex");
|
||||
break;
|
||||
case StageInputKind::InvocationId:
|
||||
AddInput(info, kind, 0, 1, "gl_InvocationID");
|
||||
break;
|
||||
case StageInputKind::PrimitiveId:
|
||||
AddInput(info, kind, 0, 1, "gl_PrimitiveID");
|
||||
break;
|
||||
case StageInputKind::TessCoord: AddInput(info, kind, 0, 3, "gl_TessCoord"); break;
|
||||
case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break;
|
||||
case StageInputKind::FrontFacing:
|
||||
AddInput(info, kind, 0, 1, "gl_FrontFacing");
|
||||
break;
|
||||
case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break;
|
||||
case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break;
|
||||
case StageInputKind::BaryCoordSmoothSample:
|
||||
AddInput(info, StageInputKind::SampleId, 0, 1, "gl_SampleID");
|
||||
[[fallthrough]];
|
||||
case StageInputKind::BaryCoordSmooth:
|
||||
case StageInputKind::BaryCoordSmoothCentroid:
|
||||
AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR");
|
||||
break;
|
||||
case StageInputKind::BaryCoordNoPerspective:
|
||||
AddInput(info, kind, 0, 3, "gl_BaryCoordNoPerspKHR");
|
||||
break;
|
||||
case StageInputKind::WorkgroupId:
|
||||
AddInput(info, kind, 0, 3, "gl_WorkGroupID");
|
||||
break;
|
||||
case StageInputKind::LocalInvocationId:
|
||||
AddInput(info, kind, 0, 3, "gl_LocalInvocationID");
|
||||
break;
|
||||
case StageInputKind::LocalInvocationIndex:
|
||||
AddInput(info, kind, 0, 1, "gl_LocalInvocationIndex");
|
||||
break;
|
||||
case StageInputKind::GlobalInvocationId:
|
||||
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
||||
break;
|
||||
case StageInputKind::PackedAncillary:
|
||||
case StageInputKind::Parameter: break;
|
||||
}
|
||||
}
|
||||
|
||||
void CollectOutputs(const Program& program, ShaderStageInputInfo input_info, ShaderInfo& info) {
|
||||
void CollectOutput(const Program& program, ShaderStageInputInfo input_info,
|
||||
ShaderInfo& info, const Inst& inst) {
|
||||
const bool alpha_remap = program.stage == ShaderType::Pixel && input_info.pixel != nullptr &&
|
||||
input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() != ValueOpcode::SetAttribute) {
|
||||
continue;
|
||||
const auto& export_info = program.export_info[inst.Flags<ExportFlags>().index];
|
||||
if (export_info.kind == ExportTargetKind::MrtZ) {
|
||||
if (program.stage == ShaderType::Pixel && (export_info.en & 0x1u) != 0 &&
|
||||
input_info.pixel->ps_depth_export_enable) {
|
||||
AddOutput(info, StageOutputKind::Depth, 0, 0, "gl_FragDepth");
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel && (export_info.en & 0x4u) != 0 &&
|
||||
input_info.pixel->ps_sample_mask_export_enable) {
|
||||
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (export_info.en == 0) {
|
||||
return;
|
||||
}
|
||||
switch (export_info.kind) {
|
||||
case ExportTargetKind::Position:
|
||||
if (export_info.index == 0) {
|
||||
AddOutput(info, StageOutputKind::Position, 0, 0, "out_position");
|
||||
break;
|
||||
}
|
||||
const auto& export_info = program.export_info[inst.Flags<ExportFlags>().index];
|
||||
if (export_info.kind == ExportTargetKind::MrtZ) {
|
||||
if (program.stage == ShaderType::Pixel && (export_info.en & 0x1u) != 0 &&
|
||||
input_info.pixel->ps_depth_export_enable) {
|
||||
AddOutput(info, StageOutputKind::Depth, 0, 0, "gl_FragDepth");
|
||||
if (export_info.compr) {
|
||||
return Fail("compressed auxiliary position export is unsupported");
|
||||
}
|
||||
for (uint32_t component = 0; component < 4; component++) {
|
||||
if ((export_info.en & (1u << component)) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel && (export_info.en & 0x4u) != 0 &&
|
||||
input_info.pixel->ps_sample_mask_export_enable) {
|
||||
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
|
||||
const auto output = DecodePositionExportComponent(
|
||||
input_info.vertex->pa_cl_vs_out_cntl, export_info.index, component);
|
||||
if (output.viewport) {
|
||||
AddOutput(info, StageOutputKind::ViewportIndex, 0, 0, "gl_ViewportIndex");
|
||||
}
|
||||
if (output.point_size) {
|
||||
AddOutput(info, StageOutputKind::PointSize, 0, 0, "gl_PointSize");
|
||||
}
|
||||
if (output.layer) {
|
||||
AddOutput(info, StageOutputKind::Layer, 0, 0, "gl_Layer");
|
||||
}
|
||||
if (output.clip_distance != UINT32_MAX) {
|
||||
AddOutput(info, StageOutputKind::ClipDistance, output.clip_distance, 0,
|
||||
"gl_ClipDistance");
|
||||
}
|
||||
if (output.cull_distance != UINT32_MAX) {
|
||||
AddOutput(info, StageOutputKind::CullDistance, output.cull_distance, 0,
|
||||
"gl_CullDistance");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (export_info.en == 0) {
|
||||
continue;
|
||||
break;
|
||||
case ExportTargetKind::Parameter:
|
||||
AddOutput(info, StageOutputKind::Parameter, export_info.index,
|
||||
export_info.index, fmt::format("out_param_{}", export_info.index));
|
||||
break;
|
||||
case ExportTargetKind::Mrt: {
|
||||
if (alpha_remap && export_info.index != 0) {
|
||||
break;
|
||||
}
|
||||
switch (export_info.kind) {
|
||||
case ExportTargetKind::Position:
|
||||
if (export_info.index == 0) {
|
||||
AddOutput(info, StageOutputKind::Position, 0, 0, "out_position");
|
||||
break;
|
||||
const auto slot = input_info.pixel->dual_source_blending
|
||||
? 0u : ShaderPixelExportTarget(input_info.pixel->target_shader_mask,
|
||||
export_info.index);
|
||||
if (slot >= 8) {
|
||||
break;
|
||||
}
|
||||
AddOutput(info, StageOutputKind::Mrt, export_info.index, slot,
|
||||
fmt::format("out_mrt_{}", export_info.index));
|
||||
if (alpha_remap) {
|
||||
AddOutput(info, StageOutputKind::Mrt, 1, 0, "out_mrt_1");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
struct InputUsage {
|
||||
std::array<uint8_t, 32> components {};
|
||||
std::array<bool, 32> per_vertex {};
|
||||
std::array<bool, NumScalarRegs> user_data {};
|
||||
std::array<StageInputKind, static_cast<size_t>(StageInputKind::Parameter)> builtins {};
|
||||
uint32_t builtin_mask = 0;
|
||||
uint32_t builtin_count = 0;
|
||||
|
||||
void AddBuiltin(StageInputKind kind) {
|
||||
const auto bit = 1u << static_cast<uint32_t>(kind);
|
||||
if ((builtin_mask & bit) == 0) {
|
||||
builtin_mask |= bit;
|
||||
builtins[builtin_count++] = kind;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs,
|
||||
const Inst& inst) {
|
||||
auto& info = program.info;
|
||||
const auto op = inst.GetOpcode();
|
||||
const auto buffer_access = BufferAccessOf(op);
|
||||
const auto address_access = AddressOpcodeInfoOf(op).access;
|
||||
const auto shared_access = SharedAccessOf(op);
|
||||
info.float64 |= inst.GetType() == Type::F64;
|
||||
info.buffer_int64_atomics |= buffer_access == BufferAccess::Atomic &&
|
||||
inst.GetType() == Type::U64;
|
||||
if (buffer_access != BufferAccess::None || address_access != AddressAccess::None ||
|
||||
shared_access != SharedAccess::None) {
|
||||
const auto index = inst.Flags<MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
return Fail("typed shader contains invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[index];
|
||||
const auto kind = memory.kind;
|
||||
if (!memory.planning_only) {
|
||||
info.uses_lds |= kind == ResourceKind::FlatLocal;
|
||||
if (shared_access != SharedAccess::None) {
|
||||
info.uses_lds |= kind == ResourceKind::Lds;
|
||||
info.uses_gds |= kind == ResourceKind::Gds;
|
||||
} else if (kind == ResourceKind::Buffer || kind == ResourceKind::ScalarBuffer) {
|
||||
if (memory.resource >= info.buffers.size() || memory.resource >= ShaderInfo::MaxBuffers) {
|
||||
return Fail("buffer operation has invalid resource metadata");
|
||||
}
|
||||
info.live_buffers |= uint64_t{1} << memory.resource;
|
||||
for (const auto child: info.buffers[memory.resource].indirect_resources) {
|
||||
if (child >= info.buffers.size()) {
|
||||
return Fail("indirect buffer operation has invalid resource metadata");
|
||||
}
|
||||
if (export_info.compr) {
|
||||
return Fail("compressed auxiliary position export is unsupported");
|
||||
}
|
||||
for (uint32_t component = 0; component < 4; component++) {
|
||||
if ((export_info.en & (1u << component)) == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto output = DecodePositionExportComponent(
|
||||
input_info.vertex->pa_cl_vs_out_cntl, export_info.index, component);
|
||||
if (output.viewport) {
|
||||
AddOutput(info, StageOutputKind::ViewportIndex, 0, 0, "gl_ViewportIndex");
|
||||
}
|
||||
if (output.point_size) {
|
||||
AddOutput(info, StageOutputKind::PointSize, 0, 0, "gl_PointSize");
|
||||
}
|
||||
if (output.layer) {
|
||||
AddOutput(info, StageOutputKind::Layer, 0, 0, "gl_Layer");
|
||||
}
|
||||
if (output.clip_distance != UINT32_MAX) {
|
||||
AddOutput(info, StageOutputKind::ClipDistance, output.clip_distance, 0,
|
||||
"gl_ClipDistance");
|
||||
}
|
||||
if (output.cull_distance != UINT32_MAX) {
|
||||
AddOutput(info, StageOutputKind::CullDistance, output.cull_distance, 0,
|
||||
"gl_CullDistance");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ExportTargetKind::Parameter:
|
||||
AddOutput(info, StageOutputKind::Parameter, export_info.index,
|
||||
export_info.index, fmt::format("out_param_{}", export_info.index));
|
||||
break;
|
||||
case ExportTargetKind::Mrt:
|
||||
if (alpha_remap && export_info.index != 0) {
|
||||
break;
|
||||
}
|
||||
AddOutput(info, StageOutputKind::Mrt, export_info.index, export_info.index,
|
||||
fmt::format("out_mrt_{}", export_info.index));
|
||||
if (alpha_remap) {
|
||||
AddOutput(info, StageOutputKind::Mrt, 1, 1, "out_mrt_1");
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
info.live_buffers |= uint64_t{1} << child;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (address_access != AddressAccess::None) {
|
||||
if (kind == ResourceKind::Scratch || kind == ResourceKind::FlatLocal) {
|
||||
if (program.scratch_dwords == 0) {
|
||||
return Fail("scratch operation has no per-thread storage");
|
||||
}
|
||||
info.function_scratch = true;
|
||||
info.function_lds |= kind == ResourceKind::FlatLocal &&
|
||||
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
|
||||
} else if (address_access == AddressAccess::Write) {
|
||||
return Fail("writable FLAT/GLOBAL addresses require GPU ownership tracking");
|
||||
} else {
|
||||
info.coherent_buffers |= memory.coherent;
|
||||
}
|
||||
}
|
||||
if (buffer_access != BufferAccess::None) {
|
||||
if (kind == ResourceKind::IndirectBuffer && op != ValueOpcode::ReadConstBuffer) {
|
||||
info.subgroup_local_invocation_id = true;
|
||||
}
|
||||
if (kind == ResourceKind::Buffer) {
|
||||
info.coherent_buffers |= memory.coherent;
|
||||
if (memory.resource >= info.buffers.size()) {
|
||||
return Fail("buffer operation has invalid resource metadata");
|
||||
}
|
||||
const auto bits = StorageBufferElementBits(program, memory);
|
||||
info.buffer_u8 |= bits == 8u;
|
||||
info.buffer_u16 |= bits == 16u;
|
||||
if ((info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) {
|
||||
if (program.stage != ShaderType::Compute) {
|
||||
return Fail("buffer ADD_TID is only valid for compute shaders");
|
||||
}
|
||||
info.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shared_access != SharedAccess::None) {
|
||||
if (kind != ResourceKind::Lds && kind != ResourceKind::Gds) {
|
||||
return Fail("shared operation has invalid resource kind");
|
||||
}
|
||||
if (shared_access == SharedAccess::Atomic && SharedComponentCount(op) == 2u) {
|
||||
if (kind != ResourceKind::Lds || program.stage != ShaderType::Compute) {
|
||||
return Fail("64-bit shared atomics require compute LDS");
|
||||
}
|
||||
info.shared_int64_atomics = true;
|
||||
}
|
||||
info.function_lds |= kind == ResourceKind::Lds &&
|
||||
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
|
||||
if (shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
||||
info.subgroup_ballot = true;
|
||||
info.subgroup_shuffle = true;
|
||||
info.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (inst.GetOpcode()) {
|
||||
case ValueOpcode::GetAttribute: {
|
||||
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
|
||||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
|
||||
return Fail("typed attribute reference is not constant");
|
||||
}
|
||||
if ((program.stage == ShaderType::Vertex ||
|
||||
program.stage == ShaderType::Local) &&
|
||||
(inst.Arg(1).U32() >= 4u ||
|
||||
inst.Arg(0).U32() >=
|
||||
static_cast<uint32_t>(input_info.vertex->resources_num))) {
|
||||
return Fail("vertex input reference is out of range");
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel &&
|
||||
(inst.Arg(1).U32() >= 4u ||
|
||||
inst.Arg(0).U32() >= input_info.pixel->input_num)) {
|
||||
return Fail("pixel input reference is out of range");
|
||||
}
|
||||
if (program.stage == ShaderType::Vertex || program.stage == ShaderType::Local ||
|
||||
program.stage == ShaderType::Pixel) {
|
||||
auto& components = inputs.components[inst.Arg(0).U32()];
|
||||
components = std::max(components, static_cast<uint8_t>(inst.Arg(1).U32() + 1u));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::GetInterpolationParameter: {
|
||||
if (program.stage != ShaderType::Pixel || !inst.Arg(0).IsImmediate() ||
|
||||
inst.Arg(0).GetType() != Type::U32 || !inst.Arg(1).IsImmediate() ||
|
||||
inst.Arg(1).GetType() != Type::U32 || !inst.Arg(2).IsImmediate() ||
|
||||
inst.Arg(2).GetType() != Type::U32) {
|
||||
return Fail("interpolation parameter reference is invalid");
|
||||
}
|
||||
if (inst.Arg(0).U32() >= input_info.pixel->input_num ||
|
||||
inst.Arg(1).U32() >= 4u || inst.Arg(2).U32() >= 3u) {
|
||||
return Fail("interpolation parameter reference is out of range");
|
||||
}
|
||||
const auto input = inst.Arg(0).U32();
|
||||
inputs.per_vertex[input] |= inst.Arg(2).U32() < 2u ||
|
||||
!ShaderPixelParameterIsFlat(*input_info.pixel, input);
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::GetBuiltin: {
|
||||
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
|
||||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
|
||||
return Fail("typed builtin reference is not constant");
|
||||
}
|
||||
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
|
||||
const auto component = inst.Arg(1).U32();
|
||||
switch (kind) {
|
||||
case StageInputKind::PackedAncillary:
|
||||
return Fail("packed pixel ancillary input has an unsupported live use");
|
||||
case StageInputKind::Layer:
|
||||
case StageInputKind::SampleId:
|
||||
if (program.stage != ShaderType::Pixel || component != 0u) {
|
||||
return Fail("typed pixel scalar input is invalid");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::VertexIndex:
|
||||
case StageInputKind::InvocationId:
|
||||
case StageInputKind::PrimitiveId:
|
||||
case StageInputKind::InstanceIndex:
|
||||
case StageInputKind::FrontFacing:
|
||||
case StageInputKind::LocalInvocationIndex:
|
||||
if (component != 0u) {
|
||||
return Fail("typed scalar builtin component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::FragCoord:
|
||||
if (component >= 4u) {
|
||||
return Fail("typed fragment-coordinate component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::BaryCoordSmooth:
|
||||
case StageInputKind::BaryCoordSmoothSample:
|
||||
case StageInputKind::BaryCoordSmoothCentroid:
|
||||
case StageInputKind::BaryCoordNoPerspective:
|
||||
if (component >= 2u) {
|
||||
return Fail("typed barycentric component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::TessCoord:
|
||||
case StageInputKind::WorkgroupId:
|
||||
case StageInputKind::LocalInvocationId:
|
||||
case StageInputKind::GlobalInvocationId:
|
||||
if (component >= 3u) {
|
||||
return Fail("typed invocation builtin component is out of range");
|
||||
}
|
||||
break;
|
||||
case StageInputKind::Parameter:
|
||||
default: return Fail("typed builtin kind is invalid");
|
||||
}
|
||||
inputs.AddBuiltin(kind);
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::SetAttribute: {
|
||||
const auto index = inst.Flags<ExportFlags>().index;
|
||||
if (index >= program.export_info.size()) {
|
||||
return Fail("typed export metadata index is out of range");
|
||||
}
|
||||
const auto& exp = program.export_info[index];
|
||||
if (exp.kind == ExportTargetKind::Position && exp.index != 0 && exp.en != 0 &&
|
||||
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh &&
|
||||
program.stage != ShaderType::TessellationEvaluation) {
|
||||
return Fail("auxiliary position export requires a vertex, mesh, or "
|
||||
"tessellation evaluation shader");
|
||||
}
|
||||
info.pixel_valid_mask |= program.stage == ShaderType::Pixel && exp.vm;
|
||||
CollectOutput(program, input_info, info, inst);
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::GetUserData: {
|
||||
if (!inst.HasUses()) break;
|
||||
if (inst.Arg(0).GetType() != Type::ScalarReg) {
|
||||
return Fail("typed shader contains an invalid user-data register");
|
||||
}
|
||||
const auto index = RegIndex(inst.Arg(0).ScalarRegister());
|
||||
if (index >= NumScalarRegs) {
|
||||
return Fail("typed shader contains an invalid user-data register");
|
||||
}
|
||||
inputs.user_data[index] = true;
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::GetDispatchThreadExtent:
|
||||
info.uses_dispatch_threads |= inst.HasUses();
|
||||
break;
|
||||
case ValueOpcode::ReadConst: info.uses_flattened_srt = true; break;
|
||||
case ValueOpcode::BitwiseXor32: info.has_bitwise_xor = true; break;
|
||||
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
|
||||
case ValueOpcode::BvhIntersect: info.bvh = true; break;
|
||||
case ValueOpcode::ConditionRef:
|
||||
info.subgroup_ballot |=
|
||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||
break;
|
||||
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
|
||||
case ValueOpcode::DppMoveU32:
|
||||
case ValueOpcode::ReadFirstLane:
|
||||
case ValueOpcode::ReadLane: {
|
||||
info.subgroup_ballot = true;
|
||||
info.subgroup_shuffle = true;
|
||||
if (inst.GetOpcode() == ValueOpcode::DppMoveU32) {
|
||||
info.subgroup_local_invocation_id = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::DppUpdateU32:
|
||||
case ValueOpcode::WriteLane: {
|
||||
info.subgroup_ballot = true;
|
||||
info.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::SwizzleU32:
|
||||
info.uses_swizzle = true;
|
||||
[[fallthrough]];
|
||||
case ValueOpcode::Permlane16U32:
|
||||
case ValueOpcode::PermuteU32:
|
||||
case ValueOpcode::BpermuteU32: {
|
||||
info.subgroup_ballot = true;
|
||||
info.subgroup_shuffle = true;
|
||||
info.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::LaneId:
|
||||
if (program.stage == ShaderType::TessellationControl) {
|
||||
inputs.AddBuiltin(StageInputKind::InvocationId);
|
||||
} else {
|
||||
info.subgroup_local_invocation_id = true;
|
||||
}
|
||||
break;
|
||||
case ValueOpcode::ImageQueryLod: info.compute_derivatives = true; break;
|
||||
case ValueOpcode::ImageGatherRaw:
|
||||
info.image_gather_extended = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,29 +557,49 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
|
||||
: "shader info already collected");
|
||||
}
|
||||
ValidateOptions(program, input_info);
|
||||
ValidateValueReferences(program, input_info);
|
||||
|
||||
auto& next = program.info;
|
||||
next.inputs.clear();
|
||||
next.outputs.clear();
|
||||
next.has_bitwise_xor =
|
||||
std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto* block) {
|
||||
return std::any_of(block->begin(), block->end(), [](const auto& inst) {
|
||||
return inst.GetOpcode() == ValueOpcode::BitwiseXor32;
|
||||
});
|
||||
});
|
||||
auto& info = program.info;
|
||||
if (info.buffers.size() > ShaderInfo::MaxBuffers) {
|
||||
return Fail("buffer resource limit exceeded");
|
||||
}
|
||||
info.inputs.clear();
|
||||
info.outputs.clear();
|
||||
info.user_data_registers.clear();
|
||||
info.live_buffers = 0;
|
||||
info.has_bitwise_xor = info.uses_lds = info.uses_gds = info.uses_flattened_srt =
|
||||
info.uses_dispatch_threads = info.uses_swizzle = false;
|
||||
info.bvh = info.subgroup_ballot = info.subgroup_barrier = info.subgroup_shuffle =
|
||||
info.subgroup_local_invocation_id = info.compute_derivatives = info.image_gather_extended =
|
||||
info.function_lds = info.function_scratch = info.pixel_valid_mask = info.buffer_int64_atomics =
|
||||
info.buffer_u8 = info.buffer_u16 = info.shared_int64_atomics = info.coherent_buffers = info.float64 = false;
|
||||
InputUsage inputs;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
Visit(program, input_info, inputs, inst);
|
||||
}
|
||||
}
|
||||
for (uint32_t index = 0; index < inputs.user_data.size(); index++) {
|
||||
if (inputs.user_data[index]) info.user_data_registers.push_back(index);
|
||||
}
|
||||
const auto uses_mapping = [](const auto& resource) {
|
||||
return resource.indirect_root != UINT32_MAX;
|
||||
};
|
||||
info.uses_flattened_srt |= std::ranges::any_of(info.buffers, uses_mapping) ||
|
||||
std::ranges::any_of(info.images, uses_mapping);
|
||||
switch (program.stage) {
|
||||
case ShaderType::Vertex:
|
||||
case ShaderType::Local: CollectVertexInputs(program, input_info.vertex, next); break;
|
||||
case ShaderType::Local: CollectVertexInputs(input_info.vertex, info, inputs.components); break;
|
||||
case ShaderType::TessellationControl:
|
||||
case ShaderType::TessellationEvaluation:
|
||||
case ShaderType::Mesh: break;
|
||||
case ShaderType::Pixel: CollectPixelInputs(program, input_info.pixel, next); break;
|
||||
case ShaderType::Compute: CollectComputeInputs(input_info.compute, next); break;
|
||||
case ShaderType::Pixel:
|
||||
CollectPixelInputs(input_info.pixel, info, inputs.per_vertex, inputs.components);
|
||||
break;
|
||||
case ShaderType::Compute: CollectComputeInputs(input_info.compute, info); break;
|
||||
default: return Fail("unsupported shader stage for info collection");
|
||||
}
|
||||
CollectBuiltinInputs(program, next);
|
||||
CollectOutputs(program, input_info, next);
|
||||
for (uint32_t i = 0; i < inputs.builtin_count; i++) {
|
||||
AddBuiltinInput(info, inputs.builtins[i]);
|
||||
}
|
||||
program.shader_info_complete = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Completes the immutable shader interface after resource tracking. On failure Program::info and
|
||||
// all completion state remain unchanged.
|
||||
// Collects the shader interface, resource usage and instruction features after resource tracking.
|
||||
void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -324,10 +324,9 @@ private:
|
||||
} // namespace
|
||||
|
||||
SrtWalker::SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots, SrtWalker* clean_evaluator,
|
||||
Value active_mask)
|
||||
: m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots),
|
||||
m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()),
|
||||
SrtWalker* clean_evaluator, Value active_mask)
|
||||
: m_program(program), m_runtime(runtime), m_clean_evaluator(clean_evaluator),
|
||||
m_active_mask(active_mask.Resolve()),
|
||||
m_context(AcquireContext(program)) {}
|
||||
|
||||
SrtWalker::~SrtWalker() { --m_program.evaluation_depth; }
|
||||
@@ -455,7 +454,7 @@ bool SrtWalker::EvaluateExtract(const Inst& inst, uint64_t& result) {
|
||||
}
|
||||
|
||||
bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
||||
const auto flags = inst.Flags<MemoryFlags>();
|
||||
const auto flags = inst.Flags<SrtReadFlags>();
|
||||
if (flags.index >= m_program.memory_info.size()) {
|
||||
return false;
|
||||
}
|
||||
@@ -557,9 +556,8 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
return EvaluateWide(inst.Arg(0), result);
|
||||
}
|
||||
const auto clean_runtime = CleanRuntime(m_runtime);
|
||||
SrtWalker clean_active(m_program, clean_runtime, {}, nullptr, inst.Arg(1));
|
||||
SrtWalker active(m_program, m_runtime, m_clean_flat_slots, &clean_active,
|
||||
inst.Arg(1));
|
||||
SrtWalker clean_active(m_program, clean_runtime, nullptr, inst.Arg(1));
|
||||
SrtWalker active(m_program, m_runtime, &clean_active, inst.Arg(1));
|
||||
return active.EvaluateWide(inst.Arg(0), result);
|
||||
}
|
||||
case ValueOpcode::BitCastU32F32:
|
||||
@@ -573,25 +571,14 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
result = static_cast<uint32_t>(a) |
|
||||
(static_cast<uint64_t>(static_cast<uint32_t>(b)) << 32u);
|
||||
return true;
|
||||
case ValueOpcode::ReadConst: {
|
||||
const auto slot = inst.Arg(1).Resolve();
|
||||
if (!slot.IsImmediate() || slot.GetType() != Type::U32 ||
|
||||
slot.U32() >= m_program.srt_reads.size()) {
|
||||
return false;
|
||||
}
|
||||
if (slot.U32() < m_clean_flat_slots.size() &&
|
||||
m_clean_flat_slots[slot.U32()] != 0u && m_clean_evaluator != nullptr) {
|
||||
return m_clean_evaluator->EvaluateWide(m_program.srt_reads[slot.U32()].value,
|
||||
result);
|
||||
}
|
||||
return EvaluateWide(m_program.srt_reads[slot.U32()].value, result);
|
||||
}
|
||||
case ValueOpcode::LoadAddressU32:
|
||||
case ValueOpcode::ReadConstBuffer:
|
||||
case ValueOpcode::LoadBufferU32:
|
||||
if (IsRawRead(m_program, inst)) {
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadBufferU32 && m_clean_evaluator != nullptr &&
|
||||
m_clean_evaluator->m_active_mask == m_active_mask) {
|
||||
if (m_clean_evaluator != nullptr &&
|
||||
(inst.Flags<SrtReadFlags>().clean != 0u ||
|
||||
(inst.GetOpcode() == ValueOpcode::LoadBufferU32 &&
|
||||
m_clean_evaluator->m_active_mask == m_active_mask))) {
|
||||
return m_clean_evaluator->EvaluateWide(Value(const_cast<Inst*>(&inst)), result);
|
||||
}
|
||||
return EvaluateRawRead(inst, result);
|
||||
@@ -934,8 +921,8 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
|
||||
const auto refresh = [&](uint32_t slot) {
|
||||
if (slot >= m_program.srt_reads.size()) return false;
|
||||
const auto& read = m_program.srt_reads[slot];
|
||||
const bool clean = read.flat_offset < m_clean_flat_slots.size() &&
|
||||
m_clean_flat_slots[read.flat_offset] != 0u;
|
||||
const auto* inst = read.value.ResolveInstruction();
|
||||
const bool clean = inst != nullptr && inst->Flags<SrtReadFlags>().clean != 0u;
|
||||
if (clean && (m_clean_evaluator == nullptr || m_runtime.read_specialization_memory == nullptr))
|
||||
return false;
|
||||
auto& evaluator = clean ? *m_clean_evaluator : *this;
|
||||
|
||||
@@ -20,6 +20,13 @@ struct SrtRuntime {
|
||||
std::span<const uint32_t> workgroup_counts;
|
||||
};
|
||||
|
||||
// Retained reads no longer need the guest instruction PC. A clean read evaluates
|
||||
// its address and bounds through the strict reader as well as the final DWORD.
|
||||
struct SrtReadFlags {
|
||||
uint32_t index = 0;
|
||||
uint32_t clean = 0;
|
||||
};
|
||||
|
||||
enum class RuntimeValueType { Any, Integer };
|
||||
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
@@ -31,8 +38,7 @@ SrtRuntime CleanRuntime(SrtRuntime runtime);
|
||||
class SrtWalker {
|
||||
public:
|
||||
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots = {}, SrtWalker* clean_evaluator = nullptr,
|
||||
Value active_mask = {});
|
||||
SrtWalker* clean_evaluator = nullptr, Value active_mask = {});
|
||||
~SrtWalker();
|
||||
SrtWalker(const SrtWalker&) = delete;
|
||||
SrtWalker& operator=(const SrtWalker&) = delete;
|
||||
@@ -54,7 +60,6 @@ private:
|
||||
|
||||
const ResourcePlan& m_program;
|
||||
SrtRuntime m_runtime;
|
||||
std::span<const uint8_t> m_clean_flat_slots;
|
||||
SrtWalker* m_clean_evaluator = nullptr;
|
||||
Value m_active_mask;
|
||||
ResourcePlan::EvaluationContext& m_context;
|
||||
|
||||
@@ -525,6 +525,9 @@ static void ShaderGetStaticInputInfoPS(
|
||||
EXIT_NOT_IMPLEMENTED(ps_info.input_num > std::size(ps_info.interpolator_settings));
|
||||
ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh);
|
||||
const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr;
|
||||
if ((active_inputs & 0x00000001u) != 0) {
|
||||
ps_info.ps_perspective_sample_vgpr = 0;
|
||||
}
|
||||
if ((active_inputs & 0x00000002u) != 0) {
|
||||
ps_info.ps_perspective_center_vgpr = (active_inputs & 0x00000001u) != 0 ? 2u : 0u;
|
||||
}
|
||||
@@ -558,10 +561,12 @@ static void ShaderGetStaticInputInfoPS(
|
||||
ps_info.interpolator_settings[i] = sh.ps_interpolator_settings[i];
|
||||
}
|
||||
|
||||
ps_info.target_shader_mask = sh.m_cbShaderMask;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
const auto slot = ShaderPixelExportTarget(ps_info.target_shader_mask, i);
|
||||
ps_info.target_output_mode[i] = sh.target_output_mode[i];
|
||||
ps_info.target_export_mapping[i] = sh.target_output_mode[i] != 0
|
||||
? target_export_mapping[i]
|
||||
ps_info.target_export_mapping[i] = sh.target_output_mode[i] != 0 && slot < 8
|
||||
? target_export_mapping[slot]
|
||||
: Prospero::ColorComponentMapping {};
|
||||
}
|
||||
}
|
||||
@@ -649,6 +654,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(info.ps_system_input_base);
|
||||
key.push_back(info.custom_interpolation_mask);
|
||||
key.push_back(info.ps_perspective_center_vgpr);
|
||||
key.push_back(info.ps_perspective_sample_vgpr);
|
||||
key.push_back(info.ps_perspective_centroid_vgpr);
|
||||
key.push_back(static_cast<uint32_t>(info.ps_pos_x));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_pos_y));
|
||||
@@ -657,6 +663,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(static_cast<uint32_t>(info.ps_front_face));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_ancillary));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_no_perspective));
|
||||
key.push_back(static_cast<uint32_t>(info.parameter_mode));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_pixel_kill_enable));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_depth_export_enable));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_sample_mask_export_enable));
|
||||
@@ -664,6 +671,8 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(static_cast<uint32_t>(info.dual_source_blending));
|
||||
key.push_back(static_cast<uint32_t>(info.alpha_blend_source));
|
||||
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
|
||||
const auto mask = info.target_shader_mask;
|
||||
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
|
||||
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
|
||||
uint32_t packed = 0;
|
||||
for (uint32_t i = 0; i < 4u; i++) {
|
||||
@@ -697,11 +706,14 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
const auto& sh = context.GetShaderRegisters();
|
||||
const auto [data, hash] = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoVS():");
|
||||
const bool merged = (context.GetShaderStages() & 0x20u) != 0;
|
||||
// GS_EN controls amplification, not whether an NGG shader uses a workgroup.
|
||||
const bool native_ngg = !merged && data.type == Prospero::ShaderBinaryType::kGs &&
|
||||
regs.gs_regs.rsrc2.lds_size != 0;
|
||||
auto params = GetShaderParams(
|
||||
regs.es_regs.data_addr, hash,
|
||||
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data,
|
||||
merged ? 8u : 0u);
|
||||
if (!merged) {
|
||||
merged || native_ngg ? 8u : 0u);
|
||||
if (!merged && !native_ngg) {
|
||||
if (!ShaderGetStaticVertexInputInfo(regs.es_regs.data_addr, regs.gs_user_sgpr,
|
||||
regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) {
|
||||
EXIT("failed to prepare vertex shader program\n");
|
||||
@@ -746,7 +758,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
group.primitive_group_size != 1u || group.vertex_group_size != 1u ||
|
||||
user_vgpr.vgpr1 || user_vgpr.vgpr2 || user_vgpr.vgpr3 ||
|
||||
mesh.max_vertices != sh.m_vgtGsMaxVertOut);
|
||||
} else {
|
||||
} else if (merged) {
|
||||
EXIT_NOT_IMPLEMENTED(regs.gs_regs.rsrc1.gs_vgpr_component_count != 3u ||
|
||||
regs.gs_regs.rsrc2.es_vgpr_component_count != 3u);
|
||||
}
|
||||
@@ -755,7 +767,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriFan &&
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip &&
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriList) ||
|
||||
sh.m_vgtGsOutPrimType != 2u || sh.m_vgtGsMaxVertOut < 3u ||
|
||||
sh.m_vgtGsOutPrimType != 2u || (!native_ngg && sh.m_vgtGsMaxVertOut < 3u) ||
|
||||
group.vertex_group_size < mesh.InputPrimitiveSize() ||
|
||||
mesh.max_vertices == 0u) {
|
||||
EXIT("unsupported GS assembly: input=%u output=%u vertices=%u GE=%u/%u max_output=%u\n",
|
||||
@@ -763,14 +775,16 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
group.primitive_group_size, group.vertex_group_size, mesh.max_vertices);
|
||||
}
|
||||
mesh.max_primitives = mesh.fast_launch ? mesh.max_vertices :
|
||||
native_ngg ? mesh.InputPrimitiveCount(mesh.max_vertices) :
|
||||
group.primitive_group_size * (sh.m_vgtGsMaxVertOut - 2u);
|
||||
mesh.primitives_per_group = std::min({static_cast<uint32_t>(group.primitive_group_size),
|
||||
mesh.InputPrimitiveCount(group.vertex_group_size),
|
||||
mesh.max_vertices / sh.m_vgtGsMaxVertOut});
|
||||
native_ngg ? mesh.max_primitives :
|
||||
mesh.max_vertices / sh.m_vgtGsMaxVertOut});
|
||||
EXIT_IF(mesh.primitives_per_group == 0u);
|
||||
mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group);
|
||||
mesh.threads_num[0] =
|
||||
((mesh.max_vertices + mesh.wave_size - 1u) / mesh.wave_size) * mesh.wave_size;
|
||||
const auto threads = native_ngg && !mesh.fast_launch ? mesh.vertices_per_group : mesh.max_vertices;
|
||||
mesh.threads_num[0] = ((threads + mesh.wave_size - 1u) / mesh.wave_size) * mesh.wave_size;
|
||||
mesh.threads_num[1] = mesh.threads_num[2] = 1u;
|
||||
return params;
|
||||
}
|
||||
@@ -910,6 +924,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) {
|
||||
"\t ps_system_input_base = %u\n"
|
||||
"\t custom_interpolation_mask = 0x%08" PRIx32 "\n"
|
||||
"\t ps_perspective_center_vgpr = %" PRIu32 "\n"
|
||||
"\t ps_perspective_sample_vgpr = %" PRIu32 "\n"
|
||||
"\t ps_perspective_centroid_vgpr = %" PRIu32 "\n"
|
||||
"\t ps_pos_x = %s\n"
|
||||
"\t ps_pos_y = %s\n"
|
||||
@@ -923,7 +938,8 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) {
|
||||
"\t ps_early_z = %s\n"
|
||||
"\t ps_execute_on_noop = %s\n",
|
||||
info.input_num, info.ps_system_input_base, info.custom_interpolation_mask,
|
||||
info.ps_perspective_center_vgpr, info.ps_perspective_centroid_vgpr,
|
||||
info.ps_perspective_center_vgpr, info.ps_perspective_sample_vgpr,
|
||||
info.ps_perspective_centroid_vgpr,
|
||||
info.ps_pos_x ? "true" : "false",
|
||||
info.ps_pos_y ? "true" : "false", info.ps_pos_z ? "true" : "false",
|
||||
info.ps_pos_w ? "true" : "false", info.ps_front_face ? "true" : "false",
|
||||
|
||||
@@ -159,6 +159,7 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
|
||||
};
|
||||
|
||||
enum class ShaderAlphaBlendSource : uint8_t { None, SourceAlpha, SourceAlphaOne, SourceAlphaZero };
|
||||
enum class ShaderPixelParameterMode : uint8_t { FirstVertex, LastVertex, Rectangle };
|
||||
|
||||
struct ShaderPixelInputInfo {
|
||||
uint32_t interpolator_settings[32] = {0};
|
||||
@@ -167,8 +168,10 @@ struct ShaderPixelInputInfo {
|
||||
uint32_t ps_system_input_base = 0;
|
||||
uint32_t custom_interpolation_mask = 0;
|
||||
uint32_t ps_perspective_center_vgpr = UINT32_MAX;
|
||||
uint32_t ps_perspective_sample_vgpr = UINT32_MAX;
|
||||
uint32_t ps_perspective_centroid_vgpr = UINT32_MAX;
|
||||
uint8_t target_output_mode[8] = {};
|
||||
uint32_t target_shader_mask = UINT32_MAX;
|
||||
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
||||
uint32_t scratch_size_dwords = 0;
|
||||
bool ps_pos_x = false;
|
||||
@@ -178,6 +181,7 @@ struct ShaderPixelInputInfo {
|
||||
bool ps_front_face = false;
|
||||
bool ps_ancillary = false;
|
||||
bool ps_no_perspective = false;
|
||||
ShaderPixelParameterMode parameter_mode = ShaderPixelParameterMode::FirstVertex;
|
||||
bool ps_pixel_kill_enable = false;
|
||||
bool ps_depth_export_enable = false;
|
||||
bool ps_sample_mask_export_enable = false;
|
||||
@@ -309,6 +313,7 @@ struct ShaderMappedData {
|
||||
|
||||
void ShaderInit();
|
||||
void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data);
|
||||
uint32_t ShaderPixelExportTarget(uint32_t shader_mask, uint32_t export_index);
|
||||
|
||||
void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info);
|
||||
void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info);
|
||||
|
||||
@@ -11,12 +11,28 @@ constexpr uint32_t PsInputFlatShade = 0x00000400u;
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t ShaderPixelExportTarget(uint32_t shader_mask, uint32_t export_index) {
|
||||
for (uint32_t target = 0; target < 8u; target++) {
|
||||
if (((shader_mask >> (target * 4u)) & 0xfu) == 0u) {
|
||||
continue;
|
||||
}
|
||||
if (export_index == 0u) {
|
||||
return target;
|
||||
}
|
||||
export_index--;
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, uint32_t input) {
|
||||
return input < info.input_num ? info.interpolator_settings[input] & PsInputOffsetMask : input;
|
||||
}
|
||||
|
||||
uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
|
||||
std::span<const uint32_t> active_inputs, uint32_t input) {
|
||||
if (info.parameter_mode != ShaderPixelParameterMode::Rectangle) {
|
||||
return ShaderPixelParameterMappedLocation(info, input);
|
||||
}
|
||||
std::array<bool, 32> used_locations {};
|
||||
for (const auto active_input: active_inputs) {
|
||||
used_locations[ShaderPixelParameterMappedLocation(info, active_input)] = true;
|
||||
@@ -29,8 +45,7 @@ uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
|
||||
auto& location = group_locations[group];
|
||||
if (location == UINT32_MAX) {
|
||||
location = mapped;
|
||||
// Smooth and custom interpolation read the same vertex output. Only
|
||||
// differing flat/smooth rectangle outputs need separate locations.
|
||||
// Rectangle expansion broadcasts flat aliases through separate outputs.
|
||||
if (group_locations[group ^ 1u] != UINT32_MAX) {
|
||||
location = 0;
|
||||
while (location < used_locations.size() && used_locations[location]) {
|
||||
|
||||
+30
-15
@@ -70,6 +70,7 @@ struct File {
|
||||
std::atomic_bool writable;
|
||||
std::atomic_bool append;
|
||||
std::atomic_bool sync_writes;
|
||||
std::atomic_bool close_on_exec;
|
||||
SpecialFile special;
|
||||
Common::Mutex mutex;
|
||||
std::vector<uint8_t> dirents;
|
||||
@@ -449,11 +450,12 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
|
||||
|
||||
EXIT_IF(file == nullptr || file->opened || file->directory);
|
||||
|
||||
file->name = path;
|
||||
file->readable = rw_mode != Common::File::Mode::Write;
|
||||
file->writable = rw_mode != Common::File::Mode::Read;
|
||||
file->append = append;
|
||||
file->sync_writes = fsync || sync || dsync;
|
||||
file->name = path;
|
||||
file->readable = rw_mode != Common::File::Mode::Write;
|
||||
file->writable = rw_mode != Common::File::Mode::Read;
|
||||
file->append = append;
|
||||
file->sync_writes = fsync || sync || dsync;
|
||||
file->close_on_exec = (static_cast<uint32_t>(flags) & 0x00100000u) != 0;
|
||||
|
||||
if (!directory && IsRandomDevice(file->name)) {
|
||||
file->real_name = file->name;
|
||||
@@ -585,6 +587,27 @@ int KYTY_SYSV_ABI KernelClose(int d) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelFcntl(int d, int command, int arg) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (d < DESCRIPTOR_MIN) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
auto* file = g_files->GetFile(d);
|
||||
if (file == nullptr || !file->opened) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 1: return file->close_on_exec.load(std::memory_order_relaxed); // F_GETFD
|
||||
case 2: // F_SETFD, FD_CLOEXEC
|
||||
file->close_on_exec.store((arg & 1) != 0, std::memory_order_relaxed);
|
||||
return OK;
|
||||
default: return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -1138,19 +1161,11 @@ int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb) {
|
||||
auto wt = at;
|
||||
|
||||
if (file->special != SpecialFile::None) {
|
||||
stat.st_mode = 0020644u;
|
||||
stat.st_size = 0;
|
||||
stat.st_blksize = 512;
|
||||
stat.st_blocks = 0;
|
||||
SecToTimespec(&stat.st_atim, at.ToUnix());
|
||||
SecToTimespec(&stat.st_mtim, wt.ToUnix());
|
||||
stat.st_ctim = stat.st_atim;
|
||||
stat.st_birthtim = stat.st_mtim;
|
||||
*sb = stat;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (!file->directory) {
|
||||
} else if (!file->directory) {
|
||||
file->mutex.Lock();
|
||||
|
||||
bool is_invalid = file->f.IsInvalid();
|
||||
|
||||
@@ -59,6 +59,7 @@ std::filesystem::path GetRealFilename(const std::string& mounted_file_name);
|
||||
|
||||
int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode);
|
||||
int KYTY_SYSV_ABI KernelClose(int d);
|
||||
int KYTY_SYSV_ABI KernelFcntl(int d, int command, int arg);
|
||||
int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes);
|
||||
int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offset);
|
||||
int64_t KYTY_SYSV_ABI KernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset);
|
||||
|
||||
+109
-97
@@ -21,6 +21,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
@@ -168,6 +169,7 @@ static bool VirtualRangesOverlap(uint64_t left_start, uint64_t left_size, uint64
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
static uint32_t g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
||||
static callback_func_t g_test_before_backing_map = nullptr;
|
||||
static callback_func_t g_test_backing_read = nullptr;
|
||||
#endif
|
||||
|
||||
#include "memoryAddressSpace.inc"
|
||||
@@ -359,12 +361,6 @@ public:
|
||||
EditUnlocked(start, size, [protection](Range* r) { r->protection = protection; });
|
||||
}
|
||||
|
||||
void SetMemoryType(uint64_t start, uint64_t size, int memory_type) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
EditUnlocked(start, size, [memory_type](Range* r) { r->memory_type = memory_type; });
|
||||
}
|
||||
|
||||
bool Query(uint64_t addr, int flags, Range* out) {
|
||||
EXIT_IF(out == nullptr);
|
||||
|
||||
@@ -702,7 +698,7 @@ public:
|
||||
bool Release(uint64_t start, size_t len, uint64_t* vaddr, uint64_t* size,
|
||||
GpuAccessMode* gpu_mode);
|
||||
bool Map(uint64_t vaddr, uint64_t phys_addr, size_t len, int prot, VirtualMemory::Mode mode,
|
||||
GpuAccessMode gpu_mode);
|
||||
GpuAccessMode gpu_mode, const char* name, int memory_type);
|
||||
bool Unmap(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode,
|
||||
uint64_t* host_vaddr_to_release = nullptr);
|
||||
bool Find(uint64_t vaddr, uint64_t* base_addr, size_t* len, int* prot,
|
||||
@@ -715,7 +711,6 @@ public:
|
||||
void ProtectMapping(uint64_t vaddr, uint64_t size, int prot, VirtualMemory::Mode mode,
|
||||
GpuAccessMode gpu_mode);
|
||||
void SetVirtualRangeName(uint64_t vaddr, uint64_t len, const char* name);
|
||||
void SetVirtualRangeMemoryType(uint64_t vaddr, uint64_t len, int memory_type);
|
||||
|
||||
[[nodiscard]] Common::Mutex& GetMutex() { return m_mutex; }
|
||||
[[nodiscard]] const std::map<uint64_t, AllocatedBlock>& GetPhysicalBlocks() const {
|
||||
@@ -1368,7 +1363,8 @@ bool PhysicalMemory::Release(uint64_t start, size_t len, uint64_t* vaddr, uint64
|
||||
}
|
||||
|
||||
bool PhysicalMemory::Map(uint64_t vaddr, uint64_t phys_addr, size_t len, int prot,
|
||||
VirtualMemory::Mode mode, GpuAccessMode gpu_mode) {
|
||||
VirtualMemory::Mode mode, GpuAccessMode gpu_mode, const char* name,
|
||||
int memory_type) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (len == 0 || UINT64_MAX - phys_addr < len) {
|
||||
@@ -1413,6 +1409,8 @@ bool PhysicalMemory::Map(uint64_t vaddr, uint64_t phys_addr, size_t len, int pro
|
||||
mapping.prot = prot;
|
||||
mapping.mode = mode;
|
||||
mapping.gpu_mode = gpu_mode;
|
||||
mapping.memory_type = memory_type;
|
||||
CopyVirtualRangeName(mapping.name, name);
|
||||
m_mappings.push_back(mapping);
|
||||
if (has_automatic) {
|
||||
RemoveFreeRange(m_automatic_free, phys_addr, len);
|
||||
@@ -2040,16 +2038,6 @@ void FlexibleMemory::SetVirtualRangeName(uint64_t vaddr, uint64_t len, const cha
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalMemory::SetVirtualRangeMemoryType(uint64_t vaddr, uint64_t len, int memory_type) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
for (auto& b: m_mappings) {
|
||||
if (VirtualRangesOverlap(vaddr, len, b.map_vaddr, b.map_size)) {
|
||||
b.memory_type = memory_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FlexibleMemory::Available() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
@@ -2268,12 +2256,9 @@ static bool UnmapPooledBackingTransactional(const std::vector<PooledMemory::Mapp
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t len, int prot,
|
||||
int flags, const char* name) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
|
||||
// The ABI entry points and batch operations hold g_memory_operation_mutex.
|
||||
static int MapFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags,
|
||||
const char* name) {
|
||||
EXIT_NOT_IMPLEMENTED(addr_in_out == nullptr);
|
||||
|
||||
constexpr size_t PAGE_SIZE = 0x4000;
|
||||
@@ -2398,8 +2383,17 @@ int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t le
|
||||
return OK;
|
||||
}
|
||||
|
||||
int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t len, int prot,
|
||||
int flags, const char* name) {
|
||||
PRINT_NAME();
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
return MapFlexibleMemory(addr_in_out, len, prot, flags, name);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMapFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags) {
|
||||
return KernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "");
|
||||
PRINT_NAME();
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
return MapFlexibleMemory(addr_in_out, len, prot, flags, "");
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelSetPrtAperture(int index, void* addr, size_t len) {
|
||||
@@ -2607,11 +2601,8 @@ static int UnmapMemoryRange(uint64_t vaddr, size_t len) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMunmap(uint64_t vaddr, size_t len) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
|
||||
// The ABI entry point and batch operations hold g_memory_operation_mutex.
|
||||
static int UnmapMemory(uint64_t vaddr, size_t len) {
|
||||
LOGF("\t start = 0x%016" PRIx64 "\n"
|
||||
"\t len = 0x%016" PRIx64 "\n",
|
||||
vaddr, len);
|
||||
@@ -2627,6 +2618,12 @@ int KYTY_SYSV_ABI KernelMunmap(uint64_t vaddr, size_t len) {
|
||||
return UnmapMemoryRange(vaddr, len);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMunmap(uint64_t vaddr, size_t len) {
|
||||
PRINT_NAME();
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
return UnmapMemory(vaddr, len);
|
||||
}
|
||||
|
||||
size_t KYTY_SYSV_ABI KernelGetDirectMemorySize() {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -2909,7 +2906,8 @@ static int ReleaseDirectMemoryInternal(int64_t start, size_t len) {
|
||||
for (const auto& removed: metadata_unmapped) {
|
||||
EXIT_IF(!g_physical_memory->Map(removed.map_vaddr, removed.start_addr,
|
||||
removed.map_size, removed.prot, removed.mode,
|
||||
removed.gpu_mode));
|
||||
removed.gpu_mode, removed.name,
|
||||
removed.memory_type));
|
||||
}
|
||||
restore_owner_aliases(owner_unmapped);
|
||||
restore_gpu_aliases();
|
||||
@@ -2980,12 +2978,10 @@ int KYTY_SYSV_ABI KernelCheckedReleaseDirectMemory(int64_t start, size_t len) {
|
||||
return result == KERNEL_ERROR_EACCES ? KERNEL_ERROR_ENOENT : result;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int flags,
|
||||
int64_t direct_memory_start, size_t alignment) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
|
||||
// The ABI entry points and batch operations hold g_memory_operation_mutex.
|
||||
static int MapDirectMemory(void** addr, size_t len, int prot, int flags,
|
||||
int64_t direct_memory_start, size_t alignment, const char* name,
|
||||
int type = -1) {
|
||||
if (addr == nullptr) {
|
||||
return KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
@@ -3036,10 +3032,8 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
|
||||
old.start_addr, old.mode));
|
||||
}
|
||||
EXIT_IF(!g_physical_memory->Map(old.map_vaddr, old.start_addr, old.map_size,
|
||||
old.prot, old.mode, old.gpu_mode));
|
||||
g_physical_memory->SetVirtualRangeName(old.map_vaddr, old.map_size, old.name);
|
||||
g_physical_memory->SetVirtualRangeMemoryType(old.map_vaddr, old.map_size,
|
||||
old.memory_type);
|
||||
old.prot, old.mode, old.gpu_mode, old.name,
|
||||
old.memory_type));
|
||||
}
|
||||
MapGpuRange(in_addr, len);
|
||||
};
|
||||
@@ -3168,7 +3162,11 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
|
||||
return KERNEL_ERROR_ENOMEM;
|
||||
}
|
||||
|
||||
if (!g_physical_memory->Map(out_addr, direct_memory_start, len, prot, mode, gpu_mode)) {
|
||||
PhysicalMemory::AllocatedBlock mapped_block {};
|
||||
g_physical_memory->Find(direct_memory_start, false, &mapped_block);
|
||||
const int memory_type = type == -1 ? mapped_block.memory_type : type;
|
||||
if (!g_physical_memory->Map(out_addr, direct_memory_start, len, prot, mode, gpu_mode, name,
|
||||
memory_type)) {
|
||||
LOGF_COLOR(Log::Color::Red, "\t [Fail]\n");
|
||||
if (replaced_mappings.empty()) {
|
||||
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
|
||||
@@ -3178,18 +3176,16 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
|
||||
return KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
|
||||
PhysicalMemory::AllocatedBlock mapped_block {};
|
||||
g_physical_memory->Find(direct_memory_start, false, &mapped_block);
|
||||
const bool published = !replaced_mappings.empty() || reserved_target
|
||||
? g_virtual_ranges->ReplaceSpan(
|
||||
out_addr, len, replaced_mappings.empty()
|
||||
? VirtualRangeType::Reserved
|
||||
: VirtualRangeType::Direct,
|
||||
direct_memory_start,
|
||||
prot, mapped_block.memory_type, VirtualRangeType::Direct, "")
|
||||
prot, memory_type, VirtualRangeType::Direct, name)
|
||||
: g_virtual_ranges->Add(out_addr, len, direct_memory_start, prot,
|
||||
mapped_block.memory_type,
|
||||
VirtualRangeType::Direct, "");
|
||||
memory_type,
|
||||
VirtualRangeType::Direct, name);
|
||||
if (!published) {
|
||||
GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess;
|
||||
EXIT_IF(!g_physical_memory->Unmap(out_addr, len, &rollback_gpu_mode));
|
||||
@@ -3212,22 +3208,18 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int flags,
|
||||
int64_t direct_memory_start, size_t alignment) {
|
||||
PRINT_NAME();
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
return MapDirectMemory(addr, len, prot, flags, direct_memory_start, alignment, "");
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMapDirectMemory2(void** addr, size_t len, int type, int prot, int flags,
|
||||
int64_t direct_memory_start, size_t alignment) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
|
||||
LOGF("\t type = %d\n", type);
|
||||
|
||||
auto ret = KernelMapDirectMemory(addr, len, prot, flags, direct_memory_start, alignment);
|
||||
if (ret == OK && addr != nullptr && *addr != nullptr) {
|
||||
const auto out_addr = reinterpret_cast<uint64_t>(*addr);
|
||||
g_physical_memory->SetVirtualRangeMemoryType(out_addr, len, type);
|
||||
g_virtual_ranges->SetMemoryType(out_addr, len, type);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return MapDirectMemory(addr, len, prot, flags, direct_memory_start, alignment, "", type);
|
||||
}
|
||||
|
||||
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot) {
|
||||
@@ -3246,11 +3238,11 @@ int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot) {
|
||||
uint64_t mapped = 0;
|
||||
for (const auto& [offset, length]: ranges) {
|
||||
void* address = reinterpret_cast<void*>(vaddr + mapped);
|
||||
const int result = KernelMapDirectMemory2(&address, length, type, prot, 0x10,
|
||||
static_cast<int64_t>(offset), page_size);
|
||||
const int result = MapDirectMemory(&address, length, prot, 0x10,
|
||||
static_cast<int64_t>(offset), page_size, "", type);
|
||||
if (result != OK) {
|
||||
if (mapped != 0) {
|
||||
EXIT_IF(KernelMunmap(vaddr, mapped) != OK);
|
||||
EXIT_IF(UnmapMemory(vaddr, mapped) != OK);
|
||||
}
|
||||
g_physical_memory->RestoreAutomatic(ranges);
|
||||
return result;
|
||||
@@ -3277,13 +3269,7 @@ int KYTY_SYSV_ABI KernelMapNamedDirectMemory(void** addr, size_t len, int prot,
|
||||
return KERNEL_ERROR_ENAMETOOLONG;
|
||||
}
|
||||
|
||||
auto ret = KernelMapDirectMemory(addr, len, prot, flags, direct_memory_start, alignment);
|
||||
if (ret == OK && addr != nullptr) {
|
||||
g_physical_memory->SetVirtualRangeName(reinterpret_cast<uint64_t>(*addr), len, name);
|
||||
g_virtual_ranges->Rename(reinterpret_cast<uint64_t>(*addr), len, name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return MapDirectMemory(addr, len, prot, flags, direct_memory_start, alignment, name);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelIsAddressSanitizerEnabled() {
|
||||
@@ -3389,7 +3375,8 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size) {
|
||||
if (chunk.backend_unmapped) {
|
||||
backend_restored = g_physical_memory->Map(
|
||||
chunk.range.start, chunk.range.offset, chunk.range.size,
|
||||
chunk.range.protection, chunk.mode, chunk.gpu_mode);
|
||||
chunk.range.protection, chunk.mode, chunk.gpu_mode, chunk.range.name,
|
||||
chunk.range.memory_type);
|
||||
}
|
||||
} else if (chunk.range.type == VirtualRangeType::Flexible && chunk.backend_unmapped) {
|
||||
host_restored = backend_restored =
|
||||
@@ -3584,6 +3571,10 @@ void TestBeforeNextBackingMap(callback_func_t callback) {
|
||||
g_test_before_backing_map = callback;
|
||||
}
|
||||
|
||||
void TestSetBackingReadCallback(callback_func_t callback) {
|
||||
g_test_backing_read = callback;
|
||||
}
|
||||
|
||||
void TestFailNextPhysicalMemoryUnmap() {
|
||||
TestFailPhysicalMemoryUnmapAfter(0);
|
||||
}
|
||||
@@ -3621,6 +3612,36 @@ uint64_t TestGuestBackingSize() {
|
||||
return g_guest_address_space->GetBackingSize();
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool TestWindowsBackingViewModes() {
|
||||
constexpr uint64_t size = 0x10000;
|
||||
GuestBackingStore backing(size);
|
||||
void* placeholder = VirtualAlloc2(GetCurrentProcess(), nullptr, size,
|
||||
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS,
|
||||
nullptr, 0);
|
||||
if (placeholder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto address = reinterpret_cast<uint64_t>(placeholder);
|
||||
bool valid = true;
|
||||
for (auto [mode, protection]: std::array {
|
||||
std::pair {VirtualMemory::Mode::NoAccess, DWORD {PAGE_NOACCESS}},
|
||||
std::pair {VirtualMemory::Mode::Read, DWORD {PAGE_READONLY}},
|
||||
std::pair {VirtualMemory::Mode::ReadWrite, DWORD {PAGE_READWRITE}},
|
||||
std::pair {VirtualMemory::Mode::ExecuteReadWrite, DWORD {PAGE_EXECUTE_READWRITE}}}) {
|
||||
if (!backing.MapFixed(address, size, 0, mode)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
MEMORY_BASIC_INFORMATION info {};
|
||||
valid = VirtualQuery(placeholder, &info, sizeof(info)) != 0 &&
|
||||
info.Type == MEM_MAPPED && info.Protect == protection && valid;
|
||||
EXIT_IF(!backing.Unmap(address, size));
|
||||
}
|
||||
return VirtualFree(placeholder, 0, MEM_RELEASE) != 0 && valid;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool TestGuestFreeRangeBounds() {
|
||||
return GuestFreeRangeContains(0x10000, 0x20000, 0x18000, 0x4000) &&
|
||||
!GuestFreeRangeContains(0x10000, 0x20000, 0x40000, 0x4000) &&
|
||||
@@ -3875,11 +3896,8 @@ bool FreeGuestMemory(uint64_t vaddr, uint64_t size) {
|
||||
return FreeGuestMemoryOwner(vaddr, mapped_size);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMprotect(const void* addr, size_t len, int prot) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
|
||||
// The ABI entry points and batch operations hold g_memory_operation_mutex.
|
||||
static int ProtectMemory(const void* addr, size_t len, int prot) {
|
||||
auto vaddr = reinterpret_cast<uint64_t>(addr);
|
||||
|
||||
LOGF("\t addr = 0x%016" PRIx64 "\n"
|
||||
@@ -3937,19 +3955,17 @@ int KYTY_SYSV_ABI KernelMprotect(const void* addr, size_t len, int prot) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMprotect(const void* addr, size_t len, int prot) {
|
||||
PRINT_NAME();
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
return ProtectMemory(addr, len, prot);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelMtypeprotect(const void* addr, size_t len, int type, int prot) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
|
||||
|
||||
LOGF("\t addr = 0x%016" PRIx64 "\n"
|
||||
"\t len = 0x%016" PRIx64 "\n"
|
||||
"\t type = 0x%08" PRIx32 "\n"
|
||||
"\t prot = 0x%08" PRIx32 "\n",
|
||||
reinterpret_cast<uint64_t>(addr), static_cast<uint64_t>(len), static_cast<uint32_t>(type),
|
||||
static_cast<uint32_t>(prot));
|
||||
|
||||
return KernelMprotect(addr, len, prot);
|
||||
LOGF("\t type = %d\n", type);
|
||||
return ProtectMemory(addr, len, prot);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelBatchMap2(KernelBatchMapEntry* entries, int num_entries,
|
||||
@@ -3996,23 +4012,19 @@ int KYTY_SYSV_ABI KernelBatchMap2(KernelBatchMapEntry* entries, int num_entries,
|
||||
int result = OK;
|
||||
switch (entry->operation) {
|
||||
case MAP_OP_MAP_DIRECT:
|
||||
result = KernelMapNamedDirectMemory(&entry->start, entry->length, entry->protection,
|
||||
flags, static_cast<int64_t>(entry->offset), 0,
|
||||
"anon");
|
||||
result = MapDirectMemory(&entry->start, entry->length, entry->protection, flags,
|
||||
static_cast<int64_t>(entry->offset), 0, "anon");
|
||||
break;
|
||||
case MAP_OP_UNMAP:
|
||||
result = KernelMunmap(reinterpret_cast<uint64_t>(entry->start), entry->length);
|
||||
result = UnmapMemory(reinterpret_cast<uint64_t>(entry->start), entry->length);
|
||||
break;
|
||||
case MAP_OP_PROTECT:
|
||||
result = KernelMprotect(entry->start, entry->length, entry->protection);
|
||||
case MAP_OP_TYPE_PROTECT:
|
||||
result = ProtectMemory(entry->start, entry->length, entry->protection);
|
||||
break;
|
||||
case MAP_OP_MAP_FLEXIBLE:
|
||||
result = KernelMapNamedFlexibleMemory(&entry->start, entry->length,
|
||||
entry->protection, flags, "anon");
|
||||
break;
|
||||
case MAP_OP_TYPE_PROTECT:
|
||||
result =
|
||||
KernelMtypeprotect(entry->start, entry->length, entry->type, entry->protection);
|
||||
result = MapFlexibleMemory(&entry->start, entry->length, entry->protection, flags,
|
||||
"anon");
|
||||
break;
|
||||
default: result = KERNEL_ERROR_EINVAL; break;
|
||||
}
|
||||
|
||||
@@ -192,6 +192,7 @@ bool FreeGuestMemory(uint64_t vaddr, uint64_t size);
|
||||
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
void TestBeforeNextBackingMap(callback_func_t callback);
|
||||
void TestSetBackingReadCallback(callback_func_t callback);
|
||||
void TestFailNextPhysicalMemoryUnmap();
|
||||
void TestFailPhysicalMemoryUnmapAfter(uint32_t successful_unmaps);
|
||||
void TestFailGuestBackingStoreUnmapAfter(uint32_t successful_unmaps);
|
||||
|
||||
@@ -161,30 +161,90 @@ public:
|
||||
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::memset(m_backing_base + backing_offset, 0, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) {
|
||||
if (data == nullptr) {
|
||||
if (!IsAvailable() || data == nullptr || size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
const auto end = vaddr + size;
|
||||
auto first = m_maps.upper_bound(vaddr);
|
||||
if (first == m_maps.begin()) {
|
||||
return false;
|
||||
}
|
||||
--first;
|
||||
auto current = vaddr;
|
||||
for (auto mapping = first; current < end; ++mapping) {
|
||||
if (mapping == m_maps.end() || current < mapping->second.vaddr ||
|
||||
current >= mapping->second.vaddr + mapping->second.size) {
|
||||
return false;
|
||||
}
|
||||
const auto& entry = mapping->second;
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
||||
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
|
||||
return false;
|
||||
}
|
||||
current += bytes;
|
||||
}
|
||||
|
||||
const auto* source = static_cast<const uint8_t*>(data);
|
||||
return TryTransferBacking(vaddr, size,
|
||||
[source](uint8_t* backing, uint64_t data_offset, size_t bytes) {
|
||||
std::memcpy(backing, source + data_offset, bytes);
|
||||
});
|
||||
current = vaddr;
|
||||
for (auto mapping = first; current < end; ++mapping) {
|
||||
const auto& entry = mapping->second;
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
||||
const auto offset = entry.backing_offset + current - entry.vaddr;
|
||||
std::memcpy(m_backing_base + offset, source + (current - vaddr), bytes);
|
||||
current += bytes;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) {
|
||||
if (data == nullptr) {
|
||||
if (!IsAvailable() || data == nullptr || size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_lock lock(m_mutex);
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
if (g_test_backing_read != nullptr) {
|
||||
g_test_backing_read(vaddr, size);
|
||||
}
|
||||
#endif
|
||||
const auto end = vaddr + size;
|
||||
auto first = m_maps.upper_bound(vaddr);
|
||||
if (first == m_maps.begin()) {
|
||||
return false;
|
||||
}
|
||||
--first;
|
||||
auto current = vaddr;
|
||||
for (auto mapping = first; current < end; ++mapping) {
|
||||
if (mapping == m_maps.end() || current < mapping->second.vaddr ||
|
||||
current >= mapping->second.vaddr + mapping->second.size) {
|
||||
return false;
|
||||
}
|
||||
const auto& entry = mapping->second;
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
||||
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
|
||||
return false;
|
||||
}
|
||||
current += bytes;
|
||||
}
|
||||
|
||||
auto* destination = static_cast<uint8_t*>(data);
|
||||
return TryTransferBacking(
|
||||
vaddr, size, [destination](uint8_t* backing, uint64_t data_offset, size_t bytes) {
|
||||
std::memcpy(destination + data_offset, backing, bytes);
|
||||
});
|
||||
current = vaddr;
|
||||
for (auto mapping = first; current < end; ++mapping) {
|
||||
const auto& entry = mapping->second;
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
||||
const auto offset = entry.backing_offset + current - entry.vaddr;
|
||||
std::memcpy(destination + (current - vaddr), m_backing_base + offset, bytes);
|
||||
current += bytes;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
|
||||
@@ -192,29 +252,24 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
const auto end = vaddr + size;
|
||||
auto first = m_maps.lower_bound(vaddr);
|
||||
if (first != m_maps.begin()) {
|
||||
auto previous = std::prev(first);
|
||||
if (previous->second.vaddr + previous->second.size > vaddr) {
|
||||
first = previous;
|
||||
}
|
||||
std::shared_lock lock(m_mutex);
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
if (g_test_backing_read != nullptr) {
|
||||
g_test_backing_read(vaddr, size);
|
||||
}
|
||||
for (auto mapping = first; mapping != m_maps.end() && mapping->second.vaddr < end;
|
||||
++mapping) {
|
||||
const auto& entry = mapping->second;
|
||||
const auto start = std::max(vaddr, entry.vaddr);
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
|
||||
if (!RangeOk(entry.backing_offset + start - entry.vaddr, bytes)) {
|
||||
return false;
|
||||
#endif
|
||||
const auto end = vaddr + size;
|
||||
auto mapping = m_maps.lower_bound(vaddr);
|
||||
if (mapping != m_maps.begin()) {
|
||||
auto previous = std::prev(mapping);
|
||||
if (previous->second.vaddr + previous->second.size > vaddr) {
|
||||
mapping = previous;
|
||||
}
|
||||
}
|
||||
|
||||
auto* destination = static_cast<uint8_t*>(data);
|
||||
auto cursor = vaddr;
|
||||
for (auto mapping = first; mapping != m_maps.end() && mapping->second.vaddr < end;
|
||||
++mapping) {
|
||||
for (; mapping != m_maps.end() && mapping->second.vaddr < end; ++mapping) {
|
||||
const auto& entry = mapping->second;
|
||||
const auto start = std::max(vaddr, entry.vaddr);
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
|
||||
@@ -437,7 +492,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
const auto end = vaddr + size;
|
||||
auto current = vaddr;
|
||||
|
||||
@@ -457,39 +512,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Transfer>
|
||||
bool TryTransferBacking(uint64_t vaddr, uint64_t size, Transfer transfer) {
|
||||
if (!IsAvailable() || size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
const auto end = vaddr + size;
|
||||
auto current = vaddr;
|
||||
while (current < end) {
|
||||
MapEntry entry {};
|
||||
if (!FindContainingUnlocked(current, 1, &entry)) {
|
||||
return false;
|
||||
}
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
||||
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
|
||||
return false;
|
||||
}
|
||||
current += bytes;
|
||||
}
|
||||
|
||||
current = vaddr;
|
||||
while (current < end) {
|
||||
MapEntry entry {};
|
||||
EXIT_IF(!FindContainingUnlocked(current, 1, &entry));
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
||||
const auto offset = entry.backing_offset + current - entry.vaddr;
|
||||
transfer(m_backing_base + offset, current - vaddr, static_cast<size_t>(bytes));
|
||||
current += bytes;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct MapEntry {
|
||||
uint64_t vaddr = 0;
|
||||
uint64_t size = 0;
|
||||
@@ -597,7 +619,7 @@ private:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ApplyProtection(ptr, size, mode)) {
|
||||
if (mode == VirtualMemory::Mode::NoAccess && !ApplyProtection(ptr, size, mode)) {
|
||||
UnmapViewOfFile2(GetCurrentProcess(), ptr, MEM_PRESERVE_PLACEHOLDER);
|
||||
SetFailure(failure_reason, FailureReason::ProtectFailed);
|
||||
return nullptr;
|
||||
@@ -656,7 +678,7 @@ private:
|
||||
|
||||
uint8_t* m_backing_base = nullptr;
|
||||
uint64_t m_size = 0;
|
||||
std::mutex m_mutex;
|
||||
std::shared_mutex m_mutex;
|
||||
std::map<uint64_t, MapEntry> m_maps;
|
||||
};
|
||||
|
||||
@@ -727,7 +749,7 @@ public:
|
||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
||||
return vaddr >= range.first && vaddr + size <= range.first + range.second;
|
||||
});
|
||||
@@ -755,7 +777,7 @@ public:
|
||||
if (!Owns(vaddr, size)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
auto mapping = m_mapped.upper_bound(vaddr);
|
||||
if (mapping != m_mapped.begin()) {
|
||||
--mapping;
|
||||
@@ -1381,7 +1403,7 @@ private:
|
||||
#endif
|
||||
}
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::shared_mutex m_mutex;
|
||||
std::map<uint64_t, uint64_t> m_free;
|
||||
std::map<uint64_t, MappedRegion> m_mapped;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> m_owned;
|
||||
|
||||
@@ -198,12 +198,6 @@ KernelSemaPrivate::Result KernelSemaPrivate::Wait(int need_count, uint32_t* ptr_
|
||||
infinitely = false;
|
||||
}
|
||||
|
||||
uint32_t elapsed = 0;
|
||||
Common::Timer t;
|
||||
t.Start();
|
||||
|
||||
int id = Common::Thread::GetThreadIdUnique();
|
||||
|
||||
if (m_count >= need_count) {
|
||||
m_count -= need_count;
|
||||
if (ptr_micros != nullptr) {
|
||||
@@ -216,6 +210,12 @@ KernelSemaPrivate::Result KernelSemaPrivate::Wait(int need_count, uint32_t* ptr_
|
||||
return Result::TimedOut;
|
||||
}
|
||||
|
||||
uint32_t elapsed = 0;
|
||||
Common::Timer t;
|
||||
t.Start();
|
||||
|
||||
int id = Common::Thread::GetThreadIdUnique();
|
||||
|
||||
WaitingThread waiter {};
|
||||
waiter.id = id;
|
||||
waiter.priority = (m_fifo_order ? 0 : Libs::LibKernel::PthreadGetCurrentPriorityForKernel());
|
||||
|
||||
@@ -179,6 +179,18 @@
|
||||
<property name="text"><string>Auto-hide cursor</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_skip_notice_screen">
|
||||
<property name="toolTip"><string>Skip startup logos and notices in supported games.</string></property>
|
||||
<property name="text"><string>Skip boot logos</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_sync_raw_image_buffers">
|
||||
<property name="toolTip"><string>Can fix missing textures and incorrect transparency in some games. May reduce performance.</string></property>
|
||||
<property name="text"><string>Accurate texture synchronization</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -119,8 +119,10 @@ public:
|
||||
bool fullscreen_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
bool skip_notice_screen = false;
|
||||
int vblank_frequency = 60;
|
||||
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -152,8 +154,10 @@ public:
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||
readback_linear_images = other.readback_linear_images;
|
||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||
tessellation_enabled = other.tessellation_enabled;
|
||||
trophy_enabled = other.trophy_enabled;
|
||||
skip_notice_screen = other.skip_notice_screen;
|
||||
vblank_frequency = other.vblank_frequency;
|
||||
console_language = other.console_language;
|
||||
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
||||
|
||||
@@ -12,8 +12,10 @@ QVariantMap Configuration::GameSettings() const {
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
KYTY_CFG_SET(hide_cursor_enabled);
|
||||
KYTY_CFG_SET(readback_linear_images);
|
||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||
KYTY_CFG_SET(tessellation_enabled);
|
||||
KYTY_CFG_SET(trophy_enabled);
|
||||
KYTY_CFG_SET(skip_notice_screen);
|
||||
KYTY_CFG_SET(vblank_frequency);
|
||||
KYTY_CFG_SET(console_language);
|
||||
KYTY_CFG_SET(vulkan_validation_enabled);
|
||||
@@ -55,8 +57,10 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
KYTY_CFG_GET(fullscreen_enabled);
|
||||
KYTY_CFG_GET(hide_cursor_enabled);
|
||||
KYTY_CFG_GET(readback_linear_images);
|
||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||
KYTY_CFG_GET(tessellation_enabled);
|
||||
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
|
||||
KYTY_CFG_GET(skip_notice_screen);
|
||||
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
|
||||
console_language = s.value("console_language", console_language).toInt();
|
||||
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
|
||||
|
||||
@@ -275,7 +275,9 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
|
||||
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
||||
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
|
||||
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
|
||||
m_ui->comboBox_console_language->clear();
|
||||
@@ -434,7 +436,9 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
|
||||
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
||||
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
|
||||
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
|
||||
info.console_language = ui.comboBox_console_language->currentIndex();
|
||||
|
||||
@@ -245,7 +245,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
args << "--hide-cursor";
|
||||
}
|
||||
args << "--readback-linear-images" << BoolArg(info.readback_linear_images);
|
||||
args << "--sync-raw-image-buffers" << BoolArg(info.sync_raw_image_buffers);
|
||||
args << "--trophy-notifications" << BoolArg(info.trophy_enabled);
|
||||
args << "--skip-notice-screen" << BoolArg(info.skip_notice_screen);
|
||||
if (info.tessellation_enabled) {
|
||||
args << "--tessellation";
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileInfo>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QImage>
|
||||
#include <QLabel>
|
||||
@@ -18,6 +19,7 @@
|
||||
#include <QPainterPath>
|
||||
#include <QPixmap>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSize>
|
||||
#include <QStringList>
|
||||
@@ -25,6 +27,8 @@
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
static const QRegularExpression TrophyFilePattern(QStringLiteral("^trophy(\\d+)\\.ucp$"));
|
||||
@@ -54,17 +58,115 @@ static QString GradeToText(int grade) {
|
||||
}
|
||||
}
|
||||
|
||||
static QString TrophyTooltip(const Common::Trophies::Trophy& trophy) {
|
||||
QStringList lines {QString::fromStdString(trophy.name),
|
||||
QString::fromStdString(trophy.description),
|
||||
QObject::tr("Grade: %1").arg(GradeToText(trophy.grade))};
|
||||
if (trophy.hidden) {
|
||||
lines.append(QObject::tr("Hidden trophy"));
|
||||
QString TrophyName(int id, const Common::Trophies::Trophy& trophy, bool masked) {
|
||||
return masked ? QObject::tr("Hidden trophy")
|
||||
: trophy.name.empty() ? QObject::tr("Trophy %1").arg(id)
|
||||
: QString::fromStdString(trophy.name);
|
||||
}
|
||||
|
||||
QPixmap TrophyIcon(const Common::Trophies::Trophy& trophy, bool unlocked, bool masked, int size) {
|
||||
QPixmap pixmap;
|
||||
if (masked) {
|
||||
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
|
||||
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
|
||||
static_cast<uint>(trophy.icon_png.size())) &&
|
||||
!unlocked) {
|
||||
auto image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
|
||||
for (int y = 0; y < image.height(); ++y) {
|
||||
auto* line = reinterpret_cast<QRgb*>(image.scanLine(y));
|
||||
for (int x = 0; x < image.width(); ++x) {
|
||||
const int gray = qGray(line[x]);
|
||||
line[x] = qRgba(gray, gray, gray, qAlpha(line[x]));
|
||||
}
|
||||
}
|
||||
pixmap = QPixmap::fromImage(image);
|
||||
}
|
||||
if (trophy.has_reward && !trophy.reward.empty()) {
|
||||
lines.append(QObject::tr("Reward: %1").arg(QString::fromStdString(trophy.reward)));
|
||||
return pixmap.isNull()
|
||||
? pixmap
|
||||
: pixmap.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
QString TrophyEarnedDate(int id, const Common::Trophies::UnlockData& unlocks) {
|
||||
const auto found = unlocks.timestamps.find(id);
|
||||
if (found == unlocks.timestamps.end()) {
|
||||
return {};
|
||||
}
|
||||
return lines.join(QLatin1Char('\n'));
|
||||
return QDateTime::fromMSecsSinceEpoch(
|
||||
static_cast<qint64>((found->second - Common::Trophies::UnixEpochTick) / 1000))
|
||||
.toLocalTime()
|
||||
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
void ShowTrophyInspector(QWidget* parent, int id, const Common::Trophies::Trophy& trophy,
|
||||
bool unlocked, const QString& earned_date) {
|
||||
QDialog dialog(parent);
|
||||
dialog.setWindowTitle(QObject::tr("Trophy Details"));
|
||||
dialog.resize(640, 380);
|
||||
auto* layout = new QVBoxLayout(&dialog);
|
||||
auto* top = new QHBoxLayout;
|
||||
auto* icon = new QLabel(&dialog);
|
||||
icon->setFixedSize(148, 148);
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
auto* text_box = new QVBoxLayout;
|
||||
auto* name = new QLabel(&dialog);
|
||||
name->setTextFormat(Qt::PlainText);
|
||||
name->setStyleSheet(QStringLiteral("font-size: 26px;"));
|
||||
name->setWordWrap(true);
|
||||
auto* subtitle = new QLabel(QObject::tr("Hidden trophy"), &dialog);
|
||||
subtitle->setStyleSheet(QStringLiteral("font-size: 16px;"));
|
||||
auto* reveal = new QPushButton(QObject::tr("Show Hidden Trophy"), &dialog);
|
||||
text_box->addWidget(name);
|
||||
text_box->addWidget(subtitle);
|
||||
text_box->addWidget(reveal, 0, Qt::AlignLeft);
|
||||
text_box->addStretch(1);
|
||||
top->addWidget(icon);
|
||||
top->addSpacing(16);
|
||||
top->addLayout(text_box, 1);
|
||||
layout->addLayout(top);
|
||||
|
||||
auto* grid = new QGridLayout;
|
||||
grid->setColumnStretch(1, 1);
|
||||
const auto make_row = [&](const QString& label, const QString& text = {}) {
|
||||
auto* key = new QLabel(label, &dialog);
|
||||
auto* value = new QLabel(text, &dialog);
|
||||
key->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||
value->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||
value->setTextFormat(Qt::PlainText);
|
||||
value->setWordWrap(true);
|
||||
const int row = grid->rowCount();
|
||||
grid->addWidget(key, row, 0);
|
||||
grid->addWidget(value, row, 1);
|
||||
return value;
|
||||
};
|
||||
auto* grade = make_row(QObject::tr("Grade"));
|
||||
make_row(QObject::tr("Status"), unlocked ? QObject::tr("Earned") : QObject::tr("Not earned"));
|
||||
make_row(QObject::tr("Earned date"),
|
||||
unlocked && !earned_date.isEmpty() ? earned_date : QStringLiteral("-"));
|
||||
auto* details = make_row(QObject::tr("Details"));
|
||||
auto* reward =
|
||||
trophy.has_reward && !trophy.reward.empty() ? make_row(QObject::tr("Reward")) : nullptr;
|
||||
layout->addLayout(grid);
|
||||
layout->addStretch(1);
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
|
||||
QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
const auto refresh = [&](bool masked) {
|
||||
icon->setPixmap(TrophyIcon(trophy, unlocked, masked, 148));
|
||||
name->setText(TrophyName(id, trophy, masked));
|
||||
subtitle->setVisible(trophy.hidden && !masked);
|
||||
reveal->setVisible(masked);
|
||||
grade->setText(masked ? QObject::tr("Hidden trophy") : GradeToText(trophy.grade));
|
||||
details->setText(masked || trophy.description.empty()
|
||||
? QStringLiteral("-")
|
||||
: QString::fromStdString(trophy.description));
|
||||
if (reward != nullptr) {
|
||||
reward->setText(masked ? QStringLiteral("-") : QString::fromStdString(trophy.reward));
|
||||
}
|
||||
};
|
||||
QObject::connect(reveal, &QPushButton::clicked, &dialog, [&] { refresh(false); });
|
||||
refresh(trophy.hidden && !unlocked);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -337,13 +439,13 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
}
|
||||
QStringList errors;
|
||||
for (const auto& file: trophy_files) {
|
||||
const auto package =
|
||||
auto package =
|
||||
Common::Trophies::LoadPackage(GameContent::ToPath(file), info.console_language);
|
||||
if (package.trophies.empty()) {
|
||||
errors.append(tr("Could not read trophy package %1.").arg(QFileInfo(file).fileName()));
|
||||
continue;
|
||||
}
|
||||
const auto unlocks = LoadUnlocks(info, runtime_directory, file);
|
||||
auto unlocks = LoadUnlocks(info, runtime_directory, file);
|
||||
const auto counts = Common::Trophies::GetProgress(package, unlocks);
|
||||
auto* page = new QWidget(m_tabs);
|
||||
auto* page_layout = new QVBoxLayout(page);
|
||||
@@ -395,8 +497,10 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
const auto hidden_locked = trophy.hidden && !trophy_unlocked;
|
||||
auto* item = new QListWidgetItem(list);
|
||||
item->setSizeHint(QSize(900, 96));
|
||||
item->setData(Qt::UserRole, id);
|
||||
|
||||
auto* card = new QWidget(list);
|
||||
card->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
card->setStyleSheet(
|
||||
QStringLiteral("QWidget#card { background: palette(base); border-radius: 12px; }"));
|
||||
card->setObjectName(QStringLiteral("card"));
|
||||
@@ -404,26 +508,12 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
auto* icon = new QLabel(card);
|
||||
icon->setFixedSize(76, 76);
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
QPixmap pixmap;
|
||||
if (hidden_locked) {
|
||||
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
|
||||
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
|
||||
static_cast<uint>(trophy.icon_png.size())) &&
|
||||
!trophy_unlocked) {
|
||||
pixmap =
|
||||
QPixmap::fromImage(pixmap.toImage().convertToFormat(QImage::Format_Grayscale8));
|
||||
}
|
||||
if (!pixmap.isNull()) {
|
||||
icon->setPixmap(
|
||||
pixmap.scaled(76, 76, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
icon->setPixmap(TrophyIcon(trophy, trophy_unlocked, hidden_locked, 76));
|
||||
row->addWidget(icon);
|
||||
|
||||
auto* left = new QVBoxLayout;
|
||||
const auto name_text = hidden_locked ? tr("Hidden trophy")
|
||||
: trophy.name.empty() ? tr("Trophy %1").arg(id)
|
||||
: QString::fromStdString(trophy.name);
|
||||
auto* name = new QLabel(name_text, card);
|
||||
auto* left = new QVBoxLayout;
|
||||
auto* name = new QLabel(TrophyName(id, trophy, hidden_locked), card);
|
||||
name->setTextFormat(Qt::PlainText);
|
||||
name->setStyleSheet(QStringLiteral("font-size: 20px; font-weight: bold;"));
|
||||
left->addWidget(name);
|
||||
if (!hidden_locked) {
|
||||
@@ -440,15 +530,9 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
|
||||
row->addLayout(left, 2);
|
||||
|
||||
auto* right = new QVBoxLayout;
|
||||
QString date_text;
|
||||
if (const auto found = unlocks.timestamps.find(id); found != unlocks.timestamps.end()) {
|
||||
date_text = QDateTime::fromMSecsSinceEpoch(
|
||||
static_cast<qint64>(
|
||||
(found->second - Common::Trophies::UnixEpochTick) / 1000))
|
||||
.toLocalTime()
|
||||
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
|
||||
} else if (trophy_unlocked) {
|
||||
auto* right = new QVBoxLayout;
|
||||
auto date_text = TrophyEarnedDate(id, unlocks);
|
||||
if (date_text.isEmpty() && trophy_unlocked) {
|
||||
date_text = tr("Earned");
|
||||
}
|
||||
auto* date = new QLabel(date_text, card);
|
||||
@@ -456,15 +540,22 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
date->setStyleSheet(QStringLiteral("font-size: 15px;"));
|
||||
auto* detail = new QLabel(
|
||||
hidden_locked ? QString {} : QString::fromStdString(trophy.description), card);
|
||||
detail->setTextFormat(Qt::PlainText);
|
||||
detail->setWordWrap(true);
|
||||
detail->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
detail->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||
right->addWidget(date);
|
||||
right->addWidget(detail, 1);
|
||||
row->addLayout(right, 3);
|
||||
card->setToolTip(hidden_locked ? name_text : TrophyTooltip(trophy));
|
||||
list->setItemWidget(item, card);
|
||||
}
|
||||
connect(list, &QListWidget::itemClicked, this,
|
||||
[this, trophies = std::move(package.trophies),
|
||||
unlocks = std::move(unlocks)](QListWidgetItem* item) {
|
||||
const int id = item->data(Qt::UserRole).toInt();
|
||||
ShowTrophyInspector(this, id, trophies.at(id), unlocks.unlocked.contains(id),
|
||||
TrophyEarnedDate(id, unlocks));
|
||||
});
|
||||
m_tabs->addTab(page, QString::fromStdString(package.title));
|
||||
}
|
||||
m_tabs->tabBar()->setVisible(m_tabs->count() > 1);
|
||||
|
||||
@@ -2388,6 +2388,12 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegistersIndirect(CommandBuffer*
|
||||
return cmd;
|
||||
}
|
||||
|
||||
uint64_t KYTY_SYSV_ABI AgcDcbSetShRegistersIndirectGetSize() {
|
||||
PRINT_NAME();
|
||||
|
||||
return 5u * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegistersIndirect(CommandBuffer* buf,
|
||||
const volatile ShaderRegister* regs,
|
||||
uint32_t num_regs) {
|
||||
@@ -3211,6 +3217,14 @@ uint32_t KYTY_SYSV_ABI AgcAcbJumpGetSize() {
|
||||
return 0x10u;
|
||||
}
|
||||
|
||||
uint32_t* KYTY_SYSV_ABI AgcAcbRewind(CommandBuffer* buf, uint8_t initial_state, uint8_t offload) {
|
||||
auto* cmd = AgcDcbRewind(buf, initial_state);
|
||||
if (cmd != nullptr) {
|
||||
cmd[1] |= (static_cast<uint32_t>(offload & 0x1u) << 24u);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8_t compare_function,
|
||||
uint8_t cache_policy, const volatile void* address,
|
||||
uint64_t reference, uint64_t mask, uint32_t poll_cycles) {
|
||||
|
||||
@@ -137,6 +137,7 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetCxRegistersIndirect(CommandBuffer*
|
||||
uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegistersIndirect(CommandBuffer* buf,
|
||||
const volatile ShaderRegister* regs,
|
||||
uint32_t num_regs);
|
||||
uint64_t KYTY_SYSV_ABI AgcDcbSetShRegistersIndirectGetSize();
|
||||
uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegistersIndirect(CommandBuffer* buf,
|
||||
const volatile ShaderRegister* regs,
|
||||
uint32_t num_regs);
|
||||
@@ -205,6 +206,7 @@ uint32_t KYTY_SYSV_ABI AgcAcbCondExecGetSize();
|
||||
uint32_t* KYTY_SYSV_ABI AgcAcbJump(CommandBuffer* buf, uint8_t cache_policy,
|
||||
const uint32_t* target, uint32_t size_in_dwords);
|
||||
uint32_t KYTY_SYSV_ABI AgcAcbJumpGetSize();
|
||||
uint32_t* KYTY_SYSV_ABI AgcAcbRewind(CommandBuffer* buf, uint8_t initial_state, uint8_t offload);
|
||||
uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8_t compare_function,
|
||||
uint8_t cache_policy, const volatile void* address,
|
||||
uint64_t reference, uint64_t mask, uint32_t poll_cycles);
|
||||
|
||||
+25
-29
@@ -15,7 +15,6 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <magic_enum.hpp>
|
||||
#include <vector>
|
||||
|
||||
@@ -90,8 +89,9 @@ public:
|
||||
};
|
||||
|
||||
struct OutputParam {
|
||||
Id handle;
|
||||
const void* data = nullptr;
|
||||
Id handle;
|
||||
const void* data = nullptr;
|
||||
const float* gains = nullptr;
|
||||
};
|
||||
|
||||
Audio() = default;
|
||||
@@ -159,8 +159,10 @@ private:
|
||||
static void OpenSdlDevice(PortIn* port, Format format);
|
||||
static void CloseSdlDevice(PortIn* port);
|
||||
static const void* PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
std::vector<uint8_t>* buffer, float gain);
|
||||
static bool QueueSdlAudio(PortOut* port, const void* data, bool blocking, float gain);
|
||||
std::vector<uint8_t>* buffer, float gain,
|
||||
const float* gains);
|
||||
static bool QueueSdlAudio(PortOut* port, const void* data, bool blocking, float gain,
|
||||
const float* gains);
|
||||
};
|
||||
|
||||
static Audio* g_audio = nullptr;
|
||||
@@ -196,7 +198,7 @@ uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking)
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
if (params[i].handle > 0 && params[i].data != nullptr) {
|
||||
output_params.push_back(
|
||||
Audio::OutputParam {Audio::Id(params[i].handle), params[i].data});
|
||||
Audio::OutputParam {Audio::Id(params[i].handle), params[i].data, params[i].gains});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +304,8 @@ void Audio::CloseSdlDevice(PortOut* port) {
|
||||
}
|
||||
|
||||
const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
std::vector<uint8_t>* buffer, float gain) {
|
||||
std::vector<uint8_t>* buffer, float gain,
|
||||
const float* gains) {
|
||||
EXIT_IF(data == nullptr);
|
||||
EXIT_IF(buffer == nullptr);
|
||||
|
||||
@@ -312,12 +315,12 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
const auto bytes_per_sample = BytesPerSample(port.format);
|
||||
const bool reorder = channels >= 8 && !FormatIsStd(port.format);
|
||||
|
||||
bool volume_changed = gain != 1.0f;
|
||||
std::array<double, 12> scales {};
|
||||
bool volume_changed = false;
|
||||
for (uint32_t ch = 0; ch < channels; ch++) {
|
||||
if (port.volume[ch] != 32768) {
|
||||
volume_changed = true;
|
||||
break;
|
||||
}
|
||||
scales[ch] = (static_cast<double>(port.volume[ch]) / 32768.0) * gain *
|
||||
(gains != nullptr ? gains[ch] : 1.0f);
|
||||
volume_changed |= scales[ch] != 1.0;
|
||||
}
|
||||
|
||||
if (!volume_changed && !reorder) {
|
||||
@@ -337,8 +340,7 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
for (uint32_t ch = 0; ch < output_channels; ch++) {
|
||||
const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch;
|
||||
dst[frame * output_channels + ch] =
|
||||
src[frame * channels + src_ch] *
|
||||
(static_cast<float>(port.volume[src_ch]) / 32768.0f) * gain;
|
||||
static_cast<float>(src[frame * channels + src_ch] * scales[src_ch]);
|
||||
}
|
||||
if (channels == 12) {
|
||||
// Add the four top channels to their front/back channels, turning 12 into 8.
|
||||
@@ -346,8 +348,7 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
static constexpr uint32_t HEIGHT_DST[4] = {0, 1, 4, 5};
|
||||
for (uint32_t ch = 0; ch < 4; ch++) {
|
||||
dst[frame * output_channels + HEIGHT_DST[ch]] +=
|
||||
src[frame * channels + 8 + ch] *
|
||||
(static_cast<float>(port.volume[8 + ch]) / 32768.0f) * gain;
|
||||
static_cast<float>(src[frame * channels + 8 + ch] * scales[8 + ch]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,15 +359,9 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
for (uint32_t frame = 0; frame < frames; frame++) {
|
||||
for (uint32_t ch = 0; ch < output_channels; ch++) {
|
||||
const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch;
|
||||
int64_t sample =
|
||||
static_cast<int64_t>(src[frame * channels + src_ch]) * port.volume[src_ch] / 32768;
|
||||
sample = static_cast<int64_t>(sample * static_cast<double>(gain));
|
||||
if (sample > std::numeric_limits<int16_t>::max()) {
|
||||
sample = std::numeric_limits<int16_t>::max();
|
||||
} else if (sample < std::numeric_limits<int16_t>::min()) {
|
||||
sample = std::numeric_limits<int16_t>::min();
|
||||
}
|
||||
dst[frame * output_channels + ch] = static_cast<int16_t>(sample);
|
||||
const double sample = src[frame * channels + src_ch] * scales[src_ch];
|
||||
dst[frame * output_channels + ch] = static_cast<int16_t>(
|
||||
std::clamp(sample, -32768.0, 32767.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,7 +369,8 @@ const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data,
|
||||
return buffer->data();
|
||||
}
|
||||
|
||||
bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking, float gain) {
|
||||
bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking, float gain,
|
||||
const float* gains) {
|
||||
EXIT_IF(port == nullptr);
|
||||
|
||||
if (port->stream == nullptr || data == nullptr) {
|
||||
@@ -382,7 +378,7 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking, float
|
||||
}
|
||||
|
||||
std::vector<uint8_t> prepared_buffer;
|
||||
const void* prepared_data = PrepareOutputBuffer(*port, data, &prepared_buffer, gain);
|
||||
const void* prepared_data = PrepareOutputBuffer(*port, data, &prepared_buffer, gain, gains);
|
||||
const auto output_channels = OutputChannels(*port);
|
||||
const auto prepared_size =
|
||||
BytesPerSample(port->format) * output_channels * port->samples_num;
|
||||
@@ -570,14 +566,14 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
|
||||
controller_queued_us = Controller::DualSenseHaptics::Queue(
|
||||
port.haptics, Controller::GetActiveControllerId(), params[i].data, port.samples_num,
|
||||
static_cast<uint32_t>(port.channels_num), FormatIsFloat(port.format), port.volume,
|
||||
gain);
|
||||
gain, params[i].gains);
|
||||
controller_uses_bluetooth =
|
||||
Controller::DualSenseHaptics::UsesBluetooth(port.haptics);
|
||||
}
|
||||
if (controller_queued_us == 0) {
|
||||
// No DualSense took it (e.g. it was unplugged); a pad speaker port plays on the main
|
||||
// output instead, and a vibration port has none.
|
||||
if (QueueSdlAudio(&port, params[i].data, blocking, gain)) {
|
||||
if (QueueSdlAudio(&port, params[i].data, blocking, gain, params[i].gains)) {
|
||||
any_device = true;
|
||||
paced[i] = port.queue_primed;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ enum class Format {
|
||||
};
|
||||
|
||||
struct OutputParam {
|
||||
int handle = 0;
|
||||
const void* data = nullptr;
|
||||
int handle = 0;
|
||||
const void* data = nullptr;
|
||||
const float* gains = nullptr;
|
||||
};
|
||||
|
||||
static constexpr int OUT_PORTS_MAX = 32;
|
||||
|
||||
+199
-55
@@ -24,8 +24,12 @@ namespace Libs::Controller {
|
||||
|
||||
LIB_NAME("Pad", "Pad");
|
||||
|
||||
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
|
||||
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
|
||||
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
|
||||
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
|
||||
constexpr int PAD_ERROR_ALREADY_OPENED = -2137915388; /* 0x80920004 */
|
||||
constexpr int PAD_ERROR_NOT_INITIALIZED = -2137915387; /* 0x80920005 */
|
||||
constexpr int PAD_ERROR_NO_HANDLE = -2137915384; /* 0x80920008 */
|
||||
constexpr int PAD_HANDLE = 1;
|
||||
|
||||
constexpr uint32_t RUMBLE_DURATION_MS = 0xffff;
|
||||
constexpr uint32_t RELEASE_FLUSH_MS = 50;
|
||||
@@ -67,8 +71,13 @@ struct PadTriggerEffectParam {
|
||||
PadTriggerEffectCommand command[2];
|
||||
};
|
||||
|
||||
struct PadTriggerEffectStateInformation {
|
||||
int32_t state[2];
|
||||
};
|
||||
|
||||
static_assert(sizeof(PadTriggerEffectCommand) == 56);
|
||||
static_assert(sizeof(PadTriggerEffectParam) == 120);
|
||||
static_assert(sizeof(PadTriggerEffectStateInformation) == 8);
|
||||
|
||||
struct DualSenseEffects {
|
||||
uint8_t enable_bits;
|
||||
@@ -102,6 +111,12 @@ public:
|
||||
|
||||
KYTY_CLASS_NO_COPY(GameController);
|
||||
|
||||
int InitPort();
|
||||
int OpenPort(int user_id, int type, int index);
|
||||
int GetPortHandle(int user_id, int type, int index);
|
||||
int ClosePort(int handle);
|
||||
int CheckPortHandle(int handle);
|
||||
|
||||
void Connect(int id);
|
||||
void Disconnect(int id);
|
||||
void Button(int id, uint32_t button, bool down);
|
||||
@@ -118,6 +133,7 @@ public:
|
||||
int GetActiveControllerId();
|
||||
void SetLightBar(uint8_t r, uint8_t g, uint8_t b);
|
||||
bool SetTriggerEffect(const PadTriggerEffectParam& param);
|
||||
void GetTriggerEffectState(PadTriggerEffectStateInformation* info);
|
||||
void CycleSetting(Setting setting);
|
||||
float GetSettingScale(Setting setting) const;
|
||||
void ReadState(ControllerState* state, bool* flag, int* count);
|
||||
@@ -125,14 +141,19 @@ public:
|
||||
|
||||
private:
|
||||
static constexpr uint32_t STATES_MAX = 64;
|
||||
enum class PortState { Uninitialized, Closed, Open };
|
||||
|
||||
int CheckPortHandleLocked(int handle) const;
|
||||
|
||||
void CheckActive();
|
||||
void AddState();
|
||||
// Apply cached output without changing its game-requested lifetime; caller holds m_mutex.
|
||||
void ApplyVibration();
|
||||
bool SendTriggerEffect(const PadTriggerEffectParam& param);
|
||||
void UpdateTriggerEffectState();
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
PortState m_port_state = PortState::Uninitialized;
|
||||
std::vector<int> m_connected_ids;
|
||||
int m_active_id = -1;
|
||||
bool m_connected = false;
|
||||
@@ -152,6 +173,7 @@ private:
|
||||
std::array<uint8_t, 2> m_vibration {};
|
||||
uint64_t m_vibration_until = 0;
|
||||
PadTriggerEffectParam m_trigger_effect {};
|
||||
PadTriggerEffectStateInformation m_trigger_state {};
|
||||
// Setting changes share the output lock; audio only needs an atomic scale snapshot.
|
||||
std::array<std::atomic<uint32_t>, 3> m_setting_steps {};
|
||||
};
|
||||
@@ -330,6 +352,51 @@ static bool trigger_effect_to_dualsense(const PadTriggerEffectCommand& command,
|
||||
}
|
||||
}
|
||||
|
||||
enum TriggerState : int32_t {
|
||||
TRIGGER_STATE_OFF = 0,
|
||||
TRIGGER_STATE_FEEDBACK_STANDBY = 1,
|
||||
TRIGGER_STATE_FEEDBACK_ACTIVE = 2,
|
||||
TRIGGER_STATE_WEAPON_STANDBY = 3,
|
||||
TRIGGER_STATE_WEAPON_PULLING = 4,
|
||||
TRIGGER_STATE_WEAPON_FIRED = 5,
|
||||
TRIGGER_STATE_VIBRATION_STANDBY = 6,
|
||||
TRIGGER_STATE_VIBRATION_ACTIVE = 7,
|
||||
};
|
||||
|
||||
static int32_t TriggerEffectState(const PadTriggerEffectCommand& command, int value,
|
||||
int32_t previous) {
|
||||
// Trigger positions use nonuniform travel ranges, with endpoints at 0 and 255.
|
||||
const int pos = value == 0 ? 0 : value == 255 ? 9 : 1 + value / 32;
|
||||
switch (command.mode) {
|
||||
case 1: // feedback: resistance from position data[0], strength data[1]
|
||||
return command.data[1] != 0 && pos >= command.data[0] ? TRIGGER_STATE_FEEDBACK_ACTIVE
|
||||
: TRIGGER_STATE_FEEDBACK_STANDBY;
|
||||
case 2: // weapon: resistance from data[0], released at data[1]
|
||||
if (value < command.data[0] * 32) {
|
||||
return TRIGGER_STATE_WEAPON_STANDBY;
|
||||
}
|
||||
return previous == TRIGGER_STATE_WEAPON_FIRED ||
|
||||
value >= std::min(command.data[1] * 32, 254)
|
||||
? TRIGGER_STATE_WEAPON_FIRED
|
||||
: TRIGGER_STATE_WEAPON_PULLING;
|
||||
case 3: // vibration from position data[0], amplitude data[1], frequency data[2]
|
||||
return command.data[1] != 0 && command.data[2] != 0 && pos >= command.data[0]
|
||||
? TRIGGER_STATE_VIBRATION_ACTIVE
|
||||
: TRIGGER_STATE_VIBRATION_STANDBY;
|
||||
case 4: // multiple-position feedback: strength per position
|
||||
return command.data[pos] != 0 ? TRIGGER_STATE_FEEDBACK_ACTIVE
|
||||
: TRIGGER_STATE_FEEDBACK_STANDBY;
|
||||
case 5: // slope feedback between data[0] and data[1]
|
||||
return pos >= command.data[0] && pos < command.data[1] ? TRIGGER_STATE_FEEDBACK_ACTIVE
|
||||
: TRIGGER_STATE_FEEDBACK_STANDBY;
|
||||
case 6: // multiple-position vibration: frequency data[0], amplitude per position
|
||||
return command.data[0] != 0 && command.data[1 + pos] != 0
|
||||
? TRIGGER_STATE_VIBRATION_ACTIVE
|
||||
: TRIGGER_STATE_VIBRATION_STANDBY;
|
||||
default: return TRIGGER_STATE_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
EXIT_IF(g_controller != nullptr);
|
||||
|
||||
@@ -419,6 +486,7 @@ void GameController::CheckActive() {
|
||||
m_vibration = {};
|
||||
m_vibration_until = 0;
|
||||
m_trigger_effect = {};
|
||||
m_trigger_state = {};
|
||||
}
|
||||
|
||||
void GameController::AddState() {
|
||||
@@ -466,6 +534,7 @@ void GameController::Axis(int id, Controller::Axis axis, int value) {
|
||||
}
|
||||
if (trigger != 0) {
|
||||
m_state.buttons = value > 0 ? m_state.buttons | trigger : m_state.buttons & ~trigger;
|
||||
UpdateTriggerEffectState();
|
||||
}
|
||||
|
||||
AddState();
|
||||
@@ -622,6 +691,7 @@ void GameController::ResetInputState() {
|
||||
m_state.buttons = 0;
|
||||
std::fill_n(m_state.axes, 4, 128);
|
||||
std::fill_n(m_state.axes + 4, 2, 0);
|
||||
UpdateTriggerEffectState();
|
||||
for (auto& touch: m_state.touch) {
|
||||
touch = {};
|
||||
}
|
||||
@@ -730,9 +800,25 @@ bool GameController::SetTriggerEffect(const PadTriggerEffectParam& param) {
|
||||
m_trigger_effect.command[i] = param.command[i];
|
||||
}
|
||||
}
|
||||
UpdateTriggerEffectState();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameController::GetTriggerEffectState(PadTriggerEffectStateInformation* info) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
*info = m_trigger_state;
|
||||
}
|
||||
|
||||
void GameController::UpdateTriggerEffectState() {
|
||||
// Track every input event so a weapon remains fired even when the game polls after release
|
||||
// starts.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
m_trigger_state.state[i] = TriggerEffectState(
|
||||
m_trigger_effect.command[i], m_state.axes[static_cast<int>(Axis::TriggerLeft) + i],
|
||||
m_trigger_state.state[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameController::SendTriggerEffect(const PadTriggerEffectParam& param) {
|
||||
if ((param.trigger_mask & ~0x03u) != 0) {
|
||||
return false;
|
||||
@@ -853,21 +939,71 @@ int GetActiveControllerId() {
|
||||
return g_controller != nullptr ? g_controller->GetActiveControllerId() : -1;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadInit() {
|
||||
PRINT_NAME();
|
||||
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
|
||||
// The host keyboard and gamepad feed one standard port for the configured user.
|
||||
return user_id == Config::GetUserId() && type == 0 && index == 0;
|
||||
}
|
||||
|
||||
int GameController::InitPort() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_port_state == PortState::Uninitialized) {
|
||||
m_port_state = PortState::Closed;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
|
||||
constexpr int user_id_system = 0xff;
|
||||
constexpr int port_type_standard = 0;
|
||||
constexpr int port_type_special = 2;
|
||||
constexpr int port_type_remote = 16;
|
||||
const bool personal_port =
|
||||
user_id == Config::GetUserId() && (type == port_type_standard || type == port_type_special);
|
||||
const bool system_remote_control = user_id == user_id_system && type == port_type_remote;
|
||||
return index == 0 && (personal_port || system_remote_control);
|
||||
int GameController::OpenPort(int user_id, int type, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_port_state == PortState::Uninitialized) {
|
||||
return PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!PadOpenArgsAreValid(user_id, type, index)) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (m_port_state == PortState::Open) {
|
||||
return PAD_ERROR_ALREADY_OPENED;
|
||||
}
|
||||
m_port_state = PortState::Open;
|
||||
return PAD_HANDLE;
|
||||
}
|
||||
|
||||
int GameController::GetPortHandle(int user_id, int type, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_port_state == PortState::Uninitialized) {
|
||||
return PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
return m_port_state == PortState::Open && PadOpenArgsAreValid(user_id, type, index) ? PAD_HANDLE
|
||||
: PAD_ERROR_NO_HANDLE;
|
||||
}
|
||||
|
||||
int GameController::CheckPortHandleLocked(int handle) const {
|
||||
if (m_port_state == PortState::Uninitialized) {
|
||||
return PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
return m_port_state == PortState::Open && handle == PAD_HANDLE ? OK : PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
int GameController::CheckPortHandle(int handle) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return CheckPortHandleLocked(handle);
|
||||
}
|
||||
|
||||
int GameController::ClosePort(int handle) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (const auto error = CheckPortHandleLocked(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
m_port_state = PortState::Closed;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int PadCheckHandle(int handle) {
|
||||
return g_controller->CheckPortHandle(handle);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadInit() {
|
||||
PRINT_NAME();
|
||||
return g_controller->InitPort();
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
|
||||
@@ -879,15 +1015,7 @@ int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
|
||||
"\t param = 0x%016" PRIx64 "\n",
|
||||
user_id, type, index, reinterpret_cast<uint64_t>(param));
|
||||
|
||||
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
|
||||
|
||||
if (!PadOpenArgsAreValid(user_id, type, index)) {
|
||||
return pad_error_invalid_arg;
|
||||
}
|
||||
|
||||
int handle = 1;
|
||||
|
||||
return handle;
|
||||
return g_controller->OpenPort(user_id, type, index);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
|
||||
@@ -898,20 +1026,20 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
|
||||
"\t index = %d\n",
|
||||
user_id, type, index);
|
||||
|
||||
constexpr int pad_error_device_no_handle = -2137915384; /* 0x80920008 */
|
||||
return g_controller->GetPortHandle(user_id, type, index);
|
||||
}
|
||||
|
||||
if (!PadOpenArgsAreValid(user_id, type, index)) {
|
||||
return pad_error_device_no_handle;
|
||||
}
|
||||
|
||||
return 1;
|
||||
int KYTY_SYSV_ABI PadClose(int handle) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t handle = %d\n", handle);
|
||||
return g_controller->ClosePort(handle);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
|
||||
@@ -923,8 +1051,8 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
|
||||
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
|
||||
@@ -935,8 +1063,8 @@ int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) {
|
||||
int KYTY_SYSV_ABI PadResetOrientation(int handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
g_controller->ResetOrientation();
|
||||
@@ -946,18 +1074,17 @@ int KYTY_SYSV_ABI PadResetOrientation(int handle) {
|
||||
int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformation* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
|
||||
g_controller->GetConnectionInfo(&connected, &connected_count);
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (info == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
g_controller->GetConnectionInfo(&connected, &connected_count);
|
||||
|
||||
std::memset(info, 0, sizeof(*info));
|
||||
|
||||
info->touch_pixel_density = 44.86f;
|
||||
@@ -976,8 +1103,8 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati
|
||||
int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (is_remote == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
@@ -990,8 +1117,8 @@ int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
|
||||
int KYTY_SYSV_ABI PadReadState(int handle, PadData* data) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (data == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
@@ -1012,8 +1139,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(num < 1 || num > 64);
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (data == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
@@ -1044,8 +1171,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
|
||||
int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (param == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
@@ -1063,8 +1190,8 @@ int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
|
||||
int KYTY_SYSV_ABI PadResetLightBar(int handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return OK;
|
||||
@@ -1073,8 +1200,8 @@ int KYTY_SYSV_ABI PadResetLightBar(int handle) {
|
||||
int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (param == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
@@ -1088,8 +1215,8 @@ int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
|
||||
int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (param == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
@@ -1098,4 +1225,21 @@ int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* p
|
||||
return g_controller->SetTriggerEffect(*param) ? OK : PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle, PadTriggerEffectStateInformation* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t handle = %d\n", handle);
|
||||
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (info == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
g_controller->GetTriggerEffectState(info);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
} // namespace Libs::Controller
|
||||
@@ -53,6 +53,7 @@ struct PadData;
|
||||
struct PadVibrationParam;
|
||||
struct PadLightBarParam;
|
||||
struct PadTriggerEffectParam;
|
||||
struct PadTriggerEffectStateInformation;
|
||||
|
||||
inline int controller_get_axis(int min, int max, int value) {
|
||||
int v = (255 * (value - min)) / (max - min);
|
||||
@@ -80,9 +81,12 @@ enum class Setting { SpeakerVolume, VibrationIntensity, TriggerEffectIntensity }
|
||||
void CycleSetting(Setting setting);
|
||||
float GetSettingScale(Setting setting);
|
||||
|
||||
int PadCheckHandle(int handle);
|
||||
|
||||
int KYTY_SYSV_ABI PadInit();
|
||||
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
|
||||
int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index);
|
||||
int KYTY_SYSV_ABI PadClose(int handle);
|
||||
int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable);
|
||||
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable);
|
||||
int KYTY_SYSV_ABI PadResetOrientation(int handle);
|
||||
@@ -92,6 +96,7 @@ int KYTY_SYSV_ABI PadReadState(int handle, PadData* data);
|
||||
int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num);
|
||||
int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param);
|
||||
int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param);
|
||||
int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle, PadTriggerEffectStateInformation* info);
|
||||
int KYTY_SYSV_ABI PadResetLightBar(int handle);
|
||||
int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param);
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ bool UsesBluetooth(const Stream* stream) {
|
||||
}
|
||||
|
||||
uint64_t Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume, float gain) {
|
||||
bool is_float, const int* volume, float gain, const float* gains) {
|
||||
if (stream == nullptr || data == nullptr || frames == 0 || channels == 0 || volume == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
@@ -357,7 +357,7 @@ uint64_t Queue(Stream* stream, int controller, const void* data, uint32_t frames
|
||||
const auto index = (static_cast<size_t>(first_frame) + frame) * channels + src_ch;
|
||||
float value = is_float ? static_cast<const float*>(data)[index]
|
||||
: static_cast<const int16_t*>(data)[index] / 32768.0f;
|
||||
value *= volume[src_ch] / 32768.0f * gain;
|
||||
value *= volume[src_ch] / 32768.0f * gain * (gains != nullptr ? gains[src_ch] : 1.0f);
|
||||
// USB places the speaker in front and the actuators in back; Bluetooth takes stereo.
|
||||
stream->buffer[static_cast<size_t>(frame) * output_channels + ch + channel_offset] =
|
||||
value;
|
||||
|
||||
@@ -17,7 +17,8 @@ bool UsesBluetooth(const Stream* stream);
|
||||
// (the active pad has no usable controller audio device or its speaker cannot be routed).
|
||||
// Ambiguous USB or Bluetooth endpoints are not selected.
|
||||
uint64_t Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume, float gain = 1.0f);
|
||||
bool is_float, const int* volume, float gain = 1.0f,
|
||||
const float* gains = nullptr);
|
||||
// Returns false for other gamepad types, which retain the normal rumble path.
|
||||
bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor,
|
||||
uint32_t duration_ms = 0xffff);
|
||||
|
||||
@@ -74,6 +74,7 @@ LIB_DEFINE(InitAgcDriver_1) {
|
||||
LIB_FUNC("ozKzBP4aki4", Gen5::AgcAcbCondExecGetSize);
|
||||
LIB_FUNC("e1DFTg+Sd8U", Gen5::AgcAcbJump);
|
||||
LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize);
|
||||
LIB_FUNC("DwICrVxerkY", Gen5::AgcAcbRewind);
|
||||
LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem);
|
||||
LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize);
|
||||
LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData);
|
||||
@@ -95,6 +96,7 @@ LIB_DEFINE(InitAgcDriver_1) {
|
||||
LIB_FUNC("w4-d0n60hdo", Gen5::AgcDcbSetUcRegisterDirect);
|
||||
LIB_FUNC("ZvwO9euwYzc", Gen5::AgcDcbSetCxRegistersIndirect);
|
||||
LIB_FUNC("-HOOCn0JY48", Gen5::AgcDcbSetShRegistersIndirect);
|
||||
LIB_FUNC("nNlUtdDDvZ0", Gen5::AgcDcbSetShRegistersIndirectGetSize);
|
||||
LIB_FUNC("hvUfkUIQcOE", Gen5::AgcDcbSetUcRegistersIndirect);
|
||||
LIB_FUNC("GIIW2J37e70", Gen5::AgcDcbSetIndexSize);
|
||||
LIB_FUNC("l4fM9K-Lyks", Gen5::AgcDcbSetIndexBuffer);
|
||||
@@ -155,6 +157,7 @@ LIB_DEFINE(InitAgcDriver_1) {
|
||||
LIB_FUNC("k-JpyR2dYAM", Gen5::AgcCondExecPatchSetEnd);
|
||||
LIB_FUNC("3ZWa3AoyWZQ", Gen5::AgcCondExecPatchSetCommandAddress);
|
||||
LIB_FUNC("ziVA3whp3p4", Gen5::AgcRewindPatchSetRewindState);
|
||||
LIB_FUNC("eWaWyFegzgQ", Gen5::AgcRewindPatchSetRewindState);
|
||||
LIB_FUNC("YUeqkyT7mEQ", Gen5::AgcDcbSetFlip);
|
||||
}
|
||||
|
||||
|
||||
+74
-56
@@ -1,4 +1,5 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/dateTime.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -18,6 +19,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -47,14 +49,15 @@ struct ResolvedPathInfo {
|
||||
uint32_t file_id = 0xffffffffu;
|
||||
uint64_t file_size = 0;
|
||||
bool is_dir = false;
|
||||
std::string host_path;
|
||||
std::filesystem::path host_path;
|
||||
};
|
||||
|
||||
static std::mutex g_mutex;
|
||||
static uint32_t g_next_submission_id = 1;
|
||||
static uint32_t g_next_file_id = 1;
|
||||
static std::unordered_map<uint32_t, bool> g_submissions;
|
||||
static std::condition_variable g_submission_cv;
|
||||
static std::unordered_map<uint32_t, std::string> g_files;
|
||||
static std::unordered_map<uint32_t, std::filesystem::path> g_files;
|
||||
static std::unordered_map<uint32_t, uint64_t> g_file_sizes;
|
||||
static std::unordered_map<std::string, ResolvedPathInfo> g_resolved_paths;
|
||||
|
||||
@@ -92,15 +95,6 @@ static bool WriteGuest(uint64_t addr, const T& value) {
|
||||
return WriteGuestBytes(addr, &value, sizeof(T));
|
||||
}
|
||||
|
||||
static uint32_t ComputeFileId(const char* guest_path) {
|
||||
uint32_t hash = 2166136261u;
|
||||
for (auto* p = reinterpret_cast<const uint8_t*>(guest_path); p != nullptr && *p != 0; ++p) {
|
||||
hash ^= *p;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
return hash & static_cast<uint32_t>(std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
|
||||
static int ReadGuestCString(uint64_t addr, char* out, size_t out_size) {
|
||||
if (addr == 0) {
|
||||
return LibKernel::KERNEL_ERROR_EFAULT;
|
||||
@@ -120,19 +114,7 @@ static int ReadGuestCString(uint64_t addr, char* out, size_t out_size) {
|
||||
return LibKernel::KERNEL_ERROR_ENAMETOOLONG;
|
||||
}
|
||||
|
||||
static void RegisterHostPathLocked(uint32_t file_id, const std::string& host_path,
|
||||
uint64_t file_size, bool is_dir) {
|
||||
g_files[file_id] = host_path;
|
||||
g_file_sizes[file_id] = is_dir ? 0 : file_size;
|
||||
}
|
||||
|
||||
static void RegisterHostPath(uint32_t file_id, const std::string& host_path, uint64_t file_size,
|
||||
bool is_dir) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
RegisterHostPathLocked(file_id, host_path, file_size, is_dir);
|
||||
}
|
||||
|
||||
static bool TryGetHostPath(uint32_t file_id, std::string* out) {
|
||||
static bool TryGetHostPath(uint32_t file_id, std::filesystem::path* out) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
const auto it = g_files.find(file_id);
|
||||
if (it == g_files.end()) {
|
||||
@@ -152,7 +134,7 @@ static bool TryGetHostFileSize(uint32_t file_id, uint64_t* out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int GetHostPathStat(const std::string& host_path, LibKernel::FileSystem::FileStat* st) {
|
||||
static int GetHostPathStat(const std::filesystem::path& host_path, LibKernel::FileSystem::FileStat* st) {
|
||||
if (st == nullptr) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
@@ -213,8 +195,7 @@ static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size)
|
||||
|
||||
if (!found) {
|
||||
const auto real_path = LibKernel::FileSystem::GetRealFilename(path);
|
||||
info.file_id = AprShared::ComputeFileId(guest_path);
|
||||
info.host_path = Common::PathToString(real_path);
|
||||
info.host_path = real_path;
|
||||
|
||||
if (Common::File::IsDirectoryExisting(real_path)) {
|
||||
info.is_dir = true;
|
||||
@@ -232,16 +213,21 @@ static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size)
|
||||
if (!inserted) {
|
||||
info = it->second;
|
||||
} else if (info.result == OK) {
|
||||
RegisterHostPathLocked(info.file_id, info.host_path, info.file_size, info.is_dir);
|
||||
// Allocate once under the cache lock; distinct paths can share a hash.
|
||||
if (g_next_file_id == 0xffffffffu) {
|
||||
info.result = it->second.result = LibKernel::KERNEL_ERROR_ENFILE;
|
||||
} else {
|
||||
info.file_id = it->second.file_id = g_next_file_id++;
|
||||
g_files.emplace(info.file_id, info.host_path);
|
||||
g_file_sizes.emplace(info.file_id, info.is_dir ? 0 : info.file_size);
|
||||
}
|
||||
} else if (info.result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
log_missing = true;
|
||||
}
|
||||
}
|
||||
if (log_missing) {
|
||||
LOGF("\tAPR resolve missing path: %s -> %s\n", guest_path, info.host_path.c_str());
|
||||
LOGF("\tAPR resolve missing path: %s -> %s\n", guest_path, Common::PathToString(info.host_path).c_str());
|
||||
}
|
||||
} else if (info.result == OK) {
|
||||
AprShared::RegisterHostPath(info.file_id, info.host_path, info.file_size, info.is_dir);
|
||||
}
|
||||
|
||||
if (info.result != OK) {
|
||||
@@ -423,7 +409,7 @@ static int KYTY_SYSV_ABI GetFileStat(uint32_t file_id, LibKernel::FileSystem::Fi
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EINVAL);
|
||||
}
|
||||
|
||||
std::string host_path;
|
||||
std::filesystem::path host_path;
|
||||
if (!AprShared::TryGetHostPath(file_id, &host_path)) {
|
||||
LOGF("\tAPR stat failed for unknown file id: 0x%08" PRIx32 "\n", file_id);
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_ENOENT);
|
||||
@@ -445,7 +431,7 @@ static int KYTY_SYSV_ABI GetFileSize(uint32_t file_id, uint64_t* size) {
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
|
||||
}
|
||||
|
||||
std::string host_path;
|
||||
std::filesystem::path host_path;
|
||||
if (!AprShared::TryGetHostPath(file_id, &host_path)) {
|
||||
LOGF("\tAPR size failed for unknown file id: 0x%08" PRIx32 "\n", file_id);
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_ENOENT);
|
||||
@@ -665,10 +651,15 @@ struct CommandBufferState {
|
||||
uint64_t address = 0;
|
||||
uint64_t value = 0;
|
||||
};
|
||||
struct WaitAddressCommand {
|
||||
uint64_t address;
|
||||
struct WaitCommand {
|
||||
uint64_t source;
|
||||
uint64_t value;
|
||||
uint8_t compare;
|
||||
bool is_counter;
|
||||
};
|
||||
struct WriteCounterCommand {
|
||||
uint8_t index;
|
||||
uint32_t value;
|
||||
};
|
||||
struct AmmMapCommand {
|
||||
AmmCommandKind kind = AmmCommandKind::MapAuto;
|
||||
@@ -680,7 +671,7 @@ struct CommandBufferState {
|
||||
uint8_t gpu_mask_id = 0;
|
||||
};
|
||||
using CommandData = std::variant<ReadFileCommand, KernelEventCommand, WriteAddressCommand,
|
||||
AmmMapCommand, WaitAddressCommand>;
|
||||
AmmMapCommand, WaitCommand, WriteCounterCommand>;
|
||||
struct Command {
|
||||
uint64_t record_offset;
|
||||
CommandData data;
|
||||
@@ -705,6 +696,8 @@ static_assert(sizeof(AmmUsageStatsData) == 0x18);
|
||||
static std::mutex g_command_buffer_mutex;
|
||||
static std::unordered_map<uint64_t, CommandBufferState> g_command_buffers;
|
||||
static std::unordered_map<uint64_t, uint64_t> g_command_buffer_aliases;
|
||||
// AMM and APR share 256 32-bit counters; only the first 128 are application-writable.
|
||||
static std::array<std::atomic<uint32_t>, 256> g_counters {};
|
||||
using CommandBufferIterator = std::unordered_map<uint64_t, CommandBufferState>::iterator;
|
||||
|
||||
static void RegisterCommandBufferAliasLocked(uint64_t command_buffer, uint64_t buffer) {
|
||||
@@ -1169,13 +1162,13 @@ static bool AppendCommandRecord(uint64_t command_buffer, CommandBufferState::Com
|
||||
return CommitCommandBufferRecord(command_buffer, &state, record_size);
|
||||
}
|
||||
|
||||
static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offset,
|
||||
static int ReadHostFileToGuest(const std::filesystem::path& host_path, uint64_t file_offset,
|
||||
uint64_t destination, uint64_t size, uint64_t* bytes_read);
|
||||
|
||||
static int ExecuteCommand(const CommandBufferState::Command& entry) {
|
||||
if (const auto* payload = std::get_if<CommandBufferState::ReadFileCommand>(&entry.data)) {
|
||||
const auto& command = *payload;
|
||||
std::string host_path;
|
||||
std::filesystem::path host_path;
|
||||
if (!AprShared::TryGetHostPath(command.file_id, &host_path)) {
|
||||
LOGF("\tAPR submit failed for unknown file id: 0x%08" PRIx32 "\n", command.file_id);
|
||||
return LibKernel::KERNEL_ERROR_ENOENT;
|
||||
@@ -1186,7 +1179,7 @@ static int ExecuteCommand(const CommandBufferState::Command& entry) {
|
||||
command.size, &bytes_read);
|
||||
if (result != OK) {
|
||||
LOGF("\tAPR submit read failed: id=0x%08" PRIx32 ", result=0x%08" PRIx32 ", path=%s\n",
|
||||
command.file_id, static_cast<uint32_t>(result), host_path.c_str());
|
||||
command.file_id, static_cast<uint32_t>(result), Common::PathToString(host_path).c_str());
|
||||
return result;
|
||||
}
|
||||
} else if (const auto* payload =
|
||||
@@ -1207,6 +1200,9 @@ static int ExecuteCommand(const CommandBufferState::Command& entry) {
|
||||
const auto& command = *payload;
|
||||
std::atomic_ref(*reinterpret_cast<uint64_t*>(command.address))
|
||||
.store(command.value, std::memory_order_release);
|
||||
} else if (const auto* command =
|
||||
std::get_if<CommandBufferState::WriteCounterCommand>(&entry.data)) {
|
||||
g_counters[command->index].store(command->value, std::memory_order_release);
|
||||
} else if (const auto* payload = std::get_if<CommandBufferState::AmmMapCommand>(&entry.data)) {
|
||||
const auto& command = *payload;
|
||||
int result = OK;
|
||||
@@ -1235,17 +1231,20 @@ struct Submission {
|
||||
uint32_t id = 0;
|
||||
};
|
||||
|
||||
static bool IsWaitSatisfied(const CommandBufferState::WaitAddressCommand& wait) {
|
||||
const uint64_t value =
|
||||
std::atomic_ref(*reinterpret_cast<uint64_t*>(wait.address)).load(std::memory_order_acquire);
|
||||
static bool IsWaitSatisfied(const CommandBufferState::WaitCommand& wait) {
|
||||
const uint64_t value = wait.is_counter
|
||||
? g_counters[wait.source].load(std::memory_order_acquire)
|
||||
: std::atomic_ref(*reinterpret_cast<uint64_t*>(wait.source))
|
||||
.load(std::memory_order_acquire);
|
||||
const uint64_t sign = uint64_t {1} << (wait.is_counter ? 31 : 63);
|
||||
switch (wait.compare) {
|
||||
case 0: return value == wait.value;
|
||||
case 1: return value > wait.value;
|
||||
case 2: return value < wait.value;
|
||||
case 3: return value != wait.value;
|
||||
case 4: return static_cast<int64_t>(value - wait.value) >= 0;
|
||||
case 5: return static_cast<int64_t>(value) > static_cast<int64_t>(wait.value);
|
||||
default: return static_cast<int64_t>(value) < static_cast<int64_t>(wait.value);
|
||||
case 4: return ((value - wait.value) & sign) == 0;
|
||||
case 5: return (value ^ sign) > (wait.value ^ sign);
|
||||
default: return (value ^ sign) < (wait.value ^ sign);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,7 +1278,7 @@ private:
|
||||
pending = true;
|
||||
auto& submission = queue.front();
|
||||
if (submission.cursor < submission.commands.size()) {
|
||||
const auto* wait = std::get_if<CommandBufferState::WaitAddressCommand>(
|
||||
const auto* wait = std::get_if<CommandBufferState::WaitCommand>(
|
||||
&submission.commands[submission.cursor].data);
|
||||
if (wait != nullptr && !IsWaitSatisfied(*wait)) {
|
||||
continue;
|
||||
@@ -1382,7 +1381,7 @@ static bool AdvanceCommandBuffer(uint64_t command_buffer, uint64_t record_size)
|
||||
return CommitCommandBufferRecord(command_buffer, &state, record_size);
|
||||
}
|
||||
|
||||
static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offset,
|
||||
static int ReadHostFileToGuest(const std::filesystem::path& host_path, uint64_t file_offset,
|
||||
uint64_t destination, uint64_t size, uint64_t* bytes_read) {
|
||||
if (bytes_read == nullptr) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
@@ -1398,7 +1397,7 @@ static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offse
|
||||
|
||||
Common::File file;
|
||||
if (!file.Open(host_path, Common::File::Mode::Read)) {
|
||||
LOGF("\tAPR read missing host file: %s\n", host_path.c_str());
|
||||
LOGF("\tAPR read missing host file: %s\n", Common::PathToString(host_path).c_str());
|
||||
return LibKernel::KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
const auto file_size = file.Size();
|
||||
@@ -1927,18 +1926,27 @@ static int KYTY_SYSV_ABI CommandBufferWaitOnAddress(void* command_b
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer),
|
||||
CommandBufferState::WaitAddressCommand {
|
||||
reinterpret_cast<uint64_t>(address), value, compare},
|
||||
CommandBufferState::WaitCommand {
|
||||
reinterpret_cast<uint64_t>(address), value, compare, false},
|
||||
0x20)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI CommandBufferWaitOnCounter(void* command_buffer, uint8_t, uint8_t,
|
||||
uint64_t, uint8_t, uint8_t, uint64_t, uint8_t) {
|
||||
static int KYTY_SYSV_ABI CommandBufferWaitOnCounter(void* command_buffer, uint8_t index,
|
||||
uint8_t access, uint64_t value, uint8_t compare,
|
||||
uint8_t mask_op, uint64_t, uint8_t flush) {
|
||||
PRINT_NAME();
|
||||
|
||||
return AppendNoOpCommand(command_buffer, 0x20);
|
||||
if (command_buffer == nullptr || access > 7 || compare > 6 || mask_op > 1 || flush > 1) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(access != 1 || mask_op != 0 || flush != 0 || value > UINT32_MAX);
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer),
|
||||
CommandBufferState::WaitCommand {index, value, compare, true},
|
||||
0x20)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
|
||||
static int AppendWriteAddressCommand(void* command_buffer, volatile uint64_t* address,
|
||||
@@ -1970,11 +1978,21 @@ static int KYTY_SYSV_ABI CommandBufferWriteAddressOnCompletion(void*
|
||||
return AppendWriteAddressCommand(command_buffer, address, value);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI CommandBufferWriteCounter(void* command_buffer, uint8_t, uint8_t, uint64_t,
|
||||
uint8_t, uint32_t) {
|
||||
static int KYTY_SYSV_ABI CommandBufferWriteCounter(void* command_buffer, uint8_t index,
|
||||
uint8_t access, uint64_t value,
|
||||
uint8_t operation, uint32_t mode) {
|
||||
PRINT_NAME();
|
||||
|
||||
return AppendNoOpCommand(command_buffer, 0x20);
|
||||
if (command_buffer == nullptr || index >= 128 || access > 7 || operation > 4 || mode > 1) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(access != 1 || operation != 0 || mode != 0);
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer),
|
||||
CommandBufferState::WriteCounterCommand {
|
||||
index, static_cast<uint32_t>(value)},
|
||||
0x20)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI CommandBufferWriteCounterOnCompletion(void* command_buffer, uint8_t,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "libs/systemService.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
@@ -96,6 +97,7 @@ int KYTY_SYSV_ABI AppContentInitialize(const AppContentInitParam* init_param,
|
||||
LOGF("\t TITLE_ID missing\n");
|
||||
}
|
||||
|
||||
SystemService::NotifyEntitlementUpdate();
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
+219
-100
@@ -11,6 +11,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
@@ -170,12 +171,14 @@ static std::atomic_uint64_t g_audioout2_next_port {1};
|
||||
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
||||
|
||||
struct AudioOut2ContextState {
|
||||
bool used = false;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
uint32_t queue_depth = 4;
|
||||
uint32_t queued = 0;
|
||||
uint32_t num_grains = 512;
|
||||
uint64_t last_update = 0;
|
||||
bool used = false;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
uint32_t queue_depth = 4;
|
||||
uint32_t queued = 0;
|
||||
uint32_t num_grains = 512;
|
||||
uint64_t last_update = 0;
|
||||
int object_audio_handle = 0;
|
||||
std::vector<float> object_mix;
|
||||
};
|
||||
|
||||
struct AudioOut2PortStateEntry {
|
||||
@@ -189,9 +192,11 @@ struct AudioOut2PortStateEntry {
|
||||
uint32_t samples_num = 512;
|
||||
AudioInternal::Format audio_format = AudioInternal::Format::Unknown;
|
||||
int audio_handle = 0;
|
||||
std::array<float, 16> gains {};
|
||||
uint32_t ambisonics = UINT32_MAX;
|
||||
bool attributes_locked = false;
|
||||
std::vector<uint8_t> pcm_data;
|
||||
};
|
||||
|
||||
struct AudioOut2SpeakerArrayState {
|
||||
bool used = false;
|
||||
uint32_t num_speakers = 0;
|
||||
@@ -207,9 +212,8 @@ struct AudioOut2LatencyState {
|
||||
uint32_t latency_us = 0;
|
||||
};
|
||||
|
||||
static Common::Mutex g_audioout2_context_mutex;
|
||||
static Common::Mutex g_audioout2_state_mutex;
|
||||
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
||||
static Common::Mutex g_audioout2_port_mutex;
|
||||
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
||||
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
||||
static Common::Mutex g_audioout2_speaker_array_mutex;
|
||||
@@ -223,7 +227,11 @@ static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511;
|
||||
static constexpr int AUDIO_OUT2_ERROR_BUSY = -2144960505; /* 0x80268007 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM = -2144959999; /* 0x80268201 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_MASTERING_INVALID_STATES_ID = -2144959996; /* 0x80268204 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_OUT_OF_RESOURCE = -2144960510; /* 0x80268002 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_INVALID_PORT = -2144960503; /* 0x80268009 */
|
||||
static constexpr uint32_t AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM = 0;
|
||||
static constexpr uint32_t AUDIO_OUT2_PORT_ATTRIBUTE_ID_GAIN = 1;
|
||||
static constexpr uint32_t AUDIO_OUT2_PORT_ATTRIBUTE_ID_AMBISONICS = 8;
|
||||
static constexpr uint32_t AUDIO_OUT2_MASTERING_OUTPUT_RECORDING = 2;
|
||||
static constexpr uint32_t AUDIO_OUT2_MASTERING_STATES_ID_DEFAULT = 1;
|
||||
static constexpr uint32_t AUDIO_OUT2_MASTERING_STATES_ID_V2 = 2;
|
||||
@@ -350,37 +358,80 @@ static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
||||
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
||||
}
|
||||
|
||||
static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
||||
!state.pcm_data.empty() && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
|
||||
return true;
|
||||
if (state.used && state.context == context.handle && !state.pcm_data.empty()) {
|
||||
const int handle = audioout2_port_type_is_object(state.port_type)
|
||||
? context.object_audio_handle
|
||||
: state.audio_handle;
|
||||
if (AudioInternal::AudioOutHasDevice(handle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void audioout2_mix_object(AudioOut2ContextState& context,
|
||||
const AudioOut2PortStateEntry& object) {
|
||||
auto& mix = context.object_mix;
|
||||
if (mix.empty()) {
|
||||
mix.resize(static_cast<size_t>(context.num_grains) * 2, 0.0f);
|
||||
}
|
||||
|
||||
// Center ordinary objects. First-order stereo ambisonics uses left/right-facing
|
||||
// cardioids; FuMa W has a different normalization from ACN/SN3D W.
|
||||
constexpr float half_power = 0.70710678f;
|
||||
float left = half_power, right = half_power;
|
||||
switch (object.ambisonics) {
|
||||
case UINT32_MAX:
|
||||
case 64: break;
|
||||
case 0: left = right = 1.0f; break;
|
||||
case 2:
|
||||
case 65: right = -half_power; break;
|
||||
default: return;
|
||||
}
|
||||
left *= object.gains[0];
|
||||
right *= object.gains[0];
|
||||
for (uint32_t frame = 0; frame < context.num_grains; frame++) {
|
||||
const float sample =
|
||||
object.audio_format == AudioInternal::Format::FloatMono
|
||||
? reinterpret_cast<const float*>(object.pcm_data.data())[frame]
|
||||
: reinterpret_cast<const int16_t*>(object.pcm_data.data())[frame] / 32768.0f;
|
||||
mix[frame * 2] += sample * left;
|
||||
mix[frame * 2 + 1] += sample * right;
|
||||
}
|
||||
}
|
||||
|
||||
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
|
||||
// The backend consumes the buffers synchronously, but can block while pacing SDL's queue.
|
||||
// Keep both PCM storage and port handles alive until it returns.
|
||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||
auto* context = audioout2_find_context_locked(ctx);
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
context->object_mix.clear();
|
||||
std::vector<AudioInternal::OutputParam> params;
|
||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
||||
!state.pcm_data.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data.data()});
|
||||
if (!state.used || state.context != ctx || state.pcm_data.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (audioout2_port_type_is_object(state.port_type)) {
|
||||
audioout2_mix_object(*context, state);
|
||||
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
params.push_back({state.audio_handle, state.pcm_data.data(), state.gains.data()});
|
||||
}
|
||||
}
|
||||
|
||||
if (!context->object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
params.push_back({context->object_audio_handle, context->object_mix.data()});
|
||||
}
|
||||
if (!params.empty()) {
|
||||
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
|
||||
blocking);
|
||||
}
|
||||
}
|
||||
|
||||
static void audioout2_close_audio_handle(int audio_handle) {
|
||||
if (audio_handle > 0) {
|
||||
AudioInternal::AudioOutClose(audio_handle);
|
||||
@@ -431,52 +482,58 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
||||
|
||||
*ctx = g_audioout2_next_context.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
auto* state = audioout2_find_context_locked(0);
|
||||
if (state == nullptr) {
|
||||
for (auto& candidate: g_audioout2_contexts) {
|
||||
if (!candidate.used) {
|
||||
state = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
||||
const int object_audio_handle =
|
||||
params->max_object_ports == 0
|
||||
? 0
|
||||
: AudioInternal::AudioOutOpen(AUDIO_OUT_PORT_TYPE_MAIN, num_grains, 48000,
|
||||
AudioInternal::Format::FloatStereo);
|
||||
if (params->max_object_ports != 0 && object_audio_handle == 0) {
|
||||
return AUDIO_OUT2_ERROR_OUT_OF_RESOURCE;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(state == nullptr);
|
||||
*state = AudioOut2ContextState {};
|
||||
state->used = true;
|
||||
state->handle = *ctx;
|
||||
state->queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
||||
state->queued = 0;
|
||||
state->num_grains = (params->num_grains == 0 ? 512u : params->num_grains);
|
||||
state->last_update = LibKernel::KernelGetProcessTime();
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
auto it = std::find_if(g_audioout2_contexts.begin(), g_audioout2_contexts.end(),
|
||||
[](const auto& state) { return !state.used; });
|
||||
if (it == g_audioout2_contexts.end()) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
audioout2_close_audio_handle(object_audio_handle);
|
||||
return AUDIO_OUT2_ERROR_OUT_OF_RESOURCE;
|
||||
}
|
||||
*ctx = g_audioout2_next_context.fetch_add(1, std::memory_order_relaxed);
|
||||
auto* state = &*it;
|
||||
*state = AudioOut2ContextState {};
|
||||
state->used = true;
|
||||
state->handle = *ctx;
|
||||
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
||||
state->num_grains = num_grains;
|
||||
state->last_update = LibKernel::KernelGetProcessTime();
|
||||
state->object_audio_handle = object_audio_handle;
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
||||
"\t buffer_size = 0x%016" PRIx64 "\n"
|
||||
"\t ctx = 0x%016" PRIx64 "\n"
|
||||
"\t queue_depth = %" PRIu32 ", num_grains = %" PRIu32 "\n",
|
||||
reinterpret_cast<uint64_t>(buffer), static_cast<uint64_t>(buffer_size), *ctx,
|
||||
state->queue_depth, state->num_grains);
|
||||
(params->queue_depth == 0 ? 4u : params->queue_depth), num_grains);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
std::array<int, 257> audio_handles {};
|
||||
size_t audio_handles_num = 0;
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
if (state->object_audio_handle > 0) {
|
||||
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
||||
}
|
||||
*state = AudioOut2ContextState {};
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
|
||||
std::array<int, 256> audio_handles {};
|
||||
size_t audio_handles_num = 0;
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
for (auto& port_state: g_audioout2_ports) {
|
||||
if (port_state.used && port_state.context == ctx) {
|
||||
if (port_state.audio_handle > 0 && audio_handles_num < audio_handles.size()) {
|
||||
@@ -485,7 +542,7 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
port_state = AudioOut2PortStateEntry {};
|
||||
}
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
for (size_t i = 0; i < audio_handles_num; i++) {
|
||||
audioout2_close_audio_handle(audio_handles[i]);
|
||||
@@ -504,11 +561,16 @@ int KYTY_SYSV_ABI AudioOut2ContextSetAttributes(AudioOut2ContextHandle ctx,
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) {
|
||||
g_audioout2_context_mutex.Lock();
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
audioout2_update_context_locked(state);
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
for (auto& port: g_audioout2_ports) {
|
||||
if (port.used && port.context == ctx) {
|
||||
port.attributes_locked = false;
|
||||
}
|
||||
}
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -520,11 +582,10 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
|
||||
// Only a synchronous submission carrying PCM to a real device can rely on the SDL queue for
|
||||
// pacing. Async pushes must retain queue-depth backpressure, and a handle without PCM (or a
|
||||
// vibration/failed-open handle) has no downstream operation that can block this call.
|
||||
const bool use_device_clock =
|
||||
blocking != 0 && audioout2_context_has_queueable_device(ctx);
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
const bool use_device_clock =
|
||||
blocking != 0 && audioout2_context_has_queueable_device(*state);
|
||||
audioout2_update_context_locked(state);
|
||||
sleep_micros = audioout2_grain_micros(state->num_grains);
|
||||
if (state->queued < state->queue_depth || use_device_clock) {
|
||||
@@ -534,12 +595,12 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
|
||||
if (state->queued < state->queue_depth) {
|
||||
state->queued++;
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
audioout2_queue_context_audio(ctx, blocking != 0);
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
if (blocking == 0) {
|
||||
return AUDIO_OUT2_ERROR_NOT_READY;
|
||||
@@ -558,7 +619,7 @@ int KYTY_SYSV_ABI AudioOut2ContextGetQueueLevel(AudioOut2ContextHandle ctx, uint
|
||||
*available_queues = 4;
|
||||
}
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
audioout2_update_context_locked(state);
|
||||
if (queue_level != nullptr) {
|
||||
@@ -569,7 +630,7 @@ int KYTY_SYSV_ABI AudioOut2ContextGetQueueLevel(AudioOut2ContextHandle ctx, uint
|
||||
(state->queued < state->queue_depth ? state->queue_depth - state->queued : 0);
|
||||
}
|
||||
}
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -583,19 +644,25 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
||||
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
||||
|
||||
g_audioout2_context_mutex.Lock();
|
||||
g_audioout2_state_mutex.Lock();
|
||||
const auto* context_state = audioout2_find_context_locked(ctx);
|
||||
if (context_state == nullptr) {
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
const auto samples_num = context_state->num_grains == 0 ? 512u : context_state->num_grains;
|
||||
if (audioout2_port_type_is_object(params->port_type) &&
|
||||
(params->port_type != 0x100 || context_state->object_audio_handle == 0 ||
|
||||
params->sampling_freq != 48000 ||
|
||||
(audio_format != AudioInternal::Format::FloatMono &&
|
||||
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
const auto samples_num = context_state->num_grains;
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
if (std::find(g_audioout2_users.begin(), g_audioout2_users.end(), params->user_handle) ==
|
||||
g_audioout2_users.end()) {
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
AudioOut2PortStateEntry* port_state = nullptr;
|
||||
@@ -616,9 +683,9 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
port_state->sampling_freq = params->sampling_freq;
|
||||
port_state->samples_num = samples_num;
|
||||
port_state->audio_format = audio_format;
|
||||
port_state->gains.fill(1.0f);
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
if (port_state == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||
@@ -632,12 +699,12 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
audio_format);
|
||||
}
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
g_audioout2_state_mutex.Lock();
|
||||
const bool reserved = port_state->used && port_state->handle == next_port;
|
||||
if (reserved) {
|
||||
port_state->audio_handle = audio_handle;
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
if (!reserved) {
|
||||
audioout2_close_audio_handle(audio_handle);
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
@@ -663,12 +730,12 @@ int KYTY_SYSV_ABI AudioOut2PortDestroy(AudioOut2PortHandle port) {
|
||||
LOGF("\t port = 0x%016" PRIx64 "\n", port);
|
||||
|
||||
int audio_handle = 0;
|
||||
g_audioout2_port_mutex.Lock();
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
|
||||
audio_handle = state->audio_handle;
|
||||
*state = AudioOut2PortStateEntry {};
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
audioout2_close_audio_handle(audio_handle);
|
||||
|
||||
@@ -677,36 +744,86 @@ int KYTY_SYSV_ABI AudioOut2PortDestroy(AudioOut2PortHandle port) {
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2PortSetAttributes(AudioOut2PortHandle port,
|
||||
const AudioOut2Attribute* attributes, uint32_t num) {
|
||||
EXIT_NOT_IMPLEMENTED(num != 0 && attributes == nullptr);
|
||||
if (num != 0 && attributes == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||
auto* state = audioout2_find_port_locked(port);
|
||||
if (state == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
const void* pcm_data = nullptr;
|
||||
bool has_pcm = false;
|
||||
const void* pcm_data = nullptr;
|
||||
auto gains = state->gains;
|
||||
auto ambisonics = state->ambisonics;
|
||||
uint32_t seen = 0;
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
if (attributes[i].attribute_id == AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM &&
|
||||
attributes[i].value != nullptr && attributes[i].value_size >= sizeof(AudioOut2Pcm)) {
|
||||
AudioOut2Pcm pcm {};
|
||||
std::memcpy(&pcm, attributes[i].value, sizeof(AudioOut2Pcm));
|
||||
pcm_data = pcm.data;
|
||||
has_pcm = true;
|
||||
const auto& attribute = attributes[i];
|
||||
const auto id = attribute.attribute_id;
|
||||
if (id != AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM && id != AUDIO_OUT2_PORT_ATTRIBUTE_ID_GAIN &&
|
||||
id != AUDIO_OUT2_PORT_ATTRIBUTE_ID_AMBISONICS) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_pcm) {
|
||||
g_audioout2_port_mutex.Lock();
|
||||
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
|
||||
if (pcm_data != nullptr && state->audio_format != AudioInternal::Format::Unknown) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(pcm_data);
|
||||
state->pcm_data.assign(bytes, bytes + audioout2_pcm_size(*state));
|
||||
} else {
|
||||
state->pcm_data.clear();
|
||||
if ((seen & (1u << id)) != 0) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
seen |= 1u << id;
|
||||
if (id != AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM && state->attributes_locked) {
|
||||
continue;
|
||||
}
|
||||
if (attribute.value == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
switch (id) {
|
||||
case AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM: {
|
||||
if (attribute.value_size < sizeof(AudioOut2Pcm)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
AudioOut2Pcm pcm {};
|
||||
std::memcpy(&pcm, attribute.value, sizeof(pcm));
|
||||
pcm_data = pcm.data;
|
||||
break;
|
||||
}
|
||||
case AUDIO_OUT2_PORT_ATTRIBUTE_ID_GAIN: {
|
||||
const auto channels = audioout2_data_format_channels(state->data_format);
|
||||
if (attribute.value_size != channels * sizeof(float)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
std::memcpy(gains.data(), attribute.value, attribute.value_size);
|
||||
for (uint32_t ch = 0; ch < channels; ch++) {
|
||||
if (!std::isfinite(gains[ch]) || gains[ch] < 0.0f) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AUDIO_OUT2_PORT_ATTRIBUTE_ID_AMBISONICS:
|
||||
if (!audioout2_port_type_is_object(state->port_type) ||
|
||||
attribute.value_size != sizeof(ambisonics)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
std::memcpy(&ambisonics, attribute.value, sizeof(ambisonics));
|
||||
if (ambisonics != UINT32_MAX && ambisonics > 15 &&
|
||||
(ambisonics < 64 || ambisonics > 99)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
}
|
||||
|
||||
state->gains = gains;
|
||||
state->ambisonics = ambisonics;
|
||||
if ((seen & (1u << AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM)) != 0) {
|
||||
if (pcm_data != nullptr && state->audio_format != AudioInternal::Format::Unknown) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(pcm_data);
|
||||
state->pcm_data.assign(bytes, bytes + audioout2_pcm_size(*state));
|
||||
} else {
|
||||
state->pcm_data.clear();
|
||||
}
|
||||
state->attributes_locked = audioout2_port_type_is_object(state->port_type);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2PortGetState(AudioOut2PortHandle port, AudioOut2PortState* state) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -719,11 +836,11 @@ int KYTY_SYSV_ABI AudioOut2PortGetState(AudioOut2PortHandle port, AudioOut2PortS
|
||||
state->reroute_counter = 0;
|
||||
state->flags = 0;
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* port_state = audioout2_find_port_locked(port); port_state != nullptr) {
|
||||
state->num_channels = audioout2_data_format_channels(port_state->data_format);
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
// LOGF("\t port = 0x%016" PRIx64 "\n", port);
|
||||
// LOGF("\t num_channels = %" PRIu8 "\n", state->num_channels);
|
||||
@@ -743,7 +860,7 @@ int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* han
|
||||
if (handle == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||
*handle = g_audioout2_next_user++;
|
||||
g_audioout2_users.push_back(*handle);
|
||||
LOGF("\t user_id = %" PRIu32 ", handle = 0x%016" PRIx64 "\n", user_id,
|
||||
@@ -754,7 +871,7 @@ int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* han
|
||||
int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
||||
if (it == g_audioout2_users.end()) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
@@ -773,13 +890,15 @@ int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle
|
||||
if (context_attributes == nullptr || port_attributes == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||
if (std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle) ==
|
||||
g_audioout2_users.end()) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
*context_attributes = 0;
|
||||
*port_attributes = 1u << AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM;
|
||||
*port_attributes = (1u << AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM) |
|
||||
(1u << AUDIO_OUT2_PORT_ATTRIBUTE_ID_GAIN) |
|
||||
(1u << AUDIO_OUT2_PORT_ATTRIBUTE_ID_AMBISONICS);
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
+17
-15
@@ -78,6 +78,8 @@ constexpr int SCE_FONT_TEXT_PARSER_RESULT_TERMINATE = 0;
|
||||
constexpr int SCE_FONT_TEXT_PARSER_RESULT_ERROR = -1;
|
||||
constexpr int SCE_FONT_WRITING_FORM_HORIZONTAL = 0x10;
|
||||
constexpr int FONT_BITMAP_MAX_DIM = 128;
|
||||
// TrueType glyphs are rasterized at the requested size; the limit only rejects absurd scales.
|
||||
constexpr int FONT_GLYPH_MAX_DIM = 4096;
|
||||
constexpr int FONT_ERROR_INVALID_PARAMETER = static_cast<int>(0x80460002u);
|
||||
constexpr int FONT_ERROR_INVALID_FONT_HANDLE = static_cast<int>(0x80460005u);
|
||||
constexpr int FONT_ERROR_NO_SUPPORT_CODE = static_cast<int>(0x80460041u);
|
||||
@@ -206,7 +208,7 @@ struct FontState {
|
||||
const uint8_t* ttf_data;
|
||||
uint32_t ttf_size;
|
||||
bool has_ttf;
|
||||
std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM> fallback_image;
|
||||
std::vector<uint8_t> fallback_image;
|
||||
FontTransImage trans_image;
|
||||
};
|
||||
|
||||
@@ -227,7 +229,7 @@ struct GlyphState {
|
||||
uint32_t code;
|
||||
int attribute;
|
||||
FontGlyphMetrics metrics;
|
||||
std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM> image;
|
||||
std::vector<uint8_t> image;
|
||||
FontTransImage trans_image;
|
||||
};
|
||||
|
||||
@@ -851,8 +853,8 @@ static bool get_stb_metrics(FontState* font, uint32_t code, FontGlyphMetrics* me
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM>* image,
|
||||
FontTransImage* trans_image, uint32_t code, FontState* font) {
|
||||
static bool init_stb_image(std::vector<uint8_t>* image, FontTransImage* trans_image, uint32_t code,
|
||||
FontState* font) {
|
||||
if (image == nullptr || trans_image == nullptr || !ensure_stb_font(font)) {
|
||||
return false;
|
||||
}
|
||||
@@ -868,12 +870,12 @@ static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP
|
||||
stbtt_GetCodepointBitmapBox(&font->font_info, static_cast<int>(code), scale_x, scale_y, &x0, &y0,
|
||||
&x1, &y1);
|
||||
|
||||
image->fill(0);
|
||||
|
||||
const auto width = static_cast<uint32_t>(std::clamp(x1 - x0, 0, FONT_BITMAP_MAX_DIM));
|
||||
const auto height = static_cast<uint32_t>(std::clamp(y1 - y0, 0, FONT_BITMAP_MAX_DIM));
|
||||
const auto width = static_cast<uint32_t>(std::clamp(x1 - x0, 0, FONT_GLYPH_MAX_DIM));
|
||||
const auto height = static_cast<uint32_t>(std::clamp(y1 - y0, 0, FONT_GLYPH_MAX_DIM));
|
||||
// Keep at least one byte so that blank glyphs still get a valid address.
|
||||
image->assign(std::max<size_t>(static_cast<size_t>(width) * height, 1), 0);
|
||||
trans_image->address = image->data();
|
||||
trans_image->width_byte = FONT_BITMAP_MAX_DIM;
|
||||
trans_image->width_byte = width;
|
||||
trans_image->image_width = width;
|
||||
trans_image->image_height = height;
|
||||
|
||||
@@ -882,7 +884,7 @@ static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP
|
||||
}
|
||||
|
||||
stbtt_MakeCodepointBitmap(&font->font_info, image->data(), static_cast<int>(width),
|
||||
static_cast<int>(height), FONT_BITMAP_MAX_DIM, scale_x, scale_y,
|
||||
static_cast<int>(height), static_cast<int>(width), scale_x, scale_y,
|
||||
static_cast<int>(code));
|
||||
|
||||
return true;
|
||||
@@ -939,8 +941,8 @@ static uint32_t normalize_bitmap_font_code(uint32_t code) {
|
||||
return static_cast<uint32_t>('?');
|
||||
}
|
||||
|
||||
static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM>* image,
|
||||
FontTransImage* trans_image, uint32_t code, const FontState* font) {
|
||||
static void init_image(std::vector<uint8_t>* image, FontTransImage* trans_image, uint32_t code,
|
||||
const FontState* font) {
|
||||
EXIT_NOT_IMPLEMENTED(image == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(trans_image == nullptr);
|
||||
|
||||
@@ -948,11 +950,11 @@ static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX
|
||||
return;
|
||||
}
|
||||
|
||||
image->fill(0);
|
||||
code = normalize_bitmap_font_code(code);
|
||||
|
||||
const auto width = static_cast<uint32_t>(scaled_font_width(font));
|
||||
const auto height = static_cast<uint32_t>(scaled_font_height(font));
|
||||
image->assign(static_cast<size_t>(width) * height, 0);
|
||||
#if LIBAVUTIL_VERSION_MAJOR < 61
|
||||
const auto* vga_font = avpriv_vga16_font;
|
||||
#else
|
||||
@@ -965,13 +967,13 @@ static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX
|
||||
for (uint32_t x = 0; x < width; x++) {
|
||||
const uint32_t src_x = std::min<uint32_t>((x * 8u) / width, 7u);
|
||||
if ((row & (0x80u >> src_x)) != 0) {
|
||||
(*image)[y * FONT_BITMAP_MAX_DIM + x] = 0xff;
|
||||
(*image)[y * width + x] = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trans_image->address = image->data();
|
||||
trans_image->width_byte = FONT_BITMAP_MAX_DIM;
|
||||
trans_image->width_byte = width;
|
||||
trans_image->image_width = width;
|
||||
trans_image->image_height = height;
|
||||
}
|
||||
|
||||
@@ -1974,6 +1974,12 @@ int64_t KYTY_SYSV_ABI fstat(int d, LibKernel::FileSystem::FileStat* sb) {
|
||||
return POSIX_N_CALL(LibKernel::FileSystem::KernelFstat(d, sb));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI fcntl(int d, int command, int arg) {
|
||||
PRINT_NAME();
|
||||
|
||||
return POSIX_N_CALL(LibKernel::FileSystem::KernelFcntl(d, command, arg));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ftruncate(int d, int64_t length) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -2163,6 +2169,7 @@ LIB_DEFINE(InitLibKernel_1_Posix) {
|
||||
LIB_FUNC("E6ao34wPw+U", stat);
|
||||
LIB_FUNC("JGMio+21L4c", mkdir);
|
||||
LIB_FUNC("ih4CD9-gghM", Posix::ftruncate);
|
||||
LIB_FUNC("8nY19bKoiZk", Posix::fcntl);
|
||||
LIB_FUNC("pDuPEf3m4fI", Posix::sem_init);
|
||||
LIB_FUNC("cDW233RAwWo", Posix::sem_destroy);
|
||||
LIB_FUNC("YCV5dGGBcCo", Posix::sem_wait);
|
||||
|
||||
+74
-3
@@ -1497,13 +1497,43 @@ namespace LibNpCommerce {
|
||||
|
||||
LIB_VERSION("NpCommerce", 1, "NpCommerce", 1, 1);
|
||||
|
||||
constexpr int COMMERCE_STATUS_NONE = 0;
|
||||
constexpr int COMMERCE_STATUS_INITIALIZED = 1;
|
||||
constexpr int COMMERCE_STATUS_NONE = 0;
|
||||
constexpr int COMMERCE_STATUS_INITIALIZED = 1;
|
||||
constexpr int COMMERCE_STATUS_FINISHED = 3;
|
||||
constexpr int COMMERCE_RESULT_USER_CANCELED = 1;
|
||||
|
||||
constexpr int COMMERCE_ERROR_NOT_INITIALIZED = static_cast<int>(0x80B80003u);
|
||||
constexpr int COMMERCE_ERROR_ALREADY_INITIALIZED = static_cast<int>(0x80B80004u);
|
||||
constexpr int COMMERCE_ERROR_NOT_FINISHED = static_cast<int>(0x80B80005u);
|
||||
constexpr int COMMERCE_ERROR_PARAM_INVALID = static_cast<int>(0x80B8000Au);
|
||||
constexpr int COMMERCE_ERROR_ARG_NULL = static_cast<int>(0x80B8000Du);
|
||||
|
||||
static int g_commerce_status = COMMERCE_STATUS_NONE;
|
||||
struct CommerceDialogParam {
|
||||
uint8_t base_param[48];
|
||||
int32_t size;
|
||||
int32_t user_id;
|
||||
int32_t mode;
|
||||
uint32_t service_label;
|
||||
const char* const* targets;
|
||||
uint32_t num_targets;
|
||||
uint32_t padding;
|
||||
uint64_t features;
|
||||
void* user_data;
|
||||
uint8_t reserved[32];
|
||||
};
|
||||
|
||||
struct CommerceDialogResult {
|
||||
int32_t result;
|
||||
bool authorized;
|
||||
void* user_data;
|
||||
uint8_t reserved[32];
|
||||
};
|
||||
|
||||
static_assert(sizeof(CommerceDialogParam) == 128 && offsetof(CommerceDialogParam, user_data) == 88);
|
||||
static_assert(sizeof(CommerceDialogResult) == 48 && offsetof(CommerceDialogResult, user_data) == 8);
|
||||
|
||||
static int g_commerce_status = COMMERCE_STATUS_NONE;
|
||||
static CommerceDialogResult g_commerce_result {};
|
||||
|
||||
static int KYTY_SYSV_ABI NpCommerceDialogInitialize() {
|
||||
PRINT_NAME();
|
||||
@@ -1520,9 +1550,47 @@ static int KYTY_SYSV_ABI NpCommerceDialogTerminate() {
|
||||
return COMMERCE_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
g_commerce_status = COMMERCE_STATUS_NONE;
|
||||
g_commerce_result = {};
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NpCommerceDialogOpen(const CommerceDialogParam* param) {
|
||||
PRINT_NAME();
|
||||
if (g_commerce_status == COMMERCE_STATUS_NONE) {
|
||||
return COMMERCE_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (param == nullptr) {
|
||||
return COMMERCE_ERROR_ARG_NULL;
|
||||
}
|
||||
g_commerce_result = {};
|
||||
if (param->size == sizeof(CommerceDialogParam)) {
|
||||
g_commerce_result.user_data = param->user_data;
|
||||
}
|
||||
g_commerce_status = COMMERCE_STATUS_FINISHED;
|
||||
if (param->size != sizeof(CommerceDialogParam) || param->mode < 0 || param->mode > 5 ||
|
||||
std::ranges::any_of(param->reserved, [](uint8_t value) { return value != 0; })) {
|
||||
g_commerce_result.result = COMMERCE_ERROR_PARAM_INVALID;
|
||||
return COMMERCE_ERROR_PARAM_INVALID;
|
||||
}
|
||||
g_commerce_result.result = COMMERCE_RESULT_USER_CANCELED;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NpCommerceDialogGetResult(CommerceDialogResult* result) {
|
||||
PRINT_NAME();
|
||||
if (g_commerce_status == COMMERCE_STATUS_NONE) {
|
||||
return COMMERCE_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (result == nullptr) {
|
||||
return COMMERCE_ERROR_ARG_NULL;
|
||||
}
|
||||
if (g_commerce_status != COMMERCE_STATUS_FINISHED) {
|
||||
return COMMERCE_ERROR_NOT_FINISHED;
|
||||
}
|
||||
*result = g_commerce_result;
|
||||
return result->result;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NpCommerceDialogUpdateStatus() {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -1532,6 +1600,8 @@ static int KYTY_SYSV_ABI NpCommerceDialogUpdateStatus() {
|
||||
LIB_DEFINE(InitNet_1_NpCommerce) {
|
||||
LIB_FUNC("0aR2aWmQal4", NpCommerceDialogInitialize);
|
||||
LIB_FUNC("m-I92Ab50W8", NpCommerceDialogTerminate);
|
||||
LIB_FUNC("DfSCDRA3EjY", NpCommerceDialogOpen);
|
||||
LIB_FUNC("r42bWcQbtZY", NpCommerceDialogGetResult);
|
||||
LIB_FUNC("LR5cwFMMCVE", NpCommerceDialogUpdateStatus);
|
||||
}
|
||||
|
||||
@@ -1559,6 +1629,7 @@ LIB_DEFINE(InitNet_1_NpManager) {
|
||||
LIB_FUNC("rbknaUjpqWo", NpManager::NpGetAccountIdA);
|
||||
LIB_FUNC("JT+t00a3TxA", NpManager::NpGetAccountCountryA);
|
||||
LIB_FUNC("+4DegjBqV1g", NpManager::NpGetAccountAge);
|
||||
LIB_FUNC("3Tcz5bNCfZQ", NpManager::NpGetAccountLanguage2);
|
||||
LIB_FUNC("GpLQDNKICac", NpManager::NpCreateRequest);
|
||||
LIB_FUNC("eiqMCt9UshI", NpManager::NpCreateAsyncRequest);
|
||||
LIB_FUNC("S7QTn72PrDw", NpManager::NpDeleteRequest);
|
||||
|
||||
+11
-41
@@ -19,27 +19,7 @@ static int KYTY_SYSV_ABI PadSetVibrationMode(int handle, int mode) {
|
||||
"\t mode = %d\n",
|
||||
handle, mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct PadTriggerEffectStateInformation {
|
||||
int32_t state[2];
|
||||
};
|
||||
|
||||
static int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle,
|
||||
PadTriggerEffectStateInformation* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t handle = %d\n", handle);
|
||||
|
||||
if (info == nullptr) {
|
||||
return -2137653243; /* 0x80960005 */
|
||||
}
|
||||
|
||||
info->state[0] = 0;
|
||||
info->state[1] = 0;
|
||||
|
||||
return 0;
|
||||
return Controller::PadCheckHandle(handle);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled) {
|
||||
@@ -86,11 +66,10 @@ static int KYTY_SYSV_ABI
|
||||
PadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */
|
||||
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
|
||||
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
|
||||
|
||||
if (handle != 1) {
|
||||
return pad_error_invalid_handle;
|
||||
if (const auto error = Controller::PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (info == nullptr) {
|
||||
return pad_error_invalid_arg;
|
||||
@@ -105,16 +84,15 @@ static int KYTY_SYSV_ABI PadDeviceClassParseData(int handle, const Controller::P
|
||||
PadDeviceClassData* class_data) {
|
||||
PRINT_NAME();
|
||||
|
||||
constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */
|
||||
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
|
||||
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
|
||||
|
||||
LOGF("\t handle = %d\n"
|
||||
"\t data = 0x%016" PRIx64 "\n"
|
||||
"\t class_data = 0x%016" PRIx64 "\n",
|
||||
handle, reinterpret_cast<uint64_t>(data), reinterpret_cast<uint64_t>(class_data));
|
||||
|
||||
if (handle != 1) {
|
||||
return pad_error_invalid_handle;
|
||||
if (const auto error = Controller::PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
}
|
||||
if (data == nullptr || class_data == nullptr) {
|
||||
return pad_error_invalid_arg;
|
||||
@@ -142,15 +120,7 @@ static int KYTY_SYSV_ABI PadSetTiltCorrectionState(int handle, bool enabled) {
|
||||
"\t enabled = %d\n",
|
||||
handle, enabled ? 1 : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PadClose(int handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t handle = %d\n", handle);
|
||||
|
||||
return OK;
|
||||
return Controller::PadCheckHandle(handle);
|
||||
}
|
||||
|
||||
// PPSA02385
|
||||
@@ -174,7 +144,7 @@ static int KYTY_SYSV_ABI PadGetExtControllerInformationStub(int handle, void* in
|
||||
"\t info = 0x%016" PRIx64 "\n",
|
||||
handle, reinterpret_cast<uint64_t>(info));
|
||||
|
||||
return OK;
|
||||
return Controller::PadCheckHandle(handle);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PadUnknownN3kSX62fgNo(uint64_t arg0, uint64_t arg1, uint64_t arg2,
|
||||
@@ -214,10 +184,10 @@ LIB_DEFINE(InitPad_1) {
|
||||
LIB_FUNC("yFVnOdGxvZY", Controller::PadSetVibration);
|
||||
LIB_FUNC("W2G-yoyMF5U", PadSetVibrationMode);
|
||||
LIB_FUNC("Yq0zOH7YNOM", PadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse);
|
||||
LIB_FUNC("znaWI0gpuo8", PadGetTriggerEffectState);
|
||||
LIB_FUNC("znaWI0gpuo8", Controller::PadGetTriggerEffectState);
|
||||
LIB_FUNC("DscD1i9HX1w", Controller::PadResetLightBar);
|
||||
LIB_FUNC("RR4novUEENY", Controller::PadSetLightBar);
|
||||
LIB_FUNC("6ncge5+l5Qs", PadClose);
|
||||
LIB_FUNC("6ncge5+l5Qs", Controller::PadClose);
|
||||
LIB_FUNC("n3kSX62fgNo", PadUnknownN3kSX62fgNo);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Libs {
|
||||
|
||||
LIB_VERSION("Sysmodule", 1, "Sysmodule", 1, 1);
|
||||
@@ -17,6 +20,16 @@ int KYTY_SYSV_ABI KernelGetModuleInfoForUnwind(uint64_t addr, int flags, ModuleI
|
||||
|
||||
namespace Sysmodule {
|
||||
|
||||
constexpr int SYSMODULE_ERROR_UNLOADED = static_cast<int>(0x805a1001u);
|
||||
|
||||
static std::mutex g_module_mutex;
|
||||
static std::unordered_map<uint16_t, uint32_t> g_module_refs;
|
||||
|
||||
static void LoadModule(uint16_t id) {
|
||||
std::scoped_lock lock(g_module_mutex);
|
||||
++g_module_refs[id];
|
||||
}
|
||||
|
||||
static KYTY_SYSV_ABI int SysmoduleGetModuleInfoForUnwind(uint64_t addr, int flags,
|
||||
LibKernel::ModuleInfoForUnwind* info) {
|
||||
return LibKernel::KernelGetModuleInfoForUnwind(addr, flags, info);
|
||||
@@ -27,6 +40,7 @@ static KYTY_SYSV_ABI int SysmoduleLoadModule(uint16_t id) {
|
||||
|
||||
LOGF("\t id = %d\n", static_cast<int>(id));
|
||||
|
||||
LoadModule(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -35,6 +49,14 @@ static KYTY_SYSV_ABI int SysmoduleUnloadModule(uint16_t id) {
|
||||
|
||||
LOGF("\t id = %d\n", static_cast<int>(id));
|
||||
|
||||
std::scoped_lock lock(g_module_mutex);
|
||||
const auto module = g_module_refs.find(id);
|
||||
if (module == g_module_refs.end()) {
|
||||
return SYSMODULE_ERROR_UNLOADED;
|
||||
}
|
||||
if (--module->second == 0) {
|
||||
g_module_refs.erase(module);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -49,6 +71,7 @@ static KYTY_SYSV_ABI int SysmoduleLoadModuleInternalWithArg(uint16_t id, int arg
|
||||
EXIT_IF(arg3 != 0);
|
||||
EXIT_IF(ret == nullptr);
|
||||
|
||||
LoadModule(id);
|
||||
*ret = 0;
|
||||
|
||||
return 0;
|
||||
@@ -59,7 +82,8 @@ static KYTY_SYSV_ABI int SysmoduleIsLoaded(uint16_t id) {
|
||||
|
||||
LOGF("\t id = %d\n", static_cast<int>(id));
|
||||
|
||||
return 0;
|
||||
std::scoped_lock lock(g_module_mutex);
|
||||
return g_module_refs.contains(id) ? 0 : SYSMODULE_ERROR_UNLOADED;
|
||||
}
|
||||
|
||||
} // namespace Sysmodule
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
#include "libs/dialog.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "libs/systemService.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
namespace Libs {
|
||||
|
||||
@@ -70,6 +73,14 @@ struct SystemServiceEvent {
|
||||
uint8_t data[8192];
|
||||
};
|
||||
|
||||
static std::mutex g_event_mutex;
|
||||
static std::deque<int32_t> g_events;
|
||||
|
||||
void NotifyEntitlementUpdate() {
|
||||
std::scoped_lock lock(g_event_mutex);
|
||||
g_events.push_back(0x10000003); // SCE_SYSTEM_SERVICE_EVENT_ENTITLEMENT_UPDATE
|
||||
}
|
||||
|
||||
struct SystemServiceDisplaySafeAreaInfo {
|
||||
float ratio;
|
||||
uint8_t reserved[128];
|
||||
@@ -153,7 +164,14 @@ static int KYTY_SYSV_ABI SystemServiceReceiveEvent(SystemServiceEvent* event) {
|
||||
event->event_type = -1;
|
||||
std::memset(event->data, 0, sizeof(event->data));
|
||||
|
||||
return SYSTEM_SERVICE_ERROR_NO_EVENT;
|
||||
std::scoped_lock lock(g_event_mutex);
|
||||
if (g_events.empty()) {
|
||||
return SYSTEM_SERVICE_ERROR_NO_EVENT;
|
||||
}
|
||||
event->event_type = g_events.front();
|
||||
g_events.pop_front();
|
||||
LOGF("\t SystemService event = 0x%08x\n", static_cast<uint32_t>(event->event_type));
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SystemServiceGetStatus(SystemServiceStatus* status) {
|
||||
@@ -167,6 +185,10 @@ static int KYTY_SYSV_ABI SystemServiceGetStatus(SystemServiceStatus* status) {
|
||||
*status = SystemServiceStatus();
|
||||
status->is_system_ui_overlaid = dialog.active;
|
||||
status->is_in_background_execution = dialog.background;
|
||||
{
|
||||
std::scoped_lock lock(g_event_mutex);
|
||||
status->event_num = static_cast<int32_t>(g_events.size());
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -206,7 +228,7 @@ static int KYTY_SYSV_ABI SystemServiceGetNoticeScreenSkipFlag(bool* value) {
|
||||
return SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
}
|
||||
|
||||
*value = false;
|
||||
*value = Config::SkipNoticeScreen();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
+25
-11
@@ -832,10 +832,6 @@ namespace Net {
|
||||
|
||||
LIB_NAME("Net", "Net");
|
||||
|
||||
struct NetEtherAddr {
|
||||
uint8_t data[6] = {0};
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
using NativeSocket = SOCKET;
|
||||
static constexpr NativeSocket INVALID_NATIVE_SOCKET = INVALID_SOCKET;
|
||||
@@ -1564,14 +1560,12 @@ const char* KYTY_SYSV_ABI NetInetNtop(int af, const void* src, char* dst, uint32
|
||||
int KYTY_SYSV_ABI NetEtherNtostr(const NetEtherAddr* n, char* str, size_t len) {
|
||||
PRINT_NAME();
|
||||
|
||||
NetEtherAddr zero {};
|
||||
if (n == nullptr || str == nullptr || len < 18) {
|
||||
return NET_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(len != 18);
|
||||
EXIT_NOT_IMPLEMENTED(n == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(str == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(memcmp(n->data, zero.data, sizeof(zero.data)) != 0);
|
||||
|
||||
strcpy(str, "00:00:00:00:00:00"); // NOLINT
|
||||
std::snprintf(str, len, "%02x:%02x:%02x:%02x:%02x:%02x", n->data[0], n->data[1],
|
||||
n->data[2], n->data[3], n->data[4], n->data[5]);
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -4112,6 +4106,26 @@ int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age) {
|
||||
return np_error_signed_out;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t req_id = %d\n", req_id);
|
||||
LOGF("\t user_id = %d\n", user_id);
|
||||
LOGF("\t language_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(language_code));
|
||||
|
||||
if (req_id <= 0 || language_code == nullptr) {
|
||||
return np_error_invalid_argument;
|
||||
}
|
||||
|
||||
std::lock_guard lock(g_np_request_mutex);
|
||||
|
||||
if (np_get_request_locked(req_id) == nullptr) {
|
||||
return np_error_request_not_found;
|
||||
}
|
||||
|
||||
return np_error_signed_out;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NpCreateRequest() {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
+4
-1
@@ -16,7 +16,9 @@ struct Lifecycle {
|
||||
|
||||
namespace Net {
|
||||
|
||||
struct NetEtherAddr;
|
||||
struct NetEtherAddr {
|
||||
uint8_t data[6] = {0};
|
||||
};
|
||||
|
||||
union NetEpollData {
|
||||
void* ptr;
|
||||
@@ -185,6 +187,7 @@ int KYTY_SYSV_ABI NpGetOnlineId(int user_id, NpOnlineId* online_id);
|
||||
int KYTY_SYSV_ABI NpGetAccountIdA(int user_id, uint64_t* account_id);
|
||||
int KYTY_SYSV_ABI NpGetAccountCountryA(int user_id, void* country_code);
|
||||
int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age);
|
||||
int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code);
|
||||
int KYTY_SYSV_ABI NpCreateRequest();
|
||||
int KYTY_SYSV_ABI NpCreateAsyncRequest(const NpCreateAsyncRequestParameter* param);
|
||||
int KYTY_SYSV_ABI NpDeleteRequest(int req_id);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef EMULATOR_LIBS_SYSTEMSERVICE_H_
|
||||
#define EMULATOR_LIBS_SYSTEMSERVICE_H_
|
||||
|
||||
namespace Libs::SystemService {
|
||||
|
||||
void NotifyEntitlementUpdate();
|
||||
|
||||
} // namespace Libs::SystemService
|
||||
|
||||
#endif
|
||||
@@ -84,9 +84,14 @@ static void PrintUsage() {
|
||||
::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n");
|
||||
::printf(
|
||||
" --readback-linear-images <true|false> Read back writable linear images on submit.\n");
|
||||
::printf(
|
||||
" --sync-raw-image-buffers <true|false> Synchronize raw reads of GPU images. Default: false.\n");
|
||||
::printf(
|
||||
" --trophy-notifications <true|false> Show trophy unlock toasts and play their sound.\n");
|
||||
::printf(" --playgo-hack Use the supplied PlayGo stub fallback.\n");
|
||||
::printf(
|
||||
" --skip-notice-screen <true|false> Skip startup logos and notices in supported games.\n"
|
||||
" Default: false.\n");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
::printf(" --redzone Protect the guest SysV red zone.\n");
|
||||
#endif
|
||||
@@ -423,11 +428,21 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--sync-raw-image-buffers") {
|
||||
if (!ParseBool(value, options.config.sync_raw_image_buffers)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--trophy-notifications") {
|
||||
if (!ParseBool(value, options.config.trophy_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--skip-notice-screen") {
|
||||
if (!ParseBool(value, options.config.skip_notice_screen)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--keymap") {
|
||||
const auto split = value.find('=');
|
||||
if (split == std::string::npos || split == 0 || split + 1 == value.size()) {
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@@ -24,6 +27,7 @@ std::vector<int> g_live_devices;
|
||||
std::vector<int> g_device_backed_handles;
|
||||
std::vector<bool> g_output_blocking;
|
||||
std::vector<std::vector<uint8_t>> g_output_pcm;
|
||||
std::vector<std::array<float, 2>> g_output_gains;
|
||||
size_t g_capture_bytes = 0;
|
||||
int g_next_device = 1;
|
||||
int g_open_waiters = 0;
|
||||
@@ -102,8 +106,9 @@ PortParam MakeParam(uint32_t data_format = 0x200) {
|
||||
return param;
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4) {
|
||||
AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4, uint32_t max_object_ports = 0) {
|
||||
ContextParam param {};
|
||||
param.max_object_ports = max_object_ports;
|
||||
param.queue_depth = queue_depth;
|
||||
param.num_grains = 512;
|
||||
AudioOut2::AudioOut2ContextHandle context = 0;
|
||||
@@ -112,6 +117,12 @@ AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4) {
|
||||
return context;
|
||||
}
|
||||
|
||||
AudioOut2::AudioOut2PortHandle CreatePort(AudioOut2::AudioOut2ContextHandle context, const PortParam& param) {
|
||||
AudioOut2::AudioOut2PortHandle port = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "port create failed");
|
||||
return port;
|
||||
}
|
||||
|
||||
void TestUserSupportedAttributes() {
|
||||
constexpr int invalid_param = static_cast<int32_t>(0x80268001u);
|
||||
constexpr int busy = static_cast<int32_t>(0x80268007u);
|
||||
@@ -123,8 +134,8 @@ void TestUserSupportedAttributes() {
|
||||
uint32_t port_attributes = UINT32_MAX;
|
||||
Check(AudioOut2::AudioOut2UserGetSupportedAttributes(user, &context_attributes,
|
||||
&port_attributes) == OK &&
|
||||
context_attributes == 0 && port_attributes == 1,
|
||||
"user capabilities do not match implemented PCM support");
|
||||
context_attributes == 0 && port_attributes == 0x103,
|
||||
"user capabilities do not match implemented attributes");
|
||||
|
||||
context_attributes = port_attributes = UINT32_MAX;
|
||||
Check(AudioOut2::AudioOut2UserGetSupportedAttributes(user, nullptr, &port_attributes) ==
|
||||
@@ -191,6 +202,13 @@ void SetPcm(AudioOut2::AudioOut2PortHandle port, const void* data) {
|
||||
"setting PCM failed");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SetAttribute(AudioOut2::AudioOut2PortHandle port, uint32_t id, const T& value) {
|
||||
const Attribute attribute {id, 0, &value, sizeof(value)};
|
||||
Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(&attribute), 1) == OK,
|
||||
"setting attribute failed");
|
||||
}
|
||||
|
||||
void ResetOutputCalls() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_output_blocking.clear();
|
||||
@@ -212,6 +230,23 @@ std::vector<std::vector<uint8_t>> OutputPcm() {
|
||||
return g_output_pcm;
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint8_t>> PushPcm(AudioOut2::AudioOut2ContextHandle context, size_t bytes) {
|
||||
CaptureOutputPcm(bytes);
|
||||
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "PCM push failed");
|
||||
auto output = OutputPcm();
|
||||
CaptureOutputPcm(0);
|
||||
return output;
|
||||
}
|
||||
|
||||
void CheckSamples(const std::vector<uint8_t>& pcm, std::initializer_list<float> frame, const char* message) {
|
||||
Check(!pcm.empty() && pcm.size() % (frame.size() * sizeof(float)) == 0, "incorrect PCM size");
|
||||
for (size_t i = 0; i < pcm.size() / sizeof(float); i++) {
|
||||
float sample;
|
||||
std::memcpy(&sample, pcm.data() + i * sizeof(float), sizeof(float));
|
||||
Check(std::abs(sample - *(frame.begin() + i % frame.size())) < 0.000001f, message);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSlotReuse() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
@@ -428,6 +463,148 @@ void TestPcmCopiedBeforeScratchBufferReuse() {
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestPortGainAndValidation() {
|
||||
const auto context = CreateContext();
|
||||
const auto port = CreatePort(context, MakeParam());
|
||||
std::vector<float> pcm(512 * 2, 0.25f);
|
||||
SetPcm(port, pcm.data());
|
||||
auto check_output = [&](const std::array<float, 2>& gain, float expected_pcm) {
|
||||
CheckSamples(PushPcm(context, pcm.size() * sizeof(float)).at(0), {expected_pcm},
|
||||
"gain modified stored PCM");
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
Check(g_output_gains.size() == 1 && g_output_gains[0] == gain,
|
||||
"incorrect per-channel gain forwarded to backend");
|
||||
};
|
||||
check_output({1.0f, 1.0f}, 0.25f);
|
||||
for (const std::array<float, 2> gain: {std::array {2.0f, 0.5f}, {0.0f, 0.0f}, {1.0f, 1.0f}}) {
|
||||
SetAttribute(port, 1, gain);
|
||||
check_output(gain, 0.25f);
|
||||
check_output(gain, 0.25f);
|
||||
}
|
||||
|
||||
const std::array gain {0.5f, 0.25f};
|
||||
SetAttribute(port, 1, gain);
|
||||
std::fill(pcm.begin(), pcm.end(), 0.75f);
|
||||
SetPcm(port, pcm.data());
|
||||
check_output(gain, 0.75f);
|
||||
|
||||
std::fill(pcm.begin(), pcm.end(), -0.25f);
|
||||
const Pcm replacement {pcm.data()};
|
||||
for (const float invalid: {-1.0f, std::numeric_limits<float>::infinity(),
|
||||
std::numeric_limits<float>::quiet_NaN()}) {
|
||||
const std::array invalid_gain {1.0f, invalid};
|
||||
const Attribute attributes[] {{0, 0, &replacement, sizeof(replacement)},
|
||||
{1, 0, &invalid_gain, sizeof(invalid_gain)}};
|
||||
Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(attributes), 2) ==
|
||||
static_cast<int32_t>(0x80268001u), "invalid gain was accepted");
|
||||
check_output(gain, 0.75f);
|
||||
}
|
||||
for (const Attribute attribute: {Attribute {1, 0, nullptr, sizeof(gain)},
|
||||
Attribute {1, 0, &gain, sizeof(float)}}) {
|
||||
Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(&attribute), 1) ==
|
||||
static_cast<int32_t>(0x80268001u), "malformed gain was accepted");
|
||||
}
|
||||
const Attribute duplicates[] {{0, 0, &replacement, sizeof(replacement)},
|
||||
{1, 0, &gain, sizeof(gain)}, {1, 0, &gain, sizeof(gain)}};
|
||||
Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(duplicates), 3) ==
|
||||
static_cast<int32_t>(0x80268001u), "duplicate gain attributes were accepted");
|
||||
check_output(gain, 0.75f);
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestObjectGainAndAmbisonics() {
|
||||
constexpr float half_power = 0.70710678f;
|
||||
struct Coefficients { uint32_t channel; float left; float right; };
|
||||
constexpr Coefficients cases[] {{UINT32_MAX, half_power, half_power}, {0, 1.0f, 1.0f},
|
||||
{1, 0.0f, 0.0f}, {2, half_power, -half_power}, {3, 0.0f, 0.0f},
|
||||
{64, half_power, half_power}, {65, half_power, -half_power},
|
||||
{66, 0.0f, 0.0f}, {99, 0.0f, 0.0f}};
|
||||
for (const auto format: {0x100u, 0x101u}) {
|
||||
const auto context = CreateContext(1, 1);
|
||||
auto param = MakeParam(format);
|
||||
param.port_type = 0x100;
|
||||
const auto object = CreatePort(context, param);
|
||||
Check(LiveDeviceCount() == 1, "object-only context did not create one output device");
|
||||
std::vector<float> floating(512, 0.5f);
|
||||
std::vector<int16_t> integer(512, 16384);
|
||||
const void* pcm = format == 0x100 ? static_cast<const void*>(floating.data()) : integer.data();
|
||||
constexpr auto output_bytes = 512 * 2 * sizeof(float);
|
||||
SetPcm(object, pcm);
|
||||
std::fill(floating.begin(), floating.end(), 0.0f);
|
||||
std::fill(integer.begin(), integer.end(), 0);
|
||||
SetAttribute(object, 1, 0.0f);
|
||||
SetAttribute(object, 8, 65u);
|
||||
for (int push = 0; push < 2; push++) {
|
||||
const auto output = PushPcm(context, output_bytes);
|
||||
Check(output.size() == 1, "object-only push did not output exactly once");
|
||||
CheckSamples(output[0], {0.5f * half_power, 0.5f * half_power},
|
||||
"object PCM ownership, default gain, or attribute freeze failed");
|
||||
}
|
||||
std::fill(floating.begin(), floating.end(), 0.5f);
|
||||
std::fill(integer.begin(), integer.end(), 16384);
|
||||
for (const auto& test: cases) {
|
||||
Check(AudioOut2::AudioOut2ContextAdvance(context) == OK, "context advance failed");
|
||||
const Pcm input {pcm};
|
||||
const float gain = 0.5f;
|
||||
const Attribute attributes[] {{0, 0, &input, sizeof(input)}, {1, 0, &gain, sizeof(gain)},
|
||||
{8, 0, &test.channel, sizeof(test.channel)}};
|
||||
Check(AudioOut2::AudioOut2PortSetAttributes(object, AsAttribute(attributes), 3) == OK,
|
||||
"object attributes could not be set atomically with PCM first");
|
||||
for (int push = 0; push < 2; push++) {
|
||||
CheckSamples(PushPcm(context, output_bytes).at(0), {0.25f * test.left, 0.25f * test.right},
|
||||
"object gain or ambisonics decoded incorrectly or accumulated across pushes");
|
||||
}
|
||||
}
|
||||
AudioOut2::AudioOut2ContextAdvance(context);
|
||||
SetAttribute(object, 8, UINT32_MAX);
|
||||
SetAttribute(object, 1, 0.0f);
|
||||
SetPcm(object, pcm);
|
||||
CheckSamples(PushPcm(context, output_bytes).at(0), {0.0f}, "zero gain did not mute object");
|
||||
AudioOut2::AudioOut2ContextAdvance(context);
|
||||
SetPcm(object, pcm);
|
||||
CheckSamples(PushPcm(context, output_bytes).at(0), {0.0f}, "object gain did not persist");
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
Check(LiveDeviceCount() == 0, "object context leaked its output device");
|
||||
}
|
||||
}
|
||||
|
||||
void TestObjectBusRouting() {
|
||||
const auto context = CreateContext(1, 2);
|
||||
const auto other_context = CreateContext(1, 1);
|
||||
AudioOut2::AudioOut2UserHandle other_user = 0;
|
||||
Check(AudioOut2::AudioOut2UserCreate(2000, &other_user) == OK, "second user create failed");
|
||||
const auto first = CreatePort(context, MakeParam());
|
||||
const auto second = CreatePort(context, MakeParam());
|
||||
std::vector<float> bed(512 * 2, 0.125f);
|
||||
SetPcm(first, bed.data());
|
||||
SetPcm(second, bed.data());
|
||||
auto param = MakeParam(0x100);
|
||||
param.port_type = 0x100;
|
||||
std::vector<float> object_pcm(512, 0.25f);
|
||||
SetPcm(CreatePort(context, param), object_pcm.data());
|
||||
param.user_handle = other_user;
|
||||
SetPcm(CreatePort(context, param), object_pcm.data());
|
||||
std::fill(object_pcm.begin(), object_pcm.end(), 1.0f);
|
||||
SetPcm(CreatePort(other_context, param), object_pcm.data());
|
||||
constexpr auto bytes = 512 * 2 * sizeof(float);
|
||||
for (int push = 0; push < 2; push++) {
|
||||
const auto output = PushPcm(context, bytes);
|
||||
Check(output.size() == 3, "object bus was missing or queued more than once");
|
||||
CheckSamples(output[0], {0.125f}, "objects modified first bed");
|
||||
CheckSamples(output[1], {0.125f}, "objects modified second bed");
|
||||
CheckSamples(output[2], {0.35355339f}, "MAIN objects failed to mix across users or leaked contexts");
|
||||
}
|
||||
SetPcm(first, nullptr);
|
||||
SetPcm(second, nullptr);
|
||||
CheckSamples(PushPcm(context, bytes).at(0), {0.35355339f}, "object output depended on bed PCM");
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
Check(LiveDeviceCount() == 1, "context destroy closed another context's object device");
|
||||
CheckSamples(PushPcm(other_context, bytes).at(0), {0.70710678f}, "other context lost its object PCM");
|
||||
AudioOut2::AudioOut2ContextDestroy(other_context);
|
||||
Check(AudioOut2::AudioOut2UserDestroy(other_user) == OK, "second user destroy failed");
|
||||
Check(LiveDeviceCount() == 0, "object bus device leaked");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::Audio::AudioInternal {
|
||||
@@ -467,10 +644,14 @@ bool AudioOutHasDevice(int handle) {
|
||||
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_output_blocking.push_back(blocking);
|
||||
g_output_gains.clear();
|
||||
if (g_capture_bytes != 0) {
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(params[i].data);
|
||||
g_output_pcm.emplace_back(bytes, bytes + g_capture_bytes);
|
||||
g_output_gains.push_back(params[i].gains != nullptr
|
||||
? std::array {params[i].gains[0], params[i].gains[1]}
|
||||
: std::array {1.0f, 1.0f});
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -499,6 +680,9 @@ int main() {
|
||||
TestAsynchronousDevicePushKeepsQueueBounded();
|
||||
TestHandleWithoutPcmDoesNotBypassQueue();
|
||||
TestPcmCopiedBeforeScratchBufferReuse();
|
||||
TestPortGainAndValidation();
|
||||
TestObjectGainAndAmbisonics();
|
||||
TestObjectBusRouting();
|
||||
Check(AudioOut2::AudioOut2UserDestroy(g_user_handle) == OK, "test user destroy failed");
|
||||
std::printf("AudioOut2PortTests: all cases passed\n");
|
||||
return 0;
|
||||
|
||||
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user