mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 07:52:39 +00:00
Compare commits
27
Commits
b2c9ec7609
...
f90a8b1589
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f90a8b1589 | ||
|
|
17a0f53d39 | ||
|
|
943bc19f46 | ||
|
|
5429888d4c | ||
|
|
025d1f7edf | ||
|
|
3c62269751 | ||
|
|
ceb154d9e4 | ||
|
|
26ca609236 | ||
|
|
4ebae87a1b | ||
|
|
b4209dde1b | ||
|
|
72d7da7266 | ||
|
|
ce5e57aa36 | ||
|
|
6357740fa1 | ||
|
|
be1c84f4d7 | ||
|
|
560756f34c | ||
|
|
52c80be266 | ||
|
|
22a9392d23 | ||
|
|
95bbc9c65d | ||
|
|
c5f7644751 | ||
|
|
4db225008b | ||
|
|
dc3bd68dee | ||
|
|
c1dfb309a1 | ||
|
|
ba7f9969ef | ||
|
|
7524e8f79e | ||
|
|
153dadd0b3 | ||
|
|
463a096249 | ||
|
|
597137e8b1 |
No files matched your search
@@ -32,7 +32,7 @@ jobs:
|
||||
windows:
|
||||
name: Build KytyPS5 (Windows)
|
||||
needs: metadata
|
||||
runs-on: windows-2022
|
||||
runs-on: windows-2025
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
@@ -27,6 +27,7 @@ struct IndirectDrawRegisters {
|
||||
uint32_t vertex_offset;
|
||||
uint32_t instance_offset;
|
||||
uint32_t index_offset;
|
||||
uint32_t draw_index;
|
||||
};
|
||||
|
||||
class Pm4Execution {
|
||||
|
||||
@@ -1582,7 +1582,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirect) {
|
||||
// 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};
|
||||
indexed && (buffer[2] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP, Pm4::SH_NOP};
|
||||
|
||||
cp.DrawIndirect(data_offset, registers, draw_initiator, indexed);
|
||||
|
||||
@@ -1602,10 +1602,12 @@ 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.
|
||||
// Multi-draw moves the first-index enable to word 4; the destinations stay in 2/3. Word 4
|
||||
// bit 31 enables the subdraw index, written to the register in its low half.
|
||||
const IndirectDrawRegisters registers {
|
||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
||||
indexed && (buffer[3] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP};
|
||||
indexed && (buffer[3] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP,
|
||||
(buffer[3] >> 31u) != 0 ? buffer[3] & 0xffffu : Pm4::SH_NOP};
|
||||
|
||||
if (count_indirect == 0) {
|
||||
count_addr = nullptr;
|
||||
|
||||
@@ -789,7 +789,7 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
|
||||
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||
uint32_t vertex_offset, uint32_t instance_offset,
|
||||
uint32_t first_index) {
|
||||
uint32_t first_index, uint32_t draw_index) {
|
||||
const auto write = [&](uint32_t location, uint32_t value) {
|
||||
if (location == Pm4::SH_NOP) {
|
||||
return;
|
||||
@@ -801,6 +801,7 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
||||
write(registers.vertex_offset, vertex_offset);
|
||||
write(registers.instance_offset, instance_offset);
|
||||
write(registers.index_offset, first_index);
|
||||
write(registers.draw_index, draw_index);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
@@ -839,7 +840,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
// while they are zero.
|
||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
||||
registers.index_offset == Pm4::SH_NOP && (!indexed || m_index_buffer_size != 0) &&
|
||||
registers.index_offset == Pm4::SH_NOP && registers.draw_index == Pm4::SH_NOP &&
|
||||
(!indexed || m_index_buffer_size != 0) &&
|
||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = m_index_buffer_size,
|
||||
.index_addr = IndexAddress(0),
|
||||
@@ -854,7 +856,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
if (!indexed) {
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0);
|
||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0, i);
|
||||
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
||||
{.vertex_count = args.vertexCount,
|
||||
.instance_count = args.instanceCount,
|
||||
@@ -866,7 +868,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
||||
args.firstInstance, args.firstIndex);
|
||||
args.firstInstance, args.firstIndex, i);
|
||||
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = std::min(args.indexCount, max_indices),
|
||||
.index_addr = IndexAddress(args.firstIndex),
|
||||
|
||||
+22
-1
@@ -783,7 +783,15 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
if (!cached.registered) {
|
||||
return {merged_id};
|
||||
}
|
||||
if (BcAlias::Matches(requested, cached.info) && !cached.IsCpuDirty()) {
|
||||
// PS5: image heaps keep BC images and raw block views of their layers resident together, so a
|
||||
// raw read of a layer subset with the same levels views the BC image instead of evicting it.
|
||||
const bool raw_view = binding == BindingType::Texture && !requested.IsBlock() &&
|
||||
requested.type == cached.info.type &&
|
||||
requested.data.address == cached.info.data.address &&
|
||||
requested.BlockExtent() == cached.info.BlockExtent() &&
|
||||
requested.resources.levels == cached.info.resources.levels &&
|
||||
requested.resources.layers < cached.info.resources.layers;
|
||||
if (!raw_view && BcAlias::Matches(requested, cached.info) && !cached.IsCpuDirty()) {
|
||||
BcAlias::Resolve(*this, cached_id, merged_id);
|
||||
return {merged_id};
|
||||
}
|
||||
@@ -1302,6 +1310,19 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
view_layer = overlap.layer;
|
||||
}
|
||||
}
|
||||
// PS5: image heaps hold a descriptor per dynamic-resolution extent of a render target,
|
||||
// and only one extent at an address holds data, so heap lookups create no second one.
|
||||
if (!result && desc.heap &&
|
||||
std::any_of(candidates.begin(), candidates.end(), [&](ImageId id) {
|
||||
const auto* image = m_slot_images.try_get(id);
|
||||
return image != nullptr && image->registered &&
|
||||
image->heap_refs.load(std::memory_order_relaxed) != 0u &&
|
||||
image->info.data.address == desc.info.data.address &&
|
||||
image->info.type == desc.info.type &&
|
||||
image->info.extent != desc.info.extent;
|
||||
})) {
|
||||
return {};
|
||||
}
|
||||
} else if (block_alias) {
|
||||
for (const auto candidate: candidates) {
|
||||
const auto& owner = m_slot_images[candidate];
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
ImageInfo info;
|
||||
ImageViewInfo view_info;
|
||||
BindingType type = BindingType::Texture;
|
||||
bool heap = false; // PS5: resolved for a resident image heap.
|
||||
};
|
||||
|
||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
|
||||
@@ -425,15 +425,15 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
const auto destination_height =
|
||||
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
||||
const bool copy = source.backing.samples == 1;
|
||||
EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
|
||||
info.extent.width > destination_width || info.extent.height > destination_height ||
|
||||
EXIT_IF(layers == 0 ||
|
||||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
||||
: source.backing.format != backing.format));
|
||||
auto resolved_source_range = source_range;
|
||||
auto resolved_destination_range = destination_range;
|
||||
resolved_source_range.layer_count = layers;
|
||||
resolved_destination_range.layer_count = layers;
|
||||
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
|
||||
const vk::Extent3D resolve_extent {std::min(source_width, destination_width),
|
||||
std::min(source_height, destination_height), 1};
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
|
||||
@@ -378,6 +378,9 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
||||
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
||||
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
const auto unorm16 = [](uint64_t value) {
|
||||
return static_cast<float>(value & 0xffffu) / 65535.0f;
|
||||
};
|
||||
const auto float16 = [](uint32_t value) {
|
||||
const auto sign = (value & 0x8000u) << 16u;
|
||||
const auto exponent = (value >> 10u) & 0x1fu;
|
||||
@@ -403,19 +406,35 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
||||
next.float32[1] = float16(packed >> 16u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break;
|
||||
case vk::Format::eR16G16Unorm:
|
||||
next.float32[1] = static_cast<float>(packed >> 16u) / 65535.0f;
|
||||
case vk::Format::eR16G16B16A16Unorm:
|
||||
next.float32[2] = unorm16(clear_words >> 32u);
|
||||
next.float32[3] = unorm16(clear_words >> 48u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Unorm:
|
||||
next.float32[0] = static_cast<float>(packed & 0xffffu) / 65535.0f;
|
||||
case vk::Format::eR16G16Unorm:
|
||||
next.float32[1] = unorm16(packed >> 16u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Unorm: next.float32[0] = unorm16(packed); break;
|
||||
case vk::Format::eR16G16B16A16Uint:
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
next.uint32[i] = static_cast<uint16_t>(clear_words >> (16u * i));
|
||||
}
|
||||
break;
|
||||
// Each clear register stores one raw 32-bit float channel.
|
||||
case vk::Format::eR16G16B16A16Sint:
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
next.int32[i] = static_cast<int16_t>(clear_words >> (16u * i));
|
||||
}
|
||||
break;
|
||||
// Each clear register stores one raw 32-bit channel.
|
||||
case vk::Format::eR32G32Sfloat:
|
||||
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
|
||||
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
|
||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
||||
case vk::Format::eR32G32Uint:
|
||||
case vk::Format::eR32G32Sint:
|
||||
next.uint32[1] = static_cast<uint32_t>(clear_words >> 32u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR32Uint:
|
||||
case vk::Format::eR32Sint: next.uint32[0] = packed; break;
|
||||
// Narrow targets hold their channels in the low bits of the first word. UFC 6 (PPSA23566)
|
||||
// fast-clears an R8_UINT map through CMASK every frame and then writes only part of it;
|
||||
// without these the clear was dropped and old contents piled up.
|
||||
|
||||
@@ -604,8 +604,13 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
view_levels, desc.info.resources.layers);
|
||||
desc.view_info.base_level = view_base;
|
||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
||||
desc.heap = resource.heap;
|
||||
|
||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||
if (!id) {
|
||||
// Another extent at the address holds it; the heap retries once an image is created.
|
||||
return {.desc = std::move(desc), .layout = vk::ImageLayout::eGeneral};
|
||||
}
|
||||
auto* image = &texture_cache.GetImage(id);
|
||||
const bool stencil_association = static_cast<bool>(image->depth_id);
|
||||
if (stencil_association) {
|
||||
@@ -801,6 +806,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
||||
resource, buffer_offset);
|
||||
pack_memory_offset(index, buffer_offset);
|
||||
// Null bindings allow 0 bytes, not the shared null buffer's 16, so no store reaches it.
|
||||
prepared.shader_data[layout.MemorySizeDword() + index] =
|
||||
static_cast<uint32_t>(prepared.buffer_sources[index].size + buffer_offset);
|
||||
}
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] != 0) {
|
||||
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -44,6 +45,20 @@ void Widen(size_t& begin, size_t& end, size_t first, size_t last) {
|
||||
end = empty ? last : std::max(end, last);
|
||||
}
|
||||
|
||||
// Probes at first + k * step whose first dword lies within a table of size bytes.
|
||||
uint64_t Probes(uint64_t size, uint64_t first, uint64_t step) {
|
||||
return size >= first + 4u ? (size - first - 4u) / step + 1u : 0u;
|
||||
}
|
||||
|
||||
// Tables grow a little on most frames, so their slot words get room to double.
|
||||
std::unique_ptr<Buffer> SlotBuffer(RenderContext& context, uint64_t size) {
|
||||
return std::make_unique<Buffer>(
|
||||
context.GetGraphics(), context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||
std::bit_ceil(size + 4u));
|
||||
}
|
||||
|
||||
// Guest bytes of a table that its tracker pages [begin, end) cover.
|
||||
std::pair<uint64_t, uint64_t> PageRange(uint64_t base, uint64_t size, size_t begin, size_t end) {
|
||||
const auto origin = Common::AlignDown(base, TRACKER_PAGE_SIZE);
|
||||
@@ -122,22 +137,17 @@ bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView&
|
||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||
return false;
|
||||
}
|
||||
auto memory = std::ranges::find_if(m_memories, [&](const auto& candidate) {
|
||||
return candidate->base == request.base && candidate->size == size;
|
||||
});
|
||||
if (memory == m_memories.end()) {
|
||||
auto memory = std::ranges::find_if(
|
||||
m_memories, [&](const auto& candidate) { return candidate->base == request.base; });
|
||||
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
||||
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||
if (!m_context.IsMapped(request.base, size)) return false;
|
||||
auto created = std::make_unique<Memory>();
|
||||
created->base = request.base;
|
||||
created->size = size;
|
||||
created->words.resize(size / 4u);
|
||||
created->dead.resize(size / 4u);
|
||||
created->dirty_pages.assign((Common::AlignUp(request.base + size, TRACKER_PAGE_SIZE) -
|
||||
Common::AlignDown(request.base, TRACKER_PAGE_SIZE)) /
|
||||
TRACKER_PAGE_SIZE,
|
||||
uint8_t {1});
|
||||
std::scoped_lock lock {m_mutex};
|
||||
memory = m_memories.insert(m_memories.end(), std::move(created));
|
||||
if (memory == m_memories.end()) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
memory = m_memories.insert(m_memories.end(),
|
||||
std::make_unique<Memory>(Memory {.base = request.base}));
|
||||
}
|
||||
Resize(**memory, size);
|
||||
}
|
||||
// A draw refreshes a table and a view takes its changes once, so all programs of the draw see
|
||||
// one snapshot and one set of kinds.
|
||||
@@ -146,27 +156,28 @@ bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView&
|
||||
(*memory)->draw = m_draw;
|
||||
}
|
||||
auto& views = (*memory)->views;
|
||||
const auto bytes = (*memory)->size;
|
||||
const auto profile = IR::ImageHeapResource(*request.root, 0u);
|
||||
const auto probes = Probes(size, request.first, request.step);
|
||||
const auto limit = request.count < probes ? request.count : UINT64_MAX;
|
||||
auto view = std::ranges::find_if(views, [&](const auto& candidate) {
|
||||
return candidate->step == request.step && candidate->first == request.first &&
|
||||
candidate->count == request.count && candidate->profile == profile;
|
||||
candidate->limit == limit && candidate->profile == profile;
|
||||
});
|
||||
if (view == views.end()) {
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
const auto count = std::min(limit, Probes(bytes, request.first, request.step));
|
||||
auto created = std::make_unique<View>(View {.memory = **memory,
|
||||
.step = request.step,
|
||||
.first = request.first,
|
||||
.count = request.count,
|
||||
.count = count,
|
||||
.limit = limit,
|
||||
.profile = profile,
|
||||
.resolve_epoch = epoch,
|
||||
.layout_epoch = epoch});
|
||||
created->slots.resize(size / 4u + 1u);
|
||||
created->buffer = std::make_unique<Buffer>(
|
||||
m_context.GetGraphics(), m_context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||
size + 4u);
|
||||
UpdateProbes(*created, 0u, size);
|
||||
created->slots.resize(bytes / 4u + 1u);
|
||||
created->buffer = SlotBuffer(m_context, bytes);
|
||||
UpdateProbes(*created, 0u, bytes);
|
||||
// The first upload also clears the slots no probe selects.
|
||||
created->dirty_begin = 0;
|
||||
created->dirty_end = created->slots.size();
|
||||
@@ -254,10 +265,12 @@ void ImageHeap::Unwatch(const Memory& memory) {
|
||||
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (auto& memory: m_memories) {
|
||||
const auto begin = std::max(address, memory->base);
|
||||
const auto end = std::min(address + size, memory->base + memory->size);
|
||||
if (begin >= end) continue;
|
||||
// Watches cover whole pages, so a write next to the table must unwatch its page too.
|
||||
const auto origin = Common::AlignDown(memory->base, TRACKER_PAGE_SIZE);
|
||||
const auto begin = std::max(address, origin);
|
||||
const auto end = std::min(address + size,
|
||||
origin + memory->dirty_pages.size() * TRACKER_PAGE_SIZE);
|
||||
if (begin >= end) continue;
|
||||
MarkDirty(*memory, (begin - origin) / TRACKER_PAGE_SIZE,
|
||||
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
||||
}
|
||||
@@ -381,15 +394,56 @@ uint32_t ImageHeap::Allocate() {
|
||||
}
|
||||
|
||||
void ImageHeap::Retire(View& view) {
|
||||
if (view.released.empty()) return;
|
||||
// A retired index is reused once its tick ends, so no held entry may keep it.
|
||||
std::erase_if(view.held, [&](uint32_t index) { return m_elements[index].refs == 0u; });
|
||||
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
||||
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
||||
view.released.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Resize(Memory& memory, uint64_t size) {
|
||||
{
|
||||
std::scoped_lock lock {m_mutex};
|
||||
Unwatch(memory);
|
||||
memory.size = size;
|
||||
// The next refresh rereads the whole table.
|
||||
memory.dirty_pages.assign((Common::AlignUp(memory.base + size, TRACKER_PAGE_SIZE) -
|
||||
Common::AlignDown(memory.base, TRACKER_PAGE_SIZE)) /
|
||||
TRACKER_PAGE_SIZE,
|
||||
uint8_t {1});
|
||||
}
|
||||
memory.words.resize(size / 4u);
|
||||
memory.dead.resize(size / 4u);
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
for (auto& view: memory.views) {
|
||||
const auto count = std::min(view->limit, Probes(size, view->first, view->step));
|
||||
for (auto probe = count; probe < view->count; ++probe) {
|
||||
Assign(*view, (view->first + probe * view->step) / 4u, 0u);
|
||||
}
|
||||
view->count = count;
|
||||
const auto slots = view->slots.size();
|
||||
view->slots.resize(size / 4u + 1u);
|
||||
view->dirty_end = std::min(view->dirty_end, view->slots.size());
|
||||
view->dirty_begin = std::min(view->dirty_begin, view->dirty_end);
|
||||
if (view->slots.size() * sizeof(uint32_t) > view->buffer->Size()) {
|
||||
if (scheduler.Active()) {
|
||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||
}
|
||||
view->buffer = SlotBuffer(m_context, size);
|
||||
Widen(view->dirty_begin, view->dirty_end, 0u, view->slots.size());
|
||||
} else if (slots < view->slots.size()) {
|
||||
// Device words past the old end may hold stale slots.
|
||||
Widen(view->dirty_begin, view->dirty_end, slots, view->slots.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto& textures = m_context.GetTextureCache();
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
const auto serial = Image::serials.load(std::memory_order_relaxed);
|
||||
for (auto* view: m_active) {
|
||||
bool stale = false;
|
||||
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
||||
@@ -418,6 +472,19 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
mark(index);
|
||||
return true;
|
||||
});
|
||||
// Held elements retry once an image is created, such as a render target of their extent.
|
||||
if (view->held_serial != serial) {
|
||||
view->held_serial = serial;
|
||||
std::erase_if(view->held, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
if (element.refs == 0u || element.stale) return true;
|
||||
const auto binding = executor.ResolveTexture(
|
||||
IR::ImageHeapResource(view->profile, element.kind), element.value);
|
||||
if (binding.image_id) mark(index);
|
||||
// An unmapped texture stays null.
|
||||
return binding.image_id || binding.desc.info.data.Empty();
|
||||
});
|
||||
}
|
||||
if (stale) {
|
||||
// A changed image needs a new element; pending draws still read the old one. The new
|
||||
// element keeps the old value, since a view takes table changes only when acquired.
|
||||
@@ -446,6 +513,8 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
view->dcc.push_back(index);
|
||||
}
|
||||
view->away.push_back(index);
|
||||
} else if (!binding.desc.info.data.Empty()) {
|
||||
view->held.push_back(index);
|
||||
}
|
||||
m_elements[index].binding = std::move(binding);
|
||||
m_writes.push_back(index);
|
||||
|
||||
@@ -22,10 +22,11 @@ class RenderExecutor;
|
||||
struct ImageHeapTestAccess;
|
||||
|
||||
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
||||
// sampled-image array. Each view of a table owns device slot words, one per table dword, that
|
||||
// select an array element. CPU writes to a table fault in its snapshot, which each draw refreshes
|
||||
// once; a view takes the changes when a draw first acquires it. Changes to the images behind
|
||||
// elements bump Image::heap_epoch.
|
||||
// sampled-image array. A table is keyed by its base and follows its descriptor's size. Each view
|
||||
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||
// Image::heap_epoch.
|
||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||
public:
|
||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||
@@ -70,6 +71,7 @@ private:
|
||||
uint64_t step = 0;
|
||||
uint64_t first = 0;
|
||||
uint64_t count = 0;
|
||||
uint64_t limit = 0; // Probes a key domain allows, if fewer.
|
||||
ShaderRecompiler::IR::ImageResource profile;
|
||||
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
||||
std::vector<uint32_t> kind_keys;
|
||||
@@ -79,6 +81,7 @@ private:
|
||||
std::vector<uint32_t> released; // No slot selects them after the next upload.
|
||||
std::vector<uint32_t> away; // Not yet moved to the read layout.
|
||||
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
||||
std::vector<uint32_t> held; // Another extent holds their address.
|
||||
std::unique_ptr<Buffer> buffer;
|
||||
size_t dirty_begin = 0; // Slots to upload.
|
||||
size_t dirty_end = 0;
|
||||
@@ -86,6 +89,7 @@ private:
|
||||
size_t changed_end = 0;
|
||||
uint64_t resolve_epoch = 0;
|
||||
uint64_t layout_epoch = 0;
|
||||
uint64_t held_serial = 0; // Image::serials when held was last retried.
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
@@ -113,6 +117,7 @@ private:
|
||||
Image* ImageOf(const Element& element) const;
|
||||
uint32_t Allocate();
|
||||
void Retire(View& view);
|
||||
void Resize(Memory& memory, uint64_t size);
|
||||
void WriteDescriptors();
|
||||
|
||||
RenderContext& m_context;
|
||||
|
||||
@@ -711,8 +711,13 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
||||
mesh.max_vertices > limits.maxMeshOutputVertices ||
|
||||
mesh.max_primitives > limits.maxMeshOutputPrimitives ||
|
||||
mesh.lds_size_dwords * sizeof(uint32_t) > limits.maxMeshSharedMemorySize) {
|
||||
EXIT("mesh shader exceeds host limits: threads=%u vertices=%u primitives=%u LDS=%u\n",
|
||||
host_threads, mesh.max_vertices, mesh.max_primitives, mesh.lds_size_dwords);
|
||||
static std::atomic_bool warned = false;
|
||||
if (!warned.exchange(true, std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"Warning: dropping mesh draws the host does not support (shader 0x{:016x}).\n",
|
||||
vertex_params[0].hash));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
ShaderParams pixel_params;
|
||||
@@ -736,6 +741,11 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
||||
const auto& rt = context.GetRenderTarget(slot);
|
||||
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
|
||||
if (rt.base.addr == 0 || target_mask == 0u) continue;
|
||||
if (rt.info.channel_type == Prospero::ChannelType::kUInt) {
|
||||
pixel_info.target_uint_mask |= 1u << export_index;
|
||||
} else if (rt.info.channel_type == Prospero::ChannelType::kSInt) {
|
||||
pixel_info.target_sint_mask |= 1u << export_index;
|
||||
}
|
||||
const auto format = Prospero::ResolveRenderTargetFormat(rt.info.format, rt.info.channel_type);
|
||||
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
|
||||
const auto mapping = pixel_info.target_export_mapping[export_index];
|
||||
@@ -767,9 +777,11 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
||||
(blend.separate_alpha_blend && (BlendFactorIsDualSource(blend.alpha_srcblend) ||
|
||||
BlendFactorIsDualSource(blend.alpha_destblend))));
|
||||
if (pixel_info.dual_source_blending) {
|
||||
// MRT1 supplies the second blend source for target 0.
|
||||
// MRT1 supplies the second blend source for target 0; blended targets are not integer.
|
||||
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
||||
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0];
|
||||
pixel_info.target_uint_mask &= ~2u;
|
||||
pixel_info.target_sint_mask &= ~2u;
|
||||
} else if (blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||
pixel_info.target_output_mode[0] != 0 && pixel_info.target_output_mode[0] != 7 &&
|
||||
std::all_of(std::begin(pixel_info.target_output_mode) + 1,
|
||||
|
||||
@@ -843,6 +843,11 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal
|
||||
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||
}
|
||||
m_context.GetPipelineCache().GetGraphicsPrograms(buffer, state);
|
||||
// Vertex programs beyond the host mesh limits or with unsupported call targets are not
|
||||
// compiled.
|
||||
if (state.vertex_info[0].stage.module == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||
}
|
||||
@@ -1156,7 +1161,6 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (mesh_active) {
|
||||
draw_data = {draw.index_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset),
|
||||
emit.first_instance,
|
||||
index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
@@ -1475,8 +1479,9 @@ bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_
|
||||
cache.MarkGpuWritten(dst.image_id);
|
||||
auto& source = cache.GetImage(src.image_id);
|
||||
auto& destination = cache.GetImage(dst.image_id);
|
||||
destination.Resolve(source, {src.guest_mip_level, 1, src.guest_array_layer, 1},
|
||||
{dst.guest_mip_level, 1, dst.guest_array_layer, 1});
|
||||
destination.Resolve(source,
|
||||
{src.desc.view_info.base_level, 1, src.desc.view_info.base_layer, 1},
|
||||
{dst.desc.view_info.base_level, 1, dst.desc.view_info.base_layer, 1});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -337,6 +337,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
check_feature(device_features2.features.shaderImageGatherExtended,
|
||||
"shaderImageGatherExtended");
|
||||
check_feature(device_features2.features.shaderResourceMinLod, "shaderResourceMinLod");
|
||||
|
||||
check_feature(device_features2.features.independentBlend, "independentBlend");
|
||||
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
||||
@@ -636,6 +637,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.shaderStorageImageMultisample =
|
||||
supported_features2.features.shaderStorageImageMultisample;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.shaderResourceMinLod = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
@@ -733,6 +735,15 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &conditional_rendering;
|
||||
}
|
||||
// The renderer only uses the Vulkan 1.3 core dynamic states, but RADV (Mesa 26.3) creates its
|
||||
// PS epilog cache only when this EXT feature is enabled and crashes in vkCmdDraw when a
|
||||
// depth- or stencil-writing fragment shader needs an epilog.
|
||||
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extended_dynamic_state {};
|
||||
extended_dynamic_state.extendedDynamicState = VK_TRUE;
|
||||
if (HasExtension(device_extensions, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)) {
|
||||
extended_dynamic_state.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &extended_dynamic_state;
|
||||
}
|
||||
create_info.pQueueCreateInfos = &queue_create_info;
|
||||
create_info.queueCreateInfoCount = 1;
|
||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||
@@ -1111,7 +1122,8 @@ void WindowContext::CreateVulkan() {
|
||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
||||
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME}) {
|
||||
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME}) {
|
||||
if (HasExtension(available_extensions, extension)) {
|
||||
device_extensions.push_back(extension);
|
||||
}
|
||||
|
||||
@@ -467,6 +467,7 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
||||
void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
using namespace IR;
|
||||
EXIT_NOT_IMPLEMENTED(options.stage != ShaderType::Compute);
|
||||
source.wave_size = options.wave_size;
|
||||
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
||||
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
||||
source.calls.push_back({.instruction = i});
|
||||
@@ -503,7 +504,8 @@ void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
};
|
||||
Register = [&](uint32_t before, uint32_t code) -> Value {
|
||||
std::vector<uint32_t> crossed_calls;
|
||||
const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code, crossed_calls);
|
||||
const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code,
|
||||
source.wave_size, crossed_calls);
|
||||
for (const auto crossed: crossed_calls) {
|
||||
std::ranges::find(source.calls, crossed, &ShaderSource::Call::instruction)->preserved_registers.set(code);
|
||||
}
|
||||
@@ -702,10 +704,11 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
EXIT_IF(function_pc > UINT32_MAX - function_size);
|
||||
const auto function_offset = static_cast<uint32_t>(linked.code.size());
|
||||
for (auto inst: function) {
|
||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg) ||
|
||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u));
|
||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg, source.wave_size) ||
|
||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u, source.wave_size));
|
||||
for (uint32_t code = 0; code < site.preserved_registers.size(); ++code) {
|
||||
if (site.preserved_registers[code] && CFG::MayWriteScalarRegister(inst, code))
|
||||
if (site.preserved_registers[code] &&
|
||||
CFG::MayWriteScalarRegister(inst, code, source.wave_size))
|
||||
EXIT("shader call at pc 0x%08x clobbers scalar target dependency s%u", call.pc, code);
|
||||
}
|
||||
linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count);
|
||||
|
||||
@@ -62,6 +62,7 @@ struct ShaderSource {
|
||||
IR::ResourcePlan call_targets;
|
||||
std::vector<Call> calls;
|
||||
uint32_t unsupported_call_pc = UINT32_MAX;
|
||||
uint32_t wave_size = 64;
|
||||
std::optional<Linked> linked;
|
||||
uint64_t revision = 0;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> reads;
|
||||
|
||||
@@ -30,6 +30,13 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr) {
|
||||
ShaderPixelParameterIsCustom(*state.input_info.pixel, attr);
|
||||
}
|
||||
|
||||
uint32_t MrtOutputType(EmitterState& state, uint32_t index) {
|
||||
const auto& pixel = *state.input_info.pixel;
|
||||
if ((pixel.target_sint_mask & (1u << index)) != 0u) return TypeI32Vector(state, 4);
|
||||
if ((pixel.target_uint_mask & (1u << index)) != 0u) return TypeU32Vector(state, 4);
|
||||
return TypeF32Vector(state, 4);
|
||||
}
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
||||
return state.per_vertex_variable;
|
||||
|
||||
@@ -291,9 +291,10 @@ uint32_t ExportRawComponent(ValueEmitContext& ctx, uint32_t vector, uint32_t com
|
||||
}
|
||||
|
||||
uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp,
|
||||
bool uint_output) {
|
||||
auto& state = ctx.state;
|
||||
if (exp.compr && !uint_output) {
|
||||
uint32_t vector_type) {
|
||||
auto& state = ctx.state;
|
||||
const bool integer_output = vector_type != TypeF32Vector(state, 4);
|
||||
if (exp.compr && !integer_output) {
|
||||
auto unpack = GLSLstd450UnpackHalf2x16;
|
||||
switch (MrtOutputMode(state, exp)) {
|
||||
case 5: unpack = GLSLstd450UnpackUnorm2x16; break;
|
||||
@@ -330,9 +331,11 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, 0),
|
||||
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
|
||||
ConstantU32(state, integer_output ? 1u : 0x3f800000u),
|
||||
};
|
||||
if (exp.compr) {
|
||||
const auto extract = vector_type == TypeU32Vector(state, 4) ? spv::OpBitFieldUExtract
|
||||
: spv::OpBitFieldSExtract;
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||
continue;
|
||||
@@ -344,8 +347,8 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
continue;
|
||||
}
|
||||
raw[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
state.builder.AddFunction(extract, TypeU32(state), raw[component], packed,
|
||||
ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16));
|
||||
}
|
||||
}
|
||||
@@ -360,12 +363,10 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), data,
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
}
|
||||
if (uint_output) {
|
||||
if (vector_type == TypeU32Vector(state, 4)) {
|
||||
return data;
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32Vector(state, 4), vector, data);
|
||||
return vector;
|
||||
return Unary(state, spv::OpBitcast, vector_type, data);
|
||||
}
|
||||
|
||||
uint32_t DistancePointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
|
||||
@@ -563,11 +564,11 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (state.program.stage != ShaderType::Mesh && variable == 0) {
|
||||
return;
|
||||
}
|
||||
const bool uint_output = MrtOutputMode(state, exp) == 7u;
|
||||
const auto vector_type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
auto value = ExportVector(ctx, data, exp, uint_output);
|
||||
const auto vector_type = MrtOutputMode(state, exp) != 0u ? MrtOutputType(state, exp.index)
|
||||
: TypeF32Vector(state, 4);
|
||||
auto value = ExportVector(ctx, data, exp, vector_type);
|
||||
if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
||||
exp.index == 0 && !uint_output &&
|
||||
exp.index == 0 && vector_type == TypeF32Vector(state, 4) &&
|
||||
state.input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None) {
|
||||
// Broadcast logical alpha before swizzling the primary output.
|
||||
const auto blend_output =
|
||||
@@ -711,9 +712,16 @@ void EmitBarrier(EmitterState& state) {
|
||||
return;
|
||||
}
|
||||
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
||||
const auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
||||
? spv::MemorySemanticsUniformMemoryMask
|
||||
: spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
||||
? spv::MemorySemanticsUniformMemoryMask
|
||||
: spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
// Shaders read other waves' buffer and image stores with plain loads right after S_BARRIER.
|
||||
const auto written = [](const auto& resource) { return resource.written; };
|
||||
if (std::ranges::any_of(state.program.info.buffers, written) ||
|
||||
std::ranges::any_of(state.program.info.images, written)) {
|
||||
memory = memory | spv::MemorySemanticsUniformMemoryMask |
|
||||
spv::MemorySemanticsImageMemoryMask;
|
||||
}
|
||||
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
|
||||
: spv::MemorySemanticsAcquireReleaseMask | memory;
|
||||
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
@@ -811,6 +819,13 @@ uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
|
||||
return ctx.Ballot(predicate);
|
||||
}
|
||||
|
||||
uint32_t EmitLiveMask(EmitterState& state) {
|
||||
const auto helper =
|
||||
EmitNative<spv::OpLoad, IR::Type::U1>(state, state.helper_invocation_variable);
|
||||
return EmitNative<spv::OpGroupNonUniformBallot, IR::Type::U32x4>(
|
||||
state, ConstantU32(state, spv::ScopeSubgroup), EmitLogicalNotBool(state, helper));
|
||||
}
|
||||
|
||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto lane = ctx.FirstLane(ballot);
|
||||
@@ -821,18 +836,28 @@ uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return ctx.Shuffle(inst, 0, ctx.Arg(inst, 1));
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
template <spv::Op Reduce, auto Combine>
|
||||
static uint32_t EmitSubgroupReduceU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
auto value = ctx.Arg(inst, 0);
|
||||
if (ctx.other_half != nullptr) {
|
||||
value = EmitUMin32(state, ctx.HalfArg(inst, 0, 0), ctx.HalfArg(inst, 0, 1));
|
||||
value = Combine(state, ctx.HalfArg(inst, 0, 0), ctx.HalfArg(inst, 0, 1));
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpGroupNonUniformUMin, TypeU32(state), result,
|
||||
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce, value);
|
||||
state.builder.AddFunction(Reduce, TypeU32(state), result,
|
||||
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce,
|
||||
value);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return EmitSubgroupReduceU32<spv::OpGroupNonUniformUMin, EmitUMin32>(ctx, inst);
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return EmitSubgroupReduceU32<spv::OpGroupNonUniformUMax, EmitUMax32>(ctx, inst);
|
||||
}
|
||||
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto hit = state.builder.AllocateId();
|
||||
|
||||
@@ -77,6 +77,7 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||
layout.coord = cursor;
|
||||
cursor += info.coordinate_components;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLodClamp)) layout.clamp = cursor++;
|
||||
return layout;
|
||||
}
|
||||
|
||||
@@ -1069,7 +1070,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
}
|
||||
std::array<uint32_t, 4> operands {};
|
||||
// _cl clamps the computed LOD from below.
|
||||
uint32_t min_lod = 0;
|
||||
if (layout.clamp != NoImageComponent) {
|
||||
min_lod = AddressF32(ctx, mem, *address, layout.clamp);
|
||||
}
|
||||
std::array<uint32_t, 5> operands {};
|
||||
size_t operand_count = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
operands[0] = spv::ImageOperandsGradMask;
|
||||
@@ -1081,6 +1087,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||
lod = AddressF32(ctx, mem, *address, layout.lod);
|
||||
}
|
||||
// SPIR-V takes MinLod only with implicit LOD or Grad.
|
||||
if (min_lod != 0) {
|
||||
lod = EmitGlsl<GLSLstd450FMax, IR::Type::F32>(state, lod, min_lod);
|
||||
min_lod = 0;
|
||||
}
|
||||
operands[0] = spv::ImageOperandsLodMask;
|
||||
operands[1] = lod;
|
||||
operand_count = 2;
|
||||
@@ -1104,6 +1115,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
sample_operands[sample_operand_count++] = PackedOffset(
|
||||
ctx, offset_value, candidate.dimension, sample_operands[0]);
|
||||
}
|
||||
// MinLod follows the offset in operand order.
|
||||
if (min_lod != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilityMinLod);
|
||||
sample_operand_count = std::max(size_t{1}, sample_operand_count);
|
||||
sample_operands[0] |= spv::ImageOperandsMinLodMask;
|
||||
sample_operands[sample_operand_count++] = min_lod;
|
||||
}
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
|
||||
@@ -230,10 +230,12 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& in
|
||||
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
|
||||
uint32_t EmitLaneId(EmitterState& state);
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||
uint32_t EmitLiveMask(EmitterState& state);
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
@@ -112,6 +112,7 @@ struct EmitterState {
|
||||
uint32_t lane_half = 0;
|
||||
std::array<BufferDefinition, 4> storage_buffers {};
|
||||
std::vector<uint32_t> memory_byte_offsets;
|
||||
std::vector<uint32_t> memory_byte_sizes;
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
@@ -145,6 +146,7 @@ struct EmitterState {
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t pixel_valid_mask_variable = 0;
|
||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||
uint32_t helper_invocation_variable = 0;
|
||||
uint32_t per_vertex_variable = 0;
|
||||
uint32_t point_size_variable = 0;
|
||||
uint32_t clip_distance_variable = 0;
|
||||
@@ -282,6 +284,7 @@ struct ImageSampleLayout {
|
||||
uint32_t bias = NoImageComponent;
|
||||
uint32_t coord = 0;
|
||||
uint32_t lod = NoImageComponent;
|
||||
uint32_t clamp = NoImageComponent;
|
||||
uint32_t grad_x = NoImageComponent;
|
||||
uint32_t grad_y = NoImageComponent;
|
||||
};
|
||||
@@ -305,6 +308,7 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
|
||||
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
|
||||
uint32_t MrtOutputType(EmitterState& state, uint32_t index);
|
||||
|
||||
uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
||||
|
||||
@@ -414,7 +418,7 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
|
||||
|
||||
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state);
|
||||
void EmitMemoryBindings(EmitterState& state);
|
||||
|
||||
uint32_t LdsDwordCount(const EmitterState& state);
|
||||
void EnsureLdsStorage(EmitterState& state);
|
||||
|
||||
@@ -1541,16 +1541,16 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto index =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto exec = ctx.Arg(inst, 1);
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto low = state.builder.AllocateId();
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, ballot, 0);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, ballot, 1);
|
||||
// Elect and count from the guest EXEC words, which V_MBCNT also ranks lanes by.
|
||||
const auto low = ctx.Arg(inst, 1);
|
||||
const auto high = ctx.Arg(inst, 2);
|
||||
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
||||
Unary(state, spv::OpBitCount, TypeU32(state), high));
|
||||
const auto first = ctx.FirstLane(ballot);
|
||||
const auto mask = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), mask, low, high,
|
||||
ConstantU32(state, 0), ConstantU32(state, 0));
|
||||
const auto first = ctx.FirstLane(mask);
|
||||
const auto source_lane =
|
||||
state.lane_count == 2
|
||||
? Binary(state, spv::OpBitwiseAnd, TypeU32(state), first, ConstantU32(state, 31))
|
||||
@@ -1564,9 +1564,7 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto condition = AndCondition(
|
||||
state, is_first,
|
||||
AndCondition(state,
|
||||
state.lane_count == 2 ? Binary(state, spv::OpINotEqual, TypeBool(state), count,
|
||||
ConstantU32(state, 0))
|
||||
: exec,
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), count, ConstantU32(state, 0)),
|
||||
bounds));
|
||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||
const auto value = state.builder.AllocateId();
|
||||
|
||||
@@ -154,9 +154,10 @@ uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resour
|
||||
return representative;
|
||||
}
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state) {
|
||||
void EmitMemoryBindings(EmitterState& state) {
|
||||
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
|
||||
state.memory_byte_offsets.resize(count);
|
||||
state.memory_byte_sizes.resize(count);
|
||||
std::vector<bool> dynamic_offsets(count);
|
||||
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
||||
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
||||
@@ -174,6 +175,8 @@ void EmitMemoryOffsets(EmitterState& state) {
|
||||
if (dynamic_offsets[i + lane]) continue;
|
||||
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
||||
state.memory_byte_sizes[i + lane] =
|
||||
EmitShaderDataDwordLoad(state, state.program.bindings.MemorySizeDword() + i + lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,22 +260,28 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
||||
const auto offsets_start = state.program.bindings.memory_offset_dword +
|
||||
(state.program.bindings.UsesPushData() ? state.program.bindings.push_data_start_dword : 0u);
|
||||
const auto& bindings = state.program.bindings;
|
||||
const auto data_start = bindings.UsesPushData() ? bindings.push_data_start_dword : 0u;
|
||||
const auto word = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||
state, ConstantU32(state, offsets_start),
|
||||
state, ConstantU32(state, bindings.memory_offset_dword + data_start),
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), array_index, ConstantU32(state, 2))));
|
||||
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
||||
ConstantU32(state, 3));
|
||||
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, shift, ConstantU32(state, 8));
|
||||
access.length = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||
state, ConstantU32(state, bindings.MemorySizeDword() + data_start), array_index));
|
||||
} else {
|
||||
access.byte_offset = state.memory_byte_offsets[static_index];
|
||||
access.length = state.memory_byte_sizes[static_index];
|
||||
}
|
||||
// storage_buffers[i] views the binding as 1 << i byte elements.
|
||||
const auto shift = static_cast<uint32_t>(&buffer - state.storage_buffers.data());
|
||||
if (shift != 0u) {
|
||||
access.length = Binary(state, spv::OpShiftRightLogical, TypeU32(state), access.length,
|
||||
ConstantU32(state, shift));
|
||||
}
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
|
||||
access.object_pointer, 0);
|
||||
return access;
|
||||
}
|
||||
|
||||
|
||||
@@ -595,6 +595,13 @@ void DefineInputs(EmitterState& state) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationFlat);
|
||||
}
|
||||
}
|
||||
if (state.program.info.helper_invocation) {
|
||||
const auto variable = DefineInterfaceVariable(state, TypeBool(state), spv::StorageClassInput,
|
||||
"gl_HelperInvocation");
|
||||
state.helper_invocation_variable = variable;
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
|
||||
spv::BuiltInHelperInvocation);
|
||||
}
|
||||
}
|
||||
|
||||
void DefineOutputs(EmitterState& state) {
|
||||
@@ -691,13 +698,15 @@ void DefineOutputs(EmitterState& state) {
|
||||
break;
|
||||
case IR::StageOutputKind::Parameter:
|
||||
case IR::StageOutputKind::Mrt: {
|
||||
const bool uint_output =
|
||||
binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel &&
|
||||
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
|
||||
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
|
||||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
|
||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
auto type = TypeF32Vector(state, 4);
|
||||
if (binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel && binding.index < 8u) {
|
||||
// Packed UNorm stores its encoded bits.
|
||||
const auto bit = 1u << binding.index;
|
||||
type = (state.input_info.pixel->target_packed_unorm_mask & bit) != 0u
|
||||
? TypeU32Vector(state, 4)
|
||||
: MrtOutputType(state, binding.index);
|
||||
}
|
||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||
binding.debug_name.c_str());
|
||||
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
||||
|
||||
@@ -235,7 +235,7 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (ctx.half != 0 &&
|
||||
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
|
||||
op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
|
||||
op == Op::SubgroupUMaxU32 || op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
|
||||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
|
||||
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
|
||||
ctx.Define(inst, ctx.other_half->Result(inst));
|
||||
@@ -746,7 +746,7 @@ void EmitProgram(EmitterState& state) {
|
||||
state.lds_base_dwords = EmitBinaryU32(state, spv::OpIMul, index,
|
||||
ConstantU32(state, LdsDwordCount(state)));
|
||||
}
|
||||
EmitMemoryOffsets(state);
|
||||
EmitMemoryBindings(state);
|
||||
if (program.blocks.empty()) {
|
||||
EmitReturn(ctx);
|
||||
} else if (state.program.dispatcher_fallback) {
|
||||
|
||||
@@ -1710,21 +1710,25 @@ Graph Structurize(const Graph& graph) {
|
||||
return GotoStructurizer(graph).Run();
|
||||
}
|
||||
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code) {
|
||||
// Vector comparisons and carry results can write a scalar pair in wave64.
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size) {
|
||||
// A vector lane mask (comparison or carry) spans wave_size / 32 SGPRs; a lane read writes one.
|
||||
if (inst.family == Decoder::Family::VOP1 || inst.family == Decoder::Family::VOP2 ||
|
||||
inst.family == Decoder::Family::VOPC || inst.family == Decoder::Family::VOP3 ||
|
||||
inst.family == Decoder::Family::VOP3P) {
|
||||
const bool lane_read =
|
||||
inst.opcode == Opcode::V_READLANE_B32 || inst.opcode == Opcode::V_READFIRSTLANE_B32;
|
||||
const uint32_t size = lane_read ? 1u : wave_size / 32u;
|
||||
uint32_t destination = 0;
|
||||
for (const auto* operand: {&inst.dst, &inst.dst2})
|
||||
if (ScalarOperandCode(*operand, destination) && code >= destination && code <= destination + 1u)
|
||||
if (ScalarOperandCode(*operand, destination) && code >= destination && code < destination + size)
|
||||
return true;
|
||||
}
|
||||
return InstructionWritesScalarCode(inst, code);
|
||||
}
|
||||
|
||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||
uint32_t before, uint32_t code, std::vector<uint32_t>& crossed_calls) {
|
||||
uint32_t before, uint32_t code, uint32_t wave_size,
|
||||
std::vector<uint32_t>& crossed_calls) {
|
||||
EXIT_IF(before >= program.instructions.size());
|
||||
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
||||
return block.inst_begin <= before && before < block.inst_end;
|
||||
@@ -1743,11 +1747,11 @@ uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& grap
|
||||
for (uint32_t i = point.second; i > block->inst_begin;) {
|
||||
const auto& inst = program.instructions[--i];
|
||||
if (inst.opcode == Opcode::S_SWAPPC_B64) {
|
||||
EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code));
|
||||
EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code, wave_size));
|
||||
crossed_calls.push_back(i);
|
||||
continue;
|
||||
}
|
||||
if (MayWriteScalarRegister(inst, code)) { index = i; break; }
|
||||
if (MayWriteScalarRegister(inst, code, wave_size)) { index = i; break; }
|
||||
}
|
||||
if (index != UINT32_MAX || block->id == graph.entry_block) {
|
||||
EXIT_IF(definition && *definition != index);
|
||||
|
||||
@@ -113,9 +113,10 @@ struct Graph {
|
||||
};
|
||||
|
||||
Graph BuildGraph(const Decoder::Program& program);
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code);
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size);
|
||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||
uint32_t before, uint32_t code, std::vector<uint32_t>& crossed_calls);
|
||||
uint32_t before, uint32_t code, uint32_t wave_size,
|
||||
std::vector<uint32_t>& crossed_calls);
|
||||
// Returns structured control flow or failure diagnostics without changing the native graph.
|
||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||
Graph Structurize(const Graph& graph);
|
||||
|
||||
@@ -73,7 +73,7 @@ constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension d
|
||||
if ((flags & ImageSampleFlagBias) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagLod) != 0) {
|
||||
if ((flags & (ImageSampleFlagLod | ImageSampleFlagLodClamp)) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagDerivative) != 0) {
|
||||
|
||||
@@ -258,7 +258,9 @@ void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand) {
|
||||
case 126u: operand.kind = OperandKind::ExecLo; return;
|
||||
case 127u: operand.kind = OperandKind::ExecHi; return;
|
||||
case 235u: operand.kind = OperandKind::SharedBase; return;
|
||||
case 236u: operand.kind = OperandKind::SharedLimit; return;
|
||||
case 237u: operand.kind = OperandKind::PrivateBase; return;
|
||||
case 238u: operand.kind = OperandKind::PrivateLimit; return;
|
||||
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
|
||||
case 248u:
|
||||
operand.kind = OperandKind::FloatInlineConstant;
|
||||
@@ -459,7 +461,9 @@ std::string OperandToString(const Operand& operand) {
|
||||
case OperandKind::M0: text = "m0"; break;
|
||||
case OperandKind::PopsExitingWaveId: text = "pops_exiting_wave_id"; break;
|
||||
case OperandKind::SharedBase: text = "shared_base"; break;
|
||||
case OperandKind::SharedLimit: text = "shared_limit"; break;
|
||||
case OperandKind::PrivateBase: text = "private_base"; break;
|
||||
case OperandKind::PrivateLimit: text = "private_limit"; break;
|
||||
case OperandKind::Null: text = "null"; break;
|
||||
default: text = "unknown"; break;
|
||||
}
|
||||
|
||||
@@ -766,7 +766,9 @@ enum class OperandKind {
|
||||
M0,
|
||||
PopsExitingWaveId,
|
||||
SharedBase,
|
||||
SharedLimit,
|
||||
PrivateBase,
|
||||
PrivateLimit,
|
||||
Null,
|
||||
Vgpr,
|
||||
};
|
||||
|
||||
@@ -5,30 +5,20 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar,
|
||||
bool cmpx) {
|
||||
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar) {
|
||||
if (scalar) {
|
||||
ir.SetScc(value);
|
||||
return;
|
||||
}
|
||||
const auto masked = ir.LogicalAnd(ir.GetExec(), value);
|
||||
if (cmpx) {
|
||||
const auto mask = BallotMask(masked);
|
||||
ir.SetExec(masked);
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
WriteMask(inst.dst, masked);
|
||||
WriteVectorMask(inst.dst, value);
|
||||
}
|
||||
|
||||
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar,
|
||||
bool cmpx) {
|
||||
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar, cmpx);
|
||||
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar) {
|
||||
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar);
|
||||
}
|
||||
|
||||
void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
IR::Type type, bool scalar, bool cmpx, bool swap_operands) {
|
||||
IR::Type type, bool scalar, bool swap_operands) {
|
||||
// The equality compares share their IR opcode with the unsigned ones.
|
||||
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
||||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
||||
@@ -49,18 +39,18 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
|
||||
if (swap_operands) {
|
||||
std::swap(lhs, rhs);
|
||||
}
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), scalar, cmpx);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), scalar);
|
||||
}
|
||||
|
||||
void Translator::EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool signed_value, bool cmpx) {
|
||||
bool signed_value) {
|
||||
const auto lhs = ReadU16AsU32(inst.src0, signed_value);
|
||||
const auto rhs = ReadU16AsU32(inst.src1, signed_value);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), false, cmpx);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), false);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool half, bool cmpx) {
|
||||
bool half) {
|
||||
const auto type = IR::ArgTypeOf(opcode, 0);
|
||||
const auto lhs =
|
||||
half ? IR::Value(ReadF16AsF32(inst.src0)) : ReadOperand(inst.src0, type);
|
||||
@@ -68,10 +58,10 @@ void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpc
|
||||
half ? IR::Value(ReadF16AsF32(inst.src1)) : ReadOperand(inst.src1, type);
|
||||
const IR::FPCompareFlags flags{
|
||||
.flush_input_denorms = !half && type == IR::Type::F32 && flush_f32_inputs};
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs}, flags)), false, cmpx);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs}, flags)), false);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx,
|
||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered,
|
||||
bool is64) {
|
||||
const auto type = is64 ? IR::Type::F64 : IR::Type::F32;
|
||||
const auto is_nan_op = is64 ? IR::ValueOpcode::FPIsNan64 : IR::ValueOpcode::FPIsNan32;
|
||||
@@ -79,16 +69,16 @@ void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool
|
||||
const auto rhs = ReadOperand(inst.src1, type);
|
||||
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(is_nan_op, {lhs})),
|
||||
IR::U1(ir.Emit(is_nan_op, {rhs})));
|
||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, cmpx);
|
||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half) {
|
||||
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool half) {
|
||||
const auto value = half ? IR::Value(Read16LaneBits(inst.src0, false))
|
||||
: ReadOperand(inst.src0, IR::Type::F32);
|
||||
const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false))
|
||||
: ReadOperand(inst.src1, IR::Type::U32);
|
||||
const auto opcode = half ? IR::ValueOpcode::FPCmpClass16 : IR::ValueOpcode::FPCmpClass32;
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {value, mask})), false, cmpx);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {value, mask})), false);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -51,33 +51,33 @@ void Translator::S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin)
|
||||
|
||||
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
|
||||
bool negate_exec, bool negate_source, bool write_64) {
|
||||
if (!write_64) {
|
||||
// Read the encoded scalar word and preserve EXEC_HI, including in wave32.
|
||||
const auto old = ir.GetExecLo();
|
||||
const auto src = ReadU32(inst.src0);
|
||||
const auto combine = [&](IR::U32 old, IR::U32 src) {
|
||||
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
|
||||
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
|
||||
IR::U32 result;
|
||||
switch (operation) {
|
||||
case IR::ValueOpcode::LogicalAnd: result = ir.BitwiseAnd(lhs, rhs); break;
|
||||
case IR::ValueOpcode::LogicalOr: result = ir.BitwiseOr(lhs, rhs); break;
|
||||
default: EXIT("unsupported SAVEEXEC operation");
|
||||
}
|
||||
return operation == IR::ValueOpcode::LogicalAnd ? ir.BitwiseAnd(lhs, rhs)
|
||||
: ir.BitwiseOr(lhs, rhs);
|
||||
};
|
||||
if (!write_64) {
|
||||
// Read the encoded scalar word and preserve EXEC_HI, including in wave32.
|
||||
const auto old = ir.GetExecLo();
|
||||
const auto result = combine(old, ReadU32(inst.src0));
|
||||
WriteRawU32(inst.dst, old);
|
||||
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result);
|
||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||
return;
|
||||
}
|
||||
const auto old = ir.GetExec();
|
||||
const auto src = ReadMask(inst.src0);
|
||||
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
||||
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
||||
const auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
|
||||
WriteMask(inst.dst, old, true);
|
||||
const auto mask = BallotMask(result);
|
||||
ir.SetExec(result);
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
// EXEC words combine bitwise; the per-lane EXEC combines the per-lane masks, so helper lanes
|
||||
// stay enabled.
|
||||
const auto old = ir.GetExec();
|
||||
const auto src = ReadMask(inst.src0);
|
||||
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
||||
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
||||
const auto src_mask = ReadU32Pair(inst.src0);
|
||||
const std::array old_mask {ir.GetExecLo(), ir.GetExecHi()};
|
||||
const std::array mask {combine(old_mask[0], src_mask[0]), combine(old_mask[1], src_mask[1])};
|
||||
WriteMask(inst.dst, old, old_mask, true);
|
||||
WriteMask(ConditionOperand(Decoder::OperandKind::ExecLo), IR::U1(ir.Emit(operation, {lhs, rhs})),
|
||||
mask);
|
||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ void Translator::ADD_U32(const Decoder::Instruction& inst, bool vector, bool use
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (vector) {
|
||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), carry));
|
||||
WriteVectorMask(inst.dst2, carry);
|
||||
} else {
|
||||
ir.SetScc(carry);
|
||||
}
|
||||
@@ -108,7 +108,7 @@ void Translator::SUB_U32(const Decoder::Instruction& inst, bool vector, bool rev
|
||||
const auto borrow = ir.UGreaterThan(rhs, lhs);
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (vector) {
|
||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
||||
WriteVectorMask(inst.dst2, borrow);
|
||||
} else {
|
||||
ir.SetScc(borrow);
|
||||
}
|
||||
@@ -126,7 +126,7 @@ void Translator::SUBB_U32(const Decoder::Instruction& inst, bool vector, bool re
|
||||
const auto borrow = ir.LogicalOr(borrow0, borrow1);
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (vector) {
|
||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
||||
WriteVectorMask(inst.dst2, borrow);
|
||||
} else {
|
||||
ir.SetScc(borrow);
|
||||
}
|
||||
|
||||
@@ -135,6 +135,8 @@ void Translator::V_PACK_B32_F16(const Decoder::Instruction& inst) {
|
||||
}
|
||||
|
||||
IR::U32 Translator::PackU16Lanes(IR::U32 low, IR::U32 high) {
|
||||
// A zero high lane zero-extends; descriptor tracking bounds a mask, not a bitfield insert.
|
||||
if (high == IR::Value(0u)) return ir.BitwiseAnd(low, IR::U32(IR::Value(0xffffu)));
|
||||
return IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldInsert, {low, high, IR::Value(16u), IR::Value(16u)}));
|
||||
}
|
||||
|
||||
@@ -107,19 +107,7 @@ void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
||||
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
|
||||
const auto mask = WriteMask(inst.dst, invocation_result, true);
|
||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
|
||||
return;
|
||||
}
|
||||
|
||||
// Descriptor tracking reasons about each scalar word independently.
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
auto mask_valid = ReadMaskValid(inst.src0);
|
||||
if (inst.src_count > 1u) {
|
||||
mask_valid = ir.LogicalAnd(mask_valid, ReadMaskValid(inst.src1));
|
||||
}
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
std::array<IR::U32, 2> result;
|
||||
if (unary) {
|
||||
result = {ir.BitwiseNot(lhs[0]), ir.BitwiseNot(lhs[1])};
|
||||
@@ -132,11 +120,21 @@ void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
||||
result[component] = negate_result ? ir.BitwiseNot(IR::U32(value)) : IR::U32(value);
|
||||
}
|
||||
}
|
||||
WriteU32Pair(inst.dst, result);
|
||||
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
||||
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
||||
ir.SetThreadBitScalarReg(dst, invocation_result);
|
||||
ir.SetScalarMaskTag(dst, mask_valid);
|
||||
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
||||
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
|
||||
WriteMask(inst.dst, invocation_result, result, true);
|
||||
} else {
|
||||
// Descriptor tracking reasons about each scalar word independently.
|
||||
auto mask_valid = ReadMaskValid(inst.src0);
|
||||
if (inst.src_count > 1u) {
|
||||
mask_valid = ir.LogicalAnd(mask_valid, ReadMaskValid(inst.src1));
|
||||
}
|
||||
WriteU32Pair(inst.dst, result);
|
||||
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
||||
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
||||
ir.SetThreadBitScalarReg(dst, invocation_result);
|
||||
ir.SetScalarMaskTag(dst, mask_valid);
|
||||
}
|
||||
}
|
||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(result[0], result[1]), IR::U32(IR::Value(0u))));
|
||||
}
|
||||
@@ -292,7 +290,8 @@ void Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
|
||||
if (inst.dst2.kind != Decoder::OperandKind::Null &&
|
||||
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
||||
// The 32x32 product fits in 64 bits, so only the addition can carry.
|
||||
WriteMask(inst.dst2, IR::U1(ir.Emit(IR::ValueOpcode::ULessThan64, {result, product})));
|
||||
const auto carry = IR::U1(ir.Emit(IR::ValueOpcode::ULessThan64, {result, product}));
|
||||
WriteMask(inst.dst2, carry, VectorMask(carry));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +471,7 @@ void Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||
const auto bit =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {value, offset, IR::Value(1u)}));
|
||||
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
ir.SetScc(ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
}
|
||||
|
||||
void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||
@@ -483,7 +482,7 @@ void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||
ir.Select(ir.ULessThan(offset, IR::U32(IR::Value(32u))), value[0], value[1]);
|
||||
const auto bit =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
|
||||
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
ir.SetScc(ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
}
|
||||
|
||||
void Translator::V_ALIGN_B32(const Decoder::Instruction& inst, bool byte_offset) {
|
||||
|
||||
@@ -863,9 +863,10 @@ void Translator::DS_WRITE2(const Decoder::Instruction& inst) {
|
||||
|
||||
void Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
WriteOperand(inst.dst, ir.Emit(opcode,
|
||||
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc)));
|
||||
const auto exec = ExecMask();
|
||||
WriteOperand(inst.dst,
|
||||
ir.Emit(opcode, {ReadU32(MemorySourceAt(inst, 0)), exec[0], exec[1], ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc)));
|
||||
}
|
||||
|
||||
void Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
|
||||
@@ -901,10 +902,7 @@ void Translator::DS_ORDERED_COUNT(const Decoder::Instruction& inst) {
|
||||
const auto packer = ir.BitwiseAnd(m0, IR::U32(IR::Value(0xfu)));
|
||||
address = ir.IAdd(address, ir.ShiftLeftLogical(packer, IR::U32(IR::Value(2u))));
|
||||
}
|
||||
const auto exec = ir.GetExec();
|
||||
const auto ballot = ir.Emit(IR::ValueOpcode::Ballot, {exec});
|
||||
const auto low = ir.CompositeExtract(ballot, 0u);
|
||||
const auto high = ir.CompositeExtract(ballot, 1u);
|
||||
const auto [low, high] = ExecMask();
|
||||
const auto active = ir.INotEqual(ir.BitwiseOr(low, high), IR::U32(IR::Value(0u)));
|
||||
const auto first = ir.Select(
|
||||
ir.INotEqual(low, IR::U32(IR::Value(0u))),
|
||||
|
||||
@@ -73,43 +73,41 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
|
||||
case O::S_CMP_EQ_U32:
|
||||
case O::S_CMP_EQ_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_LG_U32:
|
||||
case O::S_CMP_LG_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_GT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true,
|
||||
false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_LT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_LE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_GT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true,
|
||||
false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_LT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, true);
|
||||
return;
|
||||
case O::S_CMP_LG_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, true);
|
||||
return;
|
||||
|
||||
case O::S_AND_B64:
|
||||
|
||||
@@ -129,6 +129,20 @@ std::array<IR::U32, 2> Translator::BallotMask(IR::U1 value) {
|
||||
program.wave_size == 64u ? ir.CompositeExtract(mask, 1) : IR::U32(IR::Value(0u))};
|
||||
}
|
||||
|
||||
std::array<IR::U32, 2> Translator::ExecMask() {
|
||||
return {ir.GetExecLo(), program.wave_size == 64u ? ir.GetExecHi() : IR::U32(IR::Value(0u))};
|
||||
}
|
||||
|
||||
// Pixel EXEC words exclude the helper invocations that the per-lane EXEC keeps enabled.
|
||||
std::array<IR::U32, 2> Translator::VectorMask(IR::U1 value) {
|
||||
auto mask = BallotMask(value);
|
||||
if (program.stage == ShaderType::Pixel) {
|
||||
const auto exec = ExecMask();
|
||||
mask = {ir.BitwiseAnd(mask[0], exec[0]), ir.BitwiseAnd(mask[1], exec[1])};
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::LiteralConstant:
|
||||
@@ -138,6 +152,8 @@ IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
||||
case Decoder::OperandKind::SharedBase:
|
||||
case Decoder::OperandKind::PrivateBase:
|
||||
case Decoder::OperandKind::PopsExitingWaveId: return IR::U32(IR::Value(0u));
|
||||
case Decoder::OperandKind::SharedLimit:
|
||||
case Decoder::OperandKind::PrivateLimit: return IR::U32(IR::Value(0xffffffffu));
|
||||
case Decoder::OperandKind::Sgpr:
|
||||
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg));
|
||||
case Decoder::OperandKind::Vgpr:
|
||||
@@ -388,21 +404,9 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::Scc: ir.SetScc(IR::U1(value)); return;
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi: {
|
||||
const auto mask = BallotMask(IR::U1(value));
|
||||
ir.SetExec(IR::U1(value));
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::ExecHi:
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccHi: {
|
||||
const auto mask = BallotMask(IR::U1(value));
|
||||
ir.SetVcc(IR::U1(value));
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::VccHi: EXIT("lane masks are written through WriteMask");
|
||||
default:
|
||||
WriteRawU32(operand, ir.Select(IR::U1(value), IR::U32(IR::Value(1u)),
|
||||
IR::U32(IR::Value(0u))));
|
||||
@@ -471,17 +475,17 @@ IR::U32 Translator::ReadU32(const Decoder::Operand& operand) {
|
||||
}
|
||||
|
||||
std::array<IR::U32, 2> Translator::ReadU32Pair(const Decoder::Operand& operand) {
|
||||
if (operand.kind == Decoder::OperandKind::PrivateBase ||
|
||||
operand.kind == Decoder::OperandKind::SharedBase) {
|
||||
return {IR::U32(IR::Value(0u)), IR::U32(IR::Value(
|
||||
operand.kind == Decoder::OperandKind::PrivateBase ? Decoder::PrivateApertureHigh
|
||||
: Decoder::SharedApertureHigh))};
|
||||
}
|
||||
if (operand.kind == Decoder::OperandKind::ExecLo) {
|
||||
return {ir.GetExecLo(), ir.GetExecHi()};
|
||||
}
|
||||
if (operand.kind == Decoder::OperandKind::VccLo) {
|
||||
return {ir.GetVccLo(), ir.GetVccHi()};
|
||||
// Each aperture limit is its base plus 0xffffffff.
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::SharedBase:
|
||||
case Decoder::OperandKind::SharedLimit:
|
||||
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::SharedApertureHigh))};
|
||||
case Decoder::OperandKind::PrivateBase:
|
||||
case Decoder::OperandKind::PrivateLimit:
|
||||
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::PrivateApertureHigh))};
|
||||
case Decoder::OperandKind::ExecLo: return {ir.GetExecLo(), ir.GetExecHi()};
|
||||
case Decoder::OperandKind::VccLo: return {ir.GetVccLo(), ir.GetVccHi()};
|
||||
default: break;
|
||||
}
|
||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
const auto bits =
|
||||
@@ -639,7 +643,7 @@ IR::U1 Translator::ReadMask(const Decoder::Operand& operand) {
|
||||
}
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi: return ir.GetExec();
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccLo: return ir.GetVcc();
|
||||
case Decoder::OperandKind::VccHi:
|
||||
return program.wave_size == 32u
|
||||
? ThreadBit({ReadRawU32(operand), IR::U32(IR::Value(0u))})
|
||||
@@ -677,9 +681,8 @@ IR::U1 Translator::ReadMaskValid(const Decoder::Operand& operand) {
|
||||
}
|
||||
}
|
||||
|
||||
std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||
bool write_64) {
|
||||
const auto mask = BallotMask(value);
|
||||
void Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||
const std::array<IR::U32, 2>& mask, bool write_64) {
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::Sgpr: {
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
||||
@@ -700,43 +703,43 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
|
||||
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
||||
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
||||
}
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi: {
|
||||
ir.SetExec(value);
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccHi: {
|
||||
if (!write_64 && program.wave_size == 32u) {
|
||||
if (operand.kind == Decoder::OperandKind::VccHi && !write_64 &&
|
||||
program.wave_size == 32u) {
|
||||
WriteRawU32(operand, mask[0]);
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
ir.SetVcc(value);
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return mask;
|
||||
if (write_64 || program.wave_size == 64u) {
|
||||
ir.SetVccHi(mask[1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::Scc: ir.SetScc(value); return mask;
|
||||
case Decoder::OperandKind::Scc: ir.SetScc(value); return;
|
||||
default:
|
||||
WriteRawU32(operand, mask[0]);
|
||||
WriteRawU32(OffsetOperand(operand, 1), mask[1]);
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 value) {
|
||||
if (operand.kind == Decoder::OperandKind::Scc) {
|
||||
WriteOperand(operand, value);
|
||||
return;
|
||||
}
|
||||
void Translator::WriteVectorMask(const Decoder::Operand& operand, IR::U1 value) {
|
||||
// VALU instructions only write lanes enabled by the incoming EXEC mask. This is
|
||||
// especially important for V_CMPX: replacing EXEC with the ungated comparison would
|
||||
// reactivate lanes disabled by an enclosing divergent region.
|
||||
WriteMask(operand, ir.LogicalAnd(ir.GetExec(), value));
|
||||
const auto active = ir.LogicalAnd(ir.GetExec(), value);
|
||||
WriteMask(operand, active, VectorMask(active));
|
||||
}
|
||||
|
||||
void Translator::AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||
@@ -1153,7 +1156,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
}
|
||||
entry_ir.SetExec(initial_exec);
|
||||
const auto initial_mask = entry_ir.Emit(IR::ValueOpcode::Ballot, {initial_exec});
|
||||
// Pixel EXEC words start with the live (non-helper) lanes; helpers keep the per-lane EXEC.
|
||||
const auto initial_mask = options.stage == ShaderType::Pixel
|
||||
? entry_ir.Emit(IR::ValueOpcode::LiveMask)
|
||||
: entry_ir.Emit(IR::ValueOpcode::Ballot, {initial_exec});
|
||||
const auto initial_lo = entry_ir.CompositeExtract(initial_mask, 0);
|
||||
const auto initial_hi = options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1)
|
||||
: IR::U32(IR::Value(0u));
|
||||
@@ -1238,12 +1244,12 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
||||
primitive_count, u32(8)),
|
||||
vertex_count)));
|
||||
// Instance IDs exclude the start instance, which shaders add from user data.
|
||||
if (mesh.fast_launch) {
|
||||
// Fast launch broadcasts the subgroup's base vertex and instance to every lane.
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5), entry_ir.IAdd(draw(1), group));
|
||||
entry_ir.SetVectorReg(
|
||||
static_cast<IR::VectorReg>(6),
|
||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(6),
|
||||
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||
} else {
|
||||
// GS adjacency addresses local ES records in LDS. Fans retain the draw's
|
||||
// center in every subgroup; strip winding follows the global primitive.
|
||||
@@ -1271,14 +1277,14 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
entry_ir.ShiftLeftLogical(second, u32(18))));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
|
||||
entry_ir.ShiftLeftLogical(third, u32(2)));
|
||||
const auto index_bytes = draw(3);
|
||||
const auto index_bytes = draw(2);
|
||||
const auto indexed = entry_ir.INotEqual(index_bytes, u32(0));
|
||||
const auto index_low = draw(4);
|
||||
const auto index_low = draw(3);
|
||||
const auto byte_offset = entry_ir.IAdd(entry_ir.BitwiseAnd(index_low, u32(3)),
|
||||
entry_ir.IMul(input_vertex, index_bytes));
|
||||
const auto index_resource = entry_ir.Emit(
|
||||
IR::ValueOpcode::GetAddressResource,
|
||||
{entry_ir.BitwiseAnd(index_low, u32(~3u)), draw(5)});
|
||||
{entry_ir.BitwiseAnd(index_low, u32(~3u)), draw(4)});
|
||||
const auto memory_index = static_cast<uint32_t>(result.memory_info.size());
|
||||
result.memory_info.push_back({.kind = IR::ResourceKind::Global});
|
||||
const auto packed_index = entry_ir.Emit(
|
||||
@@ -1292,9 +1298,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
entry_ir.IMul(index_bytes, u32(8))}));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||
entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex)));
|
||||
entry_ir.SetVectorReg(
|
||||
static_cast<IR::VectorReg>(8),
|
||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||
}
|
||||
} else if (options.stage == ShaderType::Local) {
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
|
||||
|
||||
@@ -27,6 +27,8 @@ private:
|
||||
Decoder::Operand ScalarDestinationOperand(const Decoder::Operand& operand, uint32_t offset);
|
||||
Decoder::Operand PlainOperand(const Decoder::Operand& operand);
|
||||
std::array<IR::U32, 2> BallotMask(IR::U1 value);
|
||||
std::array<IR::U32, 2> ExecMask();
|
||||
std::array<IR::U32, 2> VectorMask(IR::U1 value);
|
||||
IR::U32 ReadRawU32(const Decoder::Operand& operand);
|
||||
IR::U32 ReadScalarCode(uint32_t code);
|
||||
IR::U32 ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value);
|
||||
@@ -52,9 +54,9 @@ private:
|
||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMask(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
||||
std::array<IR::U32, 2> WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||
bool write_64 = false);
|
||||
void WriteCompareResult(const Decoder::Operand& operand, IR::U1 value);
|
||||
void WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||
const std::array<IR::U32, 2>& mask, bool write_64 = false);
|
||||
void WriteVectorMask(const Decoder::Operand& operand, IR::U1 value);
|
||||
|
||||
IR::MemoryFlags AddMemoryInfo(const IR::MemoryInfo& memory, uint32_t pc);
|
||||
IR::ExportFlags AddExportInfo(const Decoder::Instruction& inst);
|
||||
@@ -115,17 +117,15 @@ private:
|
||||
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
||||
IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high);
|
||||
IR::U32 Saturate16(IR::U32 value, bool signed_value);
|
||||
void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar, bool cmpx);
|
||||
void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar, bool cmpx);
|
||||
void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar);
|
||||
void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar);
|
||||
void EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
||||
bool scalar, bool cmpx, bool swap_operands = false);
|
||||
bool scalar, bool swap_operands = false);
|
||||
void EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool signed_value, bool cmpx);
|
||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
|
||||
bool cmpx);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx,
|
||||
bool is64 = false);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half = false);
|
||||
bool signed_value);
|
||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool is64 = false);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool half = false);
|
||||
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
|
||||
void V_CVT_F32_U32(const Decoder::Instruction& inst);
|
||||
void V_CVT_F32_I32(const Decoder::Instruction& inst);
|
||||
|
||||
@@ -25,444 +25,310 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
|
||||
|
||||
case O::V_CMP_F_I32:
|
||||
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false); return;
|
||||
case O::V_CMP_T_I32:
|
||||
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false); return;
|
||||
case O::V_CMP_EQ_U32:
|
||||
case O::V_CMP_EQ_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_U32:
|
||||
case O::V_CMPX_EQ_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_NE_U32:
|
||||
case O::V_CMP_NE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NE_U32:
|
||||
case O::V_CMPX_NE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_GT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
case O::V_CMPX_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
||||
true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_LE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
case O::V_CMPX_LE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_GT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
case O::V_CMPX_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
||||
true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
case O::V_CMPX_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false);
|
||||
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_CMP_F_U64:
|
||||
case O::V_CMPX_F_I64:
|
||||
case O::V_CMPX_F_U64: EmitCompareConstant(inst, false, false, true); return;
|
||||
case O::V_CMPX_F_U64: EmitCompareConstant(inst, false, false); return;
|
||||
case O::V_CMP_T_I64:
|
||||
case O::V_CMP_T_U64:
|
||||
case O::V_CMPX_T_I64:
|
||||
case O::V_CMPX_T_U64: EmitCompareConstant(inst, true, false, true); return;
|
||||
case O::V_CMPX_T_U64: EmitCompareConstant(inst, true, false); 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);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false);
|
||||
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);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false);
|
||||
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);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false);
|
||||
return;
|
||||
case O::V_CMP_NE_I64:
|
||||
case O::V_CMP_NE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NE_I64:
|
||||
case O::V_CMPX_NE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false);
|
||||
return;
|
||||
|
||||
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);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false);
|
||||
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);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false);
|
||||
return;
|
||||
// The IR has no signed 64-bit greater-than: a > b is b < a, and a >= b is b <= a.
|
||||
case O::V_CMP_GT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false,
|
||||
true);
|
||||
return;
|
||||
case O::V_CMPX_GT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true,
|
||||
true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_GE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false,
|
||||
true);
|
||||
return;
|
||||
case O::V_CMPX_GE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true,
|
||||
true);
|
||||
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;
|
||||
case O::V_CMPX_LE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false);
|
||||
return;
|
||||
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);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false);
|
||||
return;
|
||||
|
||||
case O::V_CMP_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, false);
|
||||
case O::V_CMPX_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_EQ_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_NE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false);
|
||||
return;
|
||||
case O::V_CMP_GE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true);
|
||||
return;
|
||||
case O::V_CMPX_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false);
|
||||
return;
|
||||
case O::V_CMP_LE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_GT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true);
|
||||
return;
|
||||
case O::V_CMP_GE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true);
|
||||
return;
|
||||
case O::V_CMP_LE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true);
|
||||
return;
|
||||
|
||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false); return;
|
||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false); return;
|
||||
case O::V_CMP_EQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, false);
|
||||
case O::V_CMPX_EQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_EQ_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual64, false, false);
|
||||
case O::V_CMPX_EQ_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_LE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, true);
|
||||
return;
|
||||
case O::V_CMPX_GE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual64, false, true);
|
||||
return;
|
||||
case O::V_CMP_F_F64: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_TRU_F64: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMPX_F_F64: EmitCompareConstant(inst, false, false, true); return;
|
||||
case O::V_CMPX_TRU_F64: EmitCompareConstant(inst, true, false, true); return;
|
||||
case O::V_CMP_LT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan64, false, true);
|
||||
return;
|
||||
case O::V_CMPX_EQ_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual64, false, true);
|
||||
return;
|
||||
case O::V_CMP_GT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LG_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LG_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_GE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual64, false, false);
|
||||
case O::V_CMPX_GE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_F_F64:
|
||||
case O::V_CMPX_F_F64: EmitCompareConstant(inst, false, false); return;
|
||||
case O::V_CMP_TRU_F64:
|
||||
case O::V_CMPX_TRU_F64: EmitCompareConstant(inst, true, false); return;
|
||||
case O::V_CMP_LT_F64:
|
||||
case O::V_CMPX_LT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan64, false);
|
||||
return;
|
||||
case O::V_CMP_GT_F64:
|
||||
case O::V_CMPX_GT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan64, false);
|
||||
return;
|
||||
case O::V_CMP_LG_F64:
|
||||
case O::V_CMPX_LG_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_NLG_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLG_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_NEQ_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_NLE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false);
|
||||
return;
|
||||
case O::V_CMP_NLT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_NGE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false);
|
||||
return;
|
||||
case O::V_CMP_NGT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, true);
|
||||
return;
|
||||
case O::V_CMP_O_F64: EmitFloatOrderedCompare(inst, true, false, true); return;
|
||||
case O::V_CMP_U_F64: EmitFloatOrderedCompare(inst, false, false, true); return;
|
||||
case O::V_CMPX_O_F64: EmitFloatOrderedCompare(inst, true, true, true); return;
|
||||
case O::V_CMPX_U_F64: EmitFloatOrderedCompare(inst, false, true, true); return;
|
||||
case O::V_CMPX_EQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_O_F64:
|
||||
case O::V_CMPX_O_F64: EmitFloatOrderedCompare(inst, true, true); return;
|
||||
case O::V_CMP_U_F64:
|
||||
case O::V_CMPX_U_F64: EmitFloatOrderedCompare(inst, false, true); return;
|
||||
case O::V_CMP_LG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_GT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false);
|
||||
return;
|
||||
case O::V_CMP_GE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false);
|
||||
return;
|
||||
case O::V_CMP_LE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NLG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NEQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NLE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false);
|
||||
return;
|
||||
case O::V_CMP_NLT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NGE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false);
|
||||
return;
|
||||
case O::V_CMP_NGT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_EQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_LG_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true, false);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_GT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true);
|
||||
return;
|
||||
case O::V_CMP_GE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_LT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true);
|
||||
return;
|
||||
case O::V_CMP_LE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_NGE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true, false);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true);
|
||||
return;
|
||||
case O::V_CMP_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMPX_NLE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true);
|
||||
return;
|
||||
case O::V_CMP_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true, false); return;
|
||||
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false, false); return;
|
||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
|
||||
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_CMP_O_F32:
|
||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true); return;
|
||||
case O::V_CMP_U_F32:
|
||||
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false); return;
|
||||
case O::V_CMP_CLASS_F32:
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst); return;
|
||||
case O::V_CMP_CLASS_F16:
|
||||
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true); return;
|
||||
|
||||
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return;
|
||||
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return;
|
||||
|
||||
@@ -155,7 +155,7 @@ KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
|
||||
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
|
||||
KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
||||
KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
|
||||
A, B, C, D, E, F, G, H, I, J)
|
||||
A, B, C, D, E, F, G, H, I, J, K)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d)
|
||||
|
||||
@@ -365,7 +365,7 @@ static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 58u);
|
||||
|
||||
struct PushData {
|
||||
static constexpr uint32_t DwordCount = 32;
|
||||
static constexpr uint32_t MeshDrawDwordCount = 6;
|
||||
static constexpr uint32_t MeshDrawDwordCount = 5;
|
||||
static constexpr uint32_t NoStart = UINT32_MAX;
|
||||
std::array<uint32_t, DwordCount> dwords {};
|
||||
|
||||
@@ -495,10 +495,16 @@ struct Bindings {
|
||||
uint32_t dispatch_thread_dword = PushData::NoStart;
|
||||
uint32_t memory_offset_dword = 0;
|
||||
|
||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||
// Packed 8-bit binding offsets, then each binding's bound range in bytes: OpArrayLength
|
||||
// reads 0 for ranges of 2 GiB or more on NVIDIA.
|
||||
[[nodiscard]] uint32_t MemorySizeDword() const {
|
||||
return memory_offset_dword +
|
||||
(descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)] + 3u) / 4u;
|
||||
}
|
||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||
return MemorySizeDword() +
|
||||
descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)];
|
||||
}
|
||||
[[nodiscard]] bool UsesPushData() const {
|
||||
return push_data_start_dword != PushData::NoStart;
|
||||
}
|
||||
@@ -541,6 +547,7 @@ struct ShaderInfo {
|
||||
bool subgroup_shuffle = false;
|
||||
bool subgroup_arithmetic = false;
|
||||
bool subgroup_local_invocation_id = false;
|
||||
bool helper_invocation = false;
|
||||
bool compute_derivatives = false;
|
||||
bool image_gather_extended = false;
|
||||
bool function_lds = false;
|
||||
|
||||
@@ -219,9 +219,11 @@ VALUE_OPCODE(FPFract64, F64, F64)
|
||||
|
||||
VALUE_OPCODE(LaneId, U32)
|
||||
VALUE_OPCODE(Ballot, U32x4, U1)
|
||||
VALUE_OPCODE(LiveMask, U32x4)
|
||||
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
||||
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
||||
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
|
||||
VALUE_OPCODE(SubgroupUMaxU32, U32, U32)
|
||||
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
||||
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
|
||||
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
|
||||
@@ -309,8 +311,8 @@ VALUE_OPCODE(SharedAtomicOr32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicOr64, Void, U32, U64, U1)
|
||||
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicMaskedOr32, Void, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(DataAppend, U32, U32, U1)
|
||||
VALUE_OPCODE(DataConsume, U32, U32, U1)
|
||||
VALUE_OPCODE(DataAppend, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(DataConsume, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
||||
|
||||
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
||||
|
||||
@@ -167,6 +167,16 @@ bool ReplaceBinaryIdentity(Inst& inst, Type type, uint64_t identity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Whether one operand is the other's negation.
|
||||
bool IsComplementPair(const Inst& inst, ValueOpcode negation) {
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||
const auto* negate = Arg(inst, arg).TryInstruction();
|
||||
if (negate != nullptr && negate->GetOpcode() == negation &&
|
||||
Arg(*negate, 0) == Arg(inst, arg ^ 1u)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FoldSelect(Inst& inst) {
|
||||
const auto condition = Arg(inst, 0);
|
||||
const auto true_value = Arg(inst, 1);
|
||||
@@ -626,8 +636,9 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
case ValueOpcode::BitwiseXor32:
|
||||
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
|
||||
return opcode == ValueOpcode::BitwiseOr32 ? a | b : a ^ b;
|
||||
})) {
|
||||
ReplaceBinaryIdentity(inst, Type::U32, 0u);
|
||||
}) && !ReplaceBinaryIdentity(inst, Type::U32, 0u) &&
|
||||
IsComplementPair(inst, ValueOpcode::BitwiseNot32)) {
|
||||
Replace(inst, Value(UINT32_MAX));
|
||||
}
|
||||
return;
|
||||
case ValueOpcode::BitwiseNot32: {
|
||||
@@ -802,6 +813,8 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
Replace(inst, lhs.U1() ? lhs : rhs);
|
||||
} else if (IsImmediate(rhs, Type::U1)) {
|
||||
Replace(inst, rhs.U1() ? rhs : lhs);
|
||||
} else if (IsComplementPair(inst, ValueOpcode::LogicalNot)) {
|
||||
Replace(inst, Value(true));
|
||||
} else {
|
||||
const auto* left = lhs.TryInstruction();
|
||||
const auto* right = rhs.TryInstruction();
|
||||
@@ -892,8 +905,10 @@ private:
|
||||
if (inst.GetOpcode() != ValueOpcode::CompositeExtractU32x4 ||
|
||||
!Immediate(Arg(inst, 1), 0u)) return {};
|
||||
const auto* source = Arg(inst, 0).TryInstruction();
|
||||
return source != nullptr && source->GetOpcode() == ValueOpcode::Ballot
|
||||
? Arg(*source, 0) : Value {};
|
||||
if (source == nullptr) return {};
|
||||
// Helper lanes keep the per-lane EXEC, so the live mask projects to true.
|
||||
if (source->GetOpcode() == ValueOpcode::LiveMask) return Value(true);
|
||||
return source->GetOpcode() == ValueOpcode::Ballot ? Arg(*source, 0) : Value {};
|
||||
}
|
||||
|
||||
bool CanProject(Value value) {
|
||||
|
||||
@@ -99,89 +99,66 @@ Value GetRealValue(PhiMap& phi_map, Value source, uint32_t lane, uint32_t wave_s
|
||||
return Value(&phi);
|
||||
}
|
||||
|
||||
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction.
|
||||
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction. Wave64 first joins
|
||||
// the two rows of each half; a wave32 half is one row.
|
||||
// Fragment subgroups may have no physical invocation at either endpoint.
|
||||
class UMinReductionMatcher {
|
||||
class WaveReductionMatcher {
|
||||
public:
|
||||
explicit UMinReductionMatcher(Block* block): block(block) {}
|
||||
WaveReductionMatcher(const Block* block, ValueOpcode operation)
|
||||
: block(block), operation(operation) {}
|
||||
|
||||
Value Match(const Inst& minimum) {
|
||||
const auto* low = Get(minimum.Arg(0), ValueOpcode::ReadLane);
|
||||
const auto* high = Get(minimum.Arg(1), ValueOpcode::ReadLane);
|
||||
Value Match(const Inst& reduction, uint32_t wave_size) const {
|
||||
const auto* low = Get(reduction.Arg(0), ValueOpcode::ReadLane);
|
||||
const auto* high = Get(reduction.Arg(1), ValueOpcode::ReadLane);
|
||||
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
|
||||
return {};
|
||||
}
|
||||
const auto lane = [](Value value, uint32_t expected) {
|
||||
const auto lane = [&](Value value, uint32_t expected) {
|
||||
value = value.Resolve();
|
||||
return value.IsImmediate() && value.GetType() == Type::U32 &&
|
||||
value.U32() % 64u == expected;
|
||||
value.U32() % wave_size == expected;
|
||||
};
|
||||
if (!((lane(low->Arg(1), 31) && lane(high->Arg(1), 63)) ||
|
||||
(lane(low->Arg(1), 63) && lane(high->Arg(1), 31)))) {
|
||||
const auto half = wave_size / 2u;
|
||||
if (!((lane(low->Arg(1), half - 1u) && lane(high->Arg(1), wave_size - 1u)) ||
|
||||
(lane(low->Arg(1), wave_size - 1u) && lane(high->Arg(1), half - 1u)))) {
|
||||
return {};
|
||||
}
|
||||
const auto* joined = Get(Unwrap(low->Arg(0)), ValueOpcode::UMin32);
|
||||
if (wave_size == 32u) return MatchScan(low->Arg(0));
|
||||
const auto* joined = Get(low->Arg(0), operation);
|
||||
if (joined == nullptr) return {};
|
||||
for (size_t index = 0; index < 2; index++) {
|
||||
auto attempt = *this;
|
||||
const auto* cross = attempt.Get(attempt.Unwrap(joined->Arg(index)), ValueOpcode::Permlane16U32);
|
||||
const auto* cross = Get(joined->Arg(index), ValueOpcode::Permlane16U32);
|
||||
if (cross == nullptr) continue;
|
||||
const auto flags = cross->Flags<PermlaneFlags>();
|
||||
auto prefix = attempt.Unwrap(joined->Arg(1 - index));
|
||||
const auto flags = cross->Flags<PermlaneFlags>();
|
||||
const auto prefix = joined->Arg(1 - index).Resolve();
|
||||
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
|
||||
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
|
||||
cross->Arg(2).Resolve() != Value(UINT32_MAX) ||
|
||||
!attempt.FullExec(cross->Arg(3)) || attempt.Unwrap(cross->Arg(0)) != prefix) {
|
||||
cross->Arg(2).Resolve() != Value(UINT32_MAX) || !FullExec(cross->Arg(3)) ||
|
||||
cross->Arg(0).Resolve() != prefix) {
|
||||
continue;
|
||||
}
|
||||
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
||||
prefix = attempt.MatchRow(prefix, shift);
|
||||
}
|
||||
prefix = attempt.Unwrap(prefix);
|
||||
if (prefix.IsEmpty() || attempt.saved_exec.IsEmpty()) return prefix;
|
||||
const auto* seed = attempt.Get(prefix, ValueOpcode::SelectU32);
|
||||
if (seed != nullptr && seed->Arg(2).Resolve() == Value(UINT32_MAX)) {
|
||||
const auto predicate = seed->Arg(0).Resolve();
|
||||
const auto* subset = attempt.Get(predicate, ValueOpcode::LogicalAnd);
|
||||
if (predicate == attempt.saved_exec ||
|
||||
(subset != nullptr && (subset->Arg(0).Resolve() == attempt.saved_exec ||
|
||||
subset->Arg(1).Resolve() == attempt.saved_exec))) {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
if (const auto seed = MatchScan(prefix); !seed.IsEmpty()) return seed;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
// The row scan's input.
|
||||
Value MatchScan(Value prefix) const {
|
||||
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
||||
prefix = MatchRow(prefix, shift);
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
Inst* Get(Value value, ValueOpcode opcode) const {
|
||||
auto* inst = value.Resolve().TryInstruction();
|
||||
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
|
||||
? inst : nullptr;
|
||||
}
|
||||
|
||||
bool FullExec(Value predicate) {
|
||||
predicate = predicate.Resolve();
|
||||
if (predicate == Value(true)) return true;
|
||||
const auto* either = Get(predicate, ValueOpcode::LogicalOr);
|
||||
if (either == nullptr) return false;
|
||||
for (size_t index = 0; index < 2; index++) {
|
||||
const auto* negate = Get(either->Arg(index), ValueOpcode::LogicalNot);
|
||||
if (negate == nullptr || negate->Arg(0).Resolve() != either->Arg(1 - index).Resolve()) continue;
|
||||
const auto original = negate->Arg(0).Resolve();
|
||||
if (saved_exec.IsEmpty()) saved_exec = original;
|
||||
return saved_exec == original;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Value Unwrap(Value value) {
|
||||
while (const auto* select = Get(value, ValueOpcode::SelectU32)) {
|
||||
if (!FullExec(select->Arg(0))) break;
|
||||
value = select->Arg(1);
|
||||
}
|
||||
return value.Resolve();
|
||||
}
|
||||
// Constant propagation folds the x | ~x EXEC of s_orn2_saveexec to true.
|
||||
static bool FullExec(Value predicate) { return predicate.Resolve() == Value(true); }
|
||||
|
||||
static bool IsRowShift(const Inst& inst, uint32_t shift) {
|
||||
const auto flags = inst.Flags<DppMoveFlags>();
|
||||
@@ -189,22 +166,24 @@ private:
|
||||
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
|
||||
}
|
||||
|
||||
Value MatchRow(Value value, uint32_t shift) {
|
||||
const auto* update = Get(Unwrap(value), ValueOpcode::DppUpdateU32);
|
||||
Value MatchRow(Value value, uint32_t shift) const {
|
||||
const auto* update = Get(value, ValueOpcode::DppUpdateU32);
|
||||
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
|
||||
const auto* minimum = Get(update->Arg(0), ValueOpcode::UMin32);
|
||||
if (minimum == nullptr) return {};
|
||||
const auto* combined = Get(update->Arg(0), operation);
|
||||
if (combined == nullptr) return {};
|
||||
for (size_t index = 0; index < 2; index++) {
|
||||
const auto* move = Get(minimum->Arg(index), ValueOpcode::DppMoveU32);
|
||||
const auto* move = Get(combined->Arg(index), ValueOpcode::DppMoveU32);
|
||||
if (move == nullptr || !IsRowShift(*move, shift) || !FullExec(move->Arg(1))) continue;
|
||||
const auto previous = Unwrap(minimum->Arg(1 - index));
|
||||
if (Unwrap(move->Arg(0)) == previous && Unwrap(update->Arg(1)) == previous) return previous;
|
||||
const auto previous = combined->Arg(1 - index).Resolve();
|
||||
if (move->Arg(0).Resolve() == previous && update->Arg(1).Resolve() == previous) {
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Block* block;
|
||||
Value saved_exec;
|
||||
const Block* block;
|
||||
ValueOpcode operation;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -217,18 +196,22 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
|
||||
|
||||
for (auto* block: program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
if (wave_size == 64u && inst.GetOpcode() == ValueOpcode::UMin32) {
|
||||
const auto seed = UMinReductionMatcher(block).Match(inst);
|
||||
const auto opcode = inst.GetOpcode();
|
||||
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
|
||||
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
|
||||
if (!seed.IsEmpty()) {
|
||||
const auto position = std::find_if(block->begin(), block->end(),
|
||||
[&](const Inst& candidate) { return &candidate == &inst; });
|
||||
auto& reduction = *block->PrependNewInst(position, ValueOpcode::SubgroupUMinU32, {seed});
|
||||
const auto reduce = opcode == ValueOpcode::UMin32
|
||||
? ValueOpcode::SubgroupUMinU32
|
||||
: ValueOpcode::SubgroupUMaxU32;
|
||||
auto& reduction = *block->PrependNewInst(position, reduce, {seed});
|
||||
inst.ReplaceUsesWith(Value(&reduction));
|
||||
stats.rewritten_reductions++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inst.GetOpcode() != ValueOpcode::ReadLane) {
|
||||
if (opcode != ValueOpcode::ReadLane) {
|
||||
continue;
|
||||
}
|
||||
const auto selector = inst.Arg(1).Resolve();
|
||||
|
||||
@@ -366,7 +366,6 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
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;
|
||||
}
|
||||
@@ -517,7 +516,9 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||
break;
|
||||
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
|
||||
case ValueOpcode::SubgroupUMinU32: info.subgroup_arithmetic = true; break;
|
||||
case ValueOpcode::LiveMask: info.subgroup_ballot = info.helper_invocation = true; break;
|
||||
case ValueOpcode::SubgroupUMinU32:
|
||||
case ValueOpcode::SubgroupUMaxU32: info.subgroup_arithmetic = true; break;
|
||||
case ValueOpcode::DppMoveU32:
|
||||
case ValueOpcode::ReadFirstLane:
|
||||
case ValueOpcode::ReadLane: {
|
||||
@@ -576,7 +577,7 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
|
||||
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_vote = info.subgroup_barrier =
|
||||
info.subgroup_shuffle = info.subgroup_arithmetic =
|
||||
info.subgroup_shuffle = info.subgroup_arithmetic = info.helper_invocation =
|
||||
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;
|
||||
|
||||
@@ -490,7 +490,9 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(static_cast<uint32_t>(info.object_primitive_id));
|
||||
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));
|
||||
key.push_back(info.target_packed_unorm_mask | (uint32_t {info.target_round_mode} << 8u));
|
||||
key.push_back(info.target_packed_unorm_mask | (uint32_t {info.target_round_mode} << 8u) |
|
||||
(uint32_t {info.target_uint_mask} << 16u) |
|
||||
(uint32_t {info.target_sint_mask} << 24u));
|
||||
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) {
|
||||
|
||||
@@ -181,6 +181,9 @@ struct ShaderPixelInputInfo {
|
||||
uint32_t target_shader_mask = UINT32_MAX;
|
||||
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
||||
uint8_t target_packed_unorm_mask = 0;
|
||||
// Exports to UINT and SINT targets, which take integer outputs of the same signedness.
|
||||
uint8_t target_uint_mask = 0;
|
||||
uint8_t target_sint_mask = 0;
|
||||
// CB round mode per converted export: zero adds half an LSB, one truncates.
|
||||
uint8_t target_round_mode = 0;
|
||||
uint32_t scratch_size_dwords = 0;
|
||||
|
||||
@@ -96,6 +96,13 @@ int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AcmBatchJobPriority(AcmBatchInfo* batch_info, uint32_t priority) {
|
||||
PRINT_NAME();
|
||||
(void)batch_info;
|
||||
(void)priority;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
||||
uint32_t count, const void* const ir[],
|
||||
const float* gain, void* const out[]) {
|
||||
|
||||
@@ -24,6 +24,7 @@ int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info
|
||||
AcmBatchError* batch_error, AcmBatchId* batch);
|
||||
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
|
||||
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
|
||||
int KYTY_SYSV_ABI AcmBatchJobPriority(AcmBatchInfo* batch_info, uint32_t priority);
|
||||
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
||||
uint32_t count, const void* const ir[],
|
||||
const float* gain, void* const out[]);
|
||||
|
||||
@@ -658,6 +658,8 @@ constexpr int SAVE_DATA_ERROR_BUSY_FOR_SAVING = -2137063402
|
||||
constexpr int SAVE_DATA_ERROR_LIMITATION_OVER = -2137063401; /* 0x809F0017 */
|
||||
constexpr int SAVE_DATA_ERROR_EVENT_BUSY = -2137063400; /* 0x809F0018 */
|
||||
constexpr int SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = -2137063399; /* 0x809F0019 */
|
||||
constexpr int SAVE_DATA_ERROR_NO_NEED_CONVERT = -2137063394; /* 0x809F001E */
|
||||
constexpr int SAVE_DATA_ERROR_BLOCKS_FEWER_THAN_SRC = -2137063392; /* 0x809F0020 */
|
||||
|
||||
} // namespace SaveData
|
||||
|
||||
|
||||
@@ -579,6 +579,7 @@ LIB_DEFINE(InitAudio_1_Acm) {
|
||||
LIB_FUNC("8fe55ktlNVo", Acm::AcmBatchStartBuffers);
|
||||
LIB_FUNC("RLN3gRlXJLE", Acm::AcmBatchWait);
|
||||
LIB_FUNC("r7z5YQFZo+U", Acm::AcmBatchJobNotification);
|
||||
LIB_FUNC("GTfJ64qFCPw", Acm::AcmBatchJobPriority);
|
||||
LIB_FUNC("u70oWo92SYQ", Acm::AcmConvReverbSharedInput);
|
||||
LIB_FUNC("9nLbWmRDpa8", Acm::AcmConvReverbSharedIr);
|
||||
LIB_FUNC("KovqaFbmtsM", Acm::AcmFft);
|
||||
|
||||
@@ -231,6 +231,15 @@ struct SaveDataDelete {
|
||||
int32_t pad2;
|
||||
};
|
||||
|
||||
struct SaveDataConvert {
|
||||
int32_t user_id;
|
||||
int32_t pad;
|
||||
const SceSaveDataDirName* src_dir_name;
|
||||
const SceSaveDataDirName* dst_dir_name;
|
||||
uint64_t dst_blocks;
|
||||
uint8_t reserved[24];
|
||||
};
|
||||
|
||||
struct SaveDataEvent {
|
||||
uint32_t type;
|
||||
int32_t error_code;
|
||||
@@ -309,14 +318,20 @@ static std::filesystem::path save_user_directory(std::string_view title_id, int3
|
||||
return directory / title_id;
|
||||
}
|
||||
|
||||
static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name,
|
||||
int32_t user_id) {
|
||||
static bool is_memory_dir_name(std::string_view dir_name) {
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
if (dir_name == memory_dir_name(slot)) {
|
||||
return std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name /
|
||||
std::to_string(user_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name,
|
||||
int32_t user_id) {
|
||||
if (is_memory_dir_name(dir_name)) {
|
||||
return std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name / std::to_string(user_id);
|
||||
}
|
||||
return save_user_directory(title_id, user_id) / dir_name;
|
||||
}
|
||||
|
||||
@@ -1150,13 +1165,9 @@ int KYTY_SYSV_ABI SaveDataSyncSaveDataMemory(const SaveDataMemorySync* sync_para
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI SaveDataGetEventResult(const void* event_param, SaveDataEvent* event) {
|
||||
int KYTY_SYSV_ABI SaveDataGetEventResult(const void* /*event_param*/, SaveDataEvent* event) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t event_param = 0x%016" PRIx64 "\n"
|
||||
"\t event = 0x%016" PRIx64 "\n",
|
||||
reinterpret_cast<uint64_t>(event_param), reinterpret_cast<uint64_t>(event));
|
||||
|
||||
if (event == nullptr) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
@@ -1180,6 +1191,56 @@ int KYTY_SYSV_ABI SaveDataGetEventResult(const void* event_param, SaveDataEvent*
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI SaveDataConvert(const SaveDataConvert* convert) {
|
||||
PRINT_NAME();
|
||||
|
||||
const auto valid_name = [](const SceSaveDataDirName* name) {
|
||||
return valid_path_component(name->data) && !is_memory_dir_name(name->data);
|
||||
};
|
||||
if (convert == nullptr || convert->src_dir_name == nullptr ||
|
||||
!valid_name(convert->src_dir_name) ||
|
||||
(convert->dst_dir_name != nullptr && !valid_name(convert->dst_dir_name)) ||
|
||||
convert->dst_blocks < SAVE_DATA_BLOCKS_MIN || convert->dst_blocks > SAVE_DATA_BLOCKS_MAX) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (convert->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t user_id = %" PRId32 "\n"
|
||||
"\t src_dir_name = %s\n"
|
||||
"\t dst_dir_name = %s\n"
|
||||
"\t dst_blocks = %" PRIu64 "\n",
|
||||
convert->user_id, convert->src_dir_name->data,
|
||||
convert->dst_dir_name != nullptr ? convert->dst_dir_name->data : "<none>",
|
||||
convert->dst_blocks);
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const auto source =
|
||||
save_directory(get_title_id(), convert->src_dir_name->data, convert->user_id);
|
||||
if (!Common::File::IsDirectoryExisting(source)) {
|
||||
return SAVE_DATA_ERROR_NOT_FOUND;
|
||||
}
|
||||
if (g_mount_slots.FindAvailable(source) == SaveDataMountSlots::BUSY) {
|
||||
return SAVE_DATA_ERROR_BUSY;
|
||||
}
|
||||
if (convert->dst_dir_name != nullptr &&
|
||||
Common::File::IsDirectoryExisting(
|
||||
save_directory(get_title_id(), convert->dst_dir_name->data, convert->user_id))) {
|
||||
return SAVE_DATA_ERROR_EXISTS;
|
||||
}
|
||||
uint64_t blocks = 0;
|
||||
if (read_save_blocks(source, &blocks) == OK) {
|
||||
if (convert->dst_blocks == blocks) {
|
||||
return SAVE_DATA_ERROR_NO_NEED_CONVERT;
|
||||
}
|
||||
if (convert->dst_blocks < blocks) {
|
||||
return SAVE_DATA_ERROR_BLOCKS_FEWER_THAN_SRC;
|
||||
}
|
||||
}
|
||||
EXIT("save data conversion of %s is not implemented\n", convert->src_dir_name->data);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI SaveDataBackup(const SaveDataBackup* backup) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -1313,6 +1374,7 @@ LIB_DEFINE(InitSaveDataNative_1) {
|
||||
LIB_FUNC("cduy9v4YmT4", ::Libs::SaveData::SaveDataSetSaveDataMemory2);
|
||||
LIB_FUNC("wiT9jeC7xPw", ::Libs::SaveData::SaveDataSyncSaveDataMemory);
|
||||
LIB_FUNC("j8xKtiFj0SY", ::Libs::SaveData::SaveDataGetEventResult);
|
||||
LIB_FUNC("2mfSRGdshtk", ::Libs::SaveData::SaveDataConvert);
|
||||
LIB_FUNC("WAzWTZm1H+I", ::Libs::SaveData::SaveDataTransferringMount);
|
||||
LIB_FUNC("RjMlsR8EXrw", ::Libs::SaveData::SaveDataTransferringMount);
|
||||
LIB_FUNC("z1JA8-iJt3k", ::Libs::SaveData::SaveDataBackup);
|
||||
@@ -1343,6 +1405,7 @@ LIB_DEFINE(InitSaveData_1) {
|
||||
LIB_FUNC("cduy9v4YmT4", SaveData::SaveDataSetSaveDataMemory2);
|
||||
LIB_FUNC("wiT9jeC7xPw", SaveData::SaveDataSyncSaveDataMemory);
|
||||
LIB_FUNC("j8xKtiFj0SY", SaveData::SaveDataGetEventResult);
|
||||
LIB_FUNC("2mfSRGdshtk", SaveData::SaveDataConvert);
|
||||
LIB_FUNC("WAzWTZm1H+I", SaveData::SaveDataTransferringMount);
|
||||
LIB_FUNC("RjMlsR8EXrw", SaveData::SaveDataTransferringMount);
|
||||
LIB_FUNC("z1JA8-iJt3k", SaveData::SaveDataBackup);
|
||||
|
||||
@@ -396,6 +396,38 @@ void TestClassicSavePaths() {
|
||||
CHECK(Search(1) == names);
|
||||
}
|
||||
|
||||
void TestConvert() {
|
||||
Reset("CONVERT");
|
||||
const auto source = DirName("save");
|
||||
const auto memory = DirName("sce_sdmemory");
|
||||
struct SaveDataConvert convert {};
|
||||
convert.user_id = 1;
|
||||
convert.src_dir_name = &memory;
|
||||
convert.dst_blocks = 96;
|
||||
CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_PARAMETER);
|
||||
// A missing source fails at once.
|
||||
convert.src_dir_name = &source;
|
||||
CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_NOT_FOUND);
|
||||
struct SaveDataMount3 mount {};
|
||||
mount.user_id = 1;
|
||||
mount.dir_name = &source;
|
||||
mount.mount_mode = 4;
|
||||
mount.blocks = 96;
|
||||
SaveDataMountResult mounted {};
|
||||
CHECK(SaveDataMount3(&mount, &mounted) == OK);
|
||||
CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_BUSY);
|
||||
CHECK(SaveDataUmount2(0, &mounted.mount_point) == OK);
|
||||
CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_NO_NEED_CONVERT);
|
||||
convert.dst_blocks = 95;
|
||||
CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_BLOCKS_FEWER_THAN_SRC);
|
||||
convert.dst_dir_name = &source;
|
||||
CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_EXISTS);
|
||||
for (const uint64_t blocks: {47u, 16385u}) {
|
||||
convert.dst_blocks = blocks;
|
||||
CHECK(SaveDataConvert(&convert) == SAVE_DATA_ERROR_PARAMETER);
|
||||
}
|
||||
}
|
||||
|
||||
void TestGuestSaveIsolation() {
|
||||
Reset("MULTIUSER");
|
||||
const auto name = DirName("slot");
|
||||
@@ -670,6 +702,7 @@ int main(int argc, char** argv) {
|
||||
TestIsolationAndSync();
|
||||
TestFailedWritePreservesSave();
|
||||
TestClassicSavePaths();
|
||||
TestConvert();
|
||||
TestGuestSaveIsolation();
|
||||
TestSaveAllocations();
|
||||
TestClassicSaveParams();
|
||||
|
||||
@@ -628,42 +628,45 @@ void TestReadLaneElimination() {
|
||||
"dynamic-lane read was rewritten unsafely");
|
||||
}
|
||||
|
||||
void TestCompleteUMinReduction() {
|
||||
void TestCompleteWaveReduction() {
|
||||
enum class Case {
|
||||
Masked, Subset, Literal, LiteralMasked, LiteralMove, Wave32,
|
||||
UnrelatedSeed, UnmaskedSymbolic, WrongNeutral, RowMask, BankMask,
|
||||
Masked, ScalarExec, Wave32, Wave32Rows, Wave32Maximum, RowMask, BankMask,
|
||||
Shift, FetchInactive, BoundControl, Dpp8, MoveExec, UpdateExec,
|
||||
Previous, DppSource, CrossExec, CrossRow, CrossSelectorLow,
|
||||
CrossSelectorHigh, CrossFetch, CrossBound, CrossSource, FinalExec,
|
||||
Endpoint, EndpointSource, DifferentWitness, CrossBlock, WrongOperation
|
||||
Endpoint, EndpointSource, CrossBlock, WrongOperation
|
||||
};
|
||||
for (const auto test : {Case::Masked, Case::Subset, Case::Literal,
|
||||
Case::LiteralMasked, Case::LiteralMove, Case::Wave32, Case::UnrelatedSeed,
|
||||
Case::UnmaskedSymbolic, Case::WrongNeutral, Case::RowMask, Case::BankMask,
|
||||
for (const auto test : {Case::Masked, Case::ScalarExec, Case::Wave32,
|
||||
Case::Wave32Rows, Case::Wave32Maximum, Case::RowMask, Case::BankMask,
|
||||
Case::Shift, Case::FetchInactive, Case::BoundControl, Case::Dpp8,
|
||||
Case::MoveExec, Case::UpdateExec, Case::Previous, Case::DppSource,
|
||||
Case::CrossExec, Case::CrossRow, Case::CrossSelectorLow,
|
||||
Case::CrossSelectorHigh, Case::CrossFetch, Case::CrossBound,
|
||||
Case::CrossSource, Case::FinalExec, Case::Endpoint, Case::EndpointSource,
|
||||
Case::DifferentWitness, Case::CrossBlock, Case::WrongOperation}) {
|
||||
Case::CrossBlock, Case::WrongOperation}) {
|
||||
Fixture fixture(test == Case::CrossBlock ? 2 : 1);
|
||||
const auto source = fixture.Emit(ValueOpcode::LaneId);
|
||||
const auto mask = fixture.Emit(ValueOpcode::ULessThan32, {source, Value(19u)});
|
||||
const auto other_mask = fixture.Emit(ValueOpcode::IEqual32, {source, Value(7u)});
|
||||
const auto negated = fixture.Emit(ValueOpcode::LogicalNot, {mask});
|
||||
const bool literal = test == Case::Literal || test == Case::LiteralMasked;
|
||||
const auto enabled = literal ? Value(true) :
|
||||
fixture.Emit(ValueOpcode::LogicalOr, {negated, mask});
|
||||
auto seed_mask = test == Case::UnrelatedSeed ? other_mask : mask;
|
||||
if (test == Case::Subset)
|
||||
seed_mask = fixture.Emit(ValueOpcode::LogicalAnd, {other_mask, mask});
|
||||
const auto seed = test == Case::Literal || test == Case::UnmaskedSymbolic ? source :
|
||||
fixture.Emit(ValueOpcode::SelectU32,
|
||||
{seed_mask, source, Value(test == Case::WrongNeutral ? 0u : UINT32_MAX)});
|
||||
auto enabled = fixture.Emit(ValueOpcode::LogicalOr, {negated, mask});
|
||||
if (test == Case::ScalarExec) {
|
||||
// s_orn2_saveexec of a mask that is not a ballot, read back as a lane bit.
|
||||
const auto exec = fixture.Emit(ValueOpcode::GetUserData, {Value(static_cast<ScalarReg>(2))});
|
||||
const auto full = fixture.Emit(ValueOpcode::BitwiseOr32,
|
||||
{fixture.Emit(ValueOpcode::BitwiseNot32, {exec}), exec});
|
||||
const auto bit = fixture.Emit(ValueOpcode::BitwiseAnd32, {source, Value(31u)});
|
||||
const auto shifted = fixture.Emit(ValueOpcode::ShiftRightLogical32, {full, bit});
|
||||
enabled = fixture.Emit(ValueOpcode::INotEqual32,
|
||||
{fixture.Emit(ValueOpcode::BitwiseAnd32, {shifted, Value(1u)}), Value(0u)});
|
||||
}
|
||||
// A UMax scan seeds lanes outside EXEC with zero.
|
||||
const bool maximum = test == Case::Wave32Maximum;
|
||||
const uint32_t neutral = maximum ? 0u : UINT32_MAX;
|
||||
const auto seed = fixture.Emit(ValueOpcode::SelectU32, {mask, source, Value(neutral)});
|
||||
auto prefix = fixture.Emit(ValueOpcode::SelectU32, {enabled, seed, source});
|
||||
Value preserved;
|
||||
const auto operation = test == Case::WrongOperation ? ValueOpcode::BitwiseOr32 :
|
||||
ValueOpcode::UMin32;
|
||||
auto operation = maximum ? ValueOpcode::UMax32 : ValueOpcode::UMin32;
|
||||
if (test == Case::WrongOperation) operation = ValueOpcode::BitwiseOr32;
|
||||
for (const uint32_t shift : {1u, 2u, 4u, 8u}) {
|
||||
DppMoveFlags flags{.control = 0x110u + shift};
|
||||
if (shift == 2) {
|
||||
@@ -675,8 +678,7 @@ void TestCompleteUMinReduction() {
|
||||
if (test == Case::Dpp8) flags.dpp8 = true;
|
||||
}
|
||||
const auto move = fixture.Emit(ValueOpcode::DppMoveU32,
|
||||
{test == Case::DppSource ? source : prefix,
|
||||
test == Case::MoveExec ? mask : test == Case::LiteralMove ? Value(true) : enabled});
|
||||
{test == Case::DppSource ? source : prefix, test == Case::MoveExec ? mask : enabled});
|
||||
move.Instruction()->SetFlags(flags);
|
||||
const auto combined = fixture.Emit(operation, {move, prefix});
|
||||
const auto update = fixture.Emit(ValueOpcode::DppUpdateU32,
|
||||
@@ -686,11 +688,7 @@ void TestCompleteUMinReduction() {
|
||||
prefix = update;
|
||||
if (shift == 2) preserved = fixture.Emit(ValueOpcode::ReferenceU32, {prefix});
|
||||
}
|
||||
auto cross_exec = test == Case::CrossExec ? mask : enabled;
|
||||
if (test == Case::DifferentWitness) {
|
||||
const auto other_not = fixture.Emit(ValueOpcode::LogicalNot, {other_mask});
|
||||
cross_exec = fixture.Emit(ValueOpcode::LogicalOr, {other_mask, other_not});
|
||||
}
|
||||
const auto cross_exec = test == Case::CrossExec ? mask : enabled;
|
||||
const auto cross = fixture.Emit(ValueOpcode::Permlane16U32,
|
||||
{test == Case::CrossSource ? source : prefix,
|
||||
Value(test == Case::CrossSelectorLow ? 0xfffffff0u : UINT32_MAX),
|
||||
@@ -704,10 +702,16 @@ void TestCompleteUMinReduction() {
|
||||
const auto final = fixture.Emit(ValueOpcode::SelectU32,
|
||||
{test == Case::FinalExec ? mask : enabled, combined, prefix});
|
||||
const uint32_t result_block = test == Case::CrossBlock ? 1u : 0u;
|
||||
// Wave32 reads each half's row endpoint from the scan itself.
|
||||
const bool rows = test == Case::Wave32Rows || test == Case::ScalarExec || maximum;
|
||||
const auto endpoints = rows ? prefix : final;
|
||||
uint32_t low_lane = test == Case::Endpoint ? 30u : 31u;
|
||||
if (rows) low_lane = 15u;
|
||||
const auto low = fixture.Emit(ValueOpcode::ReadLane,
|
||||
{final, Value(test == Case::Endpoint ? 30u : 31u)}, 0, result_block);
|
||||
{endpoints, Value(low_lane)}, 0, result_block);
|
||||
const auto high = fixture.Emit(ValueOpcode::ReadLane,
|
||||
{test == Case::EndpointSource ? prefix : final, Value(63u)}, 0, result_block);
|
||||
{test == Case::EndpointSource ? prefix : endpoints, Value(rows ? 31u : 63u)}, 0,
|
||||
result_block);
|
||||
const auto result = fixture.Emit(operation, {high, low}, 0, result_block);
|
||||
const auto use = fixture.Emit(ValueOpcode::ReferenceU32, {result}, 0, result_block);
|
||||
using Terminator = Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind;
|
||||
@@ -718,16 +722,19 @@ void TestCompleteUMinReduction() {
|
||||
fixture.BlockAt().terminator.true_block = &fixture.BlockAt(1);
|
||||
}
|
||||
ValidateProgram(fixture.program, true);
|
||||
const auto stats = EliminateReadLane(fixture.program, test == Case::Wave32 ? 32u : 64u);
|
||||
const bool matches = test == Case::Masked || test == Case::Subset ||
|
||||
test == Case::Literal || test == Case::LiteralMasked || test == Case::LiteralMove;
|
||||
// The pipeline folds full-EXEC predicates to true before eliminating reads.
|
||||
const uint32_t wave_size = test == Case::Wave32 || rows ? 32u : 64u;
|
||||
ConstantPropagationPass(fixture.program.blocks, wave_size);
|
||||
const auto stats = EliminateReadLane(fixture.program, wave_size);
|
||||
const bool matches = test == Case::Masked || rows;
|
||||
Check(stats.rewritten_reductions == (matches ? 1u : 0u) && stats.rewritten_reads == 0,
|
||||
("UMin reduction proof failed case " + std::to_string(static_cast<unsigned>(test))).c_str());
|
||||
("wave reduction proof failed case " + std::to_string(static_cast<unsigned>(test))).c_str());
|
||||
if (matches) {
|
||||
const auto *reduction = use.Instruction()->Arg(0).ResolveInstruction();
|
||||
Check(reduction->GetOpcode() == ValueOpcode::SubgroupUMinU32 &&
|
||||
Check(reduction->GetOpcode() ==
|
||||
(maximum ? ValueOpcode::SubgroupUMaxU32 : ValueOpcode::SubgroupUMinU32) &&
|
||||
reduction->Arg(0).Resolve() == seed.Resolve(),
|
||||
"UMin reduction changed the neutral-masked input");
|
||||
"wave reduction changed the neutral-masked input");
|
||||
RemoveIdentities(fixture.program);
|
||||
EliminateDeadCode(fixture.program.blocks);
|
||||
uint32_t updates = 0;
|
||||
@@ -735,14 +742,14 @@ void TestCompleteUMinReduction() {
|
||||
updates += inst.GetOpcode() == ValueOpcode::DppUpdateU32;
|
||||
Check(inst.GetOpcode() != ValueOpcode::ReadLane &&
|
||||
inst.GetOpcode() != ValueOpcode::Permlane16U32,
|
||||
"UMin reduction retained unused endpoint or cross-row operations");
|
||||
"wave reduction retained unused endpoint or cross-row operations");
|
||||
}
|
||||
Check(updates == 2 && preserved.Instruction()->Arg(0).Instruction()->HasUses(),
|
||||
"UMin reduction destroyed an independently consumed prefix or kept its dead suffix");
|
||||
"wave reduction destroyed an independently consumed prefix or kept its dead suffix");
|
||||
ValidateProgram(fixture.program, true);
|
||||
} else {
|
||||
Check(use.Instruction()->Arg(0).Resolve() == result,
|
||||
"rejected UMin proof changed its original result");
|
||||
"rejected wave reduction proof changed its original result");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -884,7 +891,7 @@ int main() {
|
||||
TestSharedIntegerRuntimeDependencies();
|
||||
TestConstantBufferBounds();
|
||||
TestReadLaneElimination();
|
||||
TestCompleteUMinReduction();
|
||||
TestCompleteWaveReduction();
|
||||
TestOptimizationPipeline();
|
||||
TestControlFlowValueSurvivesReadLaneFolding();
|
||||
TestDeadPhiCyclesAndPlanningRoots();
|
||||
|
||||
@@ -242,6 +242,10 @@ struct ImageHeapTestAccess {
|
||||
static ImageId ImageOf(const ImageHeap &heap, uint32_t entry) {
|
||||
return Element(heap, entry).binding.image_id;
|
||||
}
|
||||
// Whether the newest table no longer watches its last page.
|
||||
static bool TailPageDirty(const ImageHeap &heap) {
|
||||
return heap.m_memories.back()->dirty_pages.back() != 0u;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureCacheTestAccess {
|
||||
@@ -576,6 +580,10 @@ struct RenderExecutorTestAccess {
|
||||
executor.ResetBindings();
|
||||
}
|
||||
|
||||
static bool ResolveColorTargets(RenderExecutor &executor, CommandBuffer &buffer) {
|
||||
return executor.ResolveColorTargets(buffer, 0);
|
||||
}
|
||||
|
||||
static bool BoundImagesInOrder(const RenderExecutor &executor, ImageId first,
|
||||
ImageId second) {
|
||||
return executor.m_bound_images.size() == 2 &&
|
||||
@@ -1432,6 +1440,7 @@ struct GraphicsCase {
|
||||
u32 pixel_custom_interpolation_mask = 0;
|
||||
u32 width = 1;
|
||||
std::vector<u32> object_ids;
|
||||
std::vector<u32> gds_initial;
|
||||
};
|
||||
|
||||
struct CompiledShader {
|
||||
@@ -5507,6 +5516,93 @@ public:
|
||||
ImageHeapTestAccess::ImageOf(heap, filtered.slots[240]),
|
||||
"a reactivated view kept an element whose image slot holds a new image");
|
||||
|
||||
// Dynamic-resolution tables hold a descriptor per extent of one render target, each with
|
||||
// its own pitch. Only the first gets an image; the others wait until an image of their
|
||||
// extent exists.
|
||||
const auto extent = [&](uint32_t width) {
|
||||
auto words = descriptor(50, Format::k8_8_8_8UNorm);
|
||||
words[2] = (width - 1u) | (3u << 14u);
|
||||
return words;
|
||||
};
|
||||
const auto resolve = [&](const auto &root, const auto &words) {
|
||||
return executor.ResolveTexture(root, {.dwords = words, .dword_count = 8u}).image_id;
|
||||
};
|
||||
write(50, extent(64));
|
||||
write(51, extent(128));
|
||||
Require(name, "extents", acquire(sampled), "the view was declined");
|
||||
draw();
|
||||
Require(name, "held extent",
|
||||
ImageHeapTestAccess::ImageOf(heap, filtered.slots[400]) &&
|
||||
!ImageHeapTestAccess::ImageOf(heap, filtered.slots[408]),
|
||||
"a second extent of one address got its own image");
|
||||
// A non-heap lookup, as for a render target, creates the second extent.
|
||||
const auto rendered = resolve(sampled, extent(128));
|
||||
Require(name, "rendered extent", acquire(sampled), "the view was declined");
|
||||
draw();
|
||||
Require(name, "held descriptor",
|
||||
rendered && ImageHeapTestAccess::ImageOf(heap, filtered.slots[408]) == rendered,
|
||||
"a held descriptor missed the image made for its extent");
|
||||
// A released held element's index is reused once its tick ends; it is no longer held.
|
||||
write(52, extent(256));
|
||||
Require(name, "third extent", acquire(sampled), "the view was declined");
|
||||
draw();
|
||||
const auto held = filtered.slots[416];
|
||||
write(52, {});
|
||||
Require(name, "released extent", acquire(sampled), "the view was declined");
|
||||
draw();
|
||||
write(53, descriptor(53, Format::k8_8_8_8UNorm));
|
||||
Require(name, "reused element", acquire(sampled), "the view was declined");
|
||||
draw();
|
||||
Require(name, "retry", acquire(sampled), "the view was declined");
|
||||
draw();
|
||||
Require(name, "reused held element", held != 0u && filtered.slots[424] == held,
|
||||
"a reused element was retried as the held element it replaced");
|
||||
// A 2D view of a heap-pinned volume is another image type, not another extent.
|
||||
auto plane_root = sampled;
|
||||
plane_root.heap = true;
|
||||
auto volume_root = plane_root;
|
||||
volume_root.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim3D;
|
||||
auto volume_words = descriptor(54, Format::k8_8_8_8UNorm);
|
||||
volume_words[3] = DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Prospero::ImageType::kColor3D) << 28u);
|
||||
volume_words[4] = 3u;
|
||||
const auto volume = resolve(volume_root, volume_words);
|
||||
Require(name, "volume", static_cast<bool>(volume), "a volume texture was not resolved");
|
||||
auto &volume_refs = textures.GetImage(volume).heap_refs;
|
||||
volume_refs.fetch_add(1u);
|
||||
const auto plane = resolve(plane_root, descriptor(54, Format::k8_8_8_8UNorm));
|
||||
volume_refs.fetch_sub(1u);
|
||||
Require(name, "volume plane", static_cast<bool>(plane),
|
||||
"a 2D view of a pinned volume was held as another extent");
|
||||
|
||||
// A table follows its descriptor's size and keeps its elements. Growing probes the new
|
||||
// words, shrinking releases them, and a draw sees one size.
|
||||
write(table_size / 32u, descriptor(40, Format::k8_8_8_8UNorm));
|
||||
const auto elements = ImageHeapTestAccess::ElementCount(heap);
|
||||
const IR::ImageHeapRequest grown{table, table_size * 2u, 32u, 0u, table_size / 16u, &sampled};
|
||||
Require(name, "grown table",
|
||||
heap.Acquire(grown, view) && view.slot_count == table_size / 2u &&
|
||||
ImageHeapTestAccess::MemoryCount(heap) == 1u,
|
||||
"a grown table was declined or kept apart from its old size");
|
||||
draw();
|
||||
const auto added = ImageHeapTestAccess::ImageOf(heap, filtered.slots[table_size / 4u]);
|
||||
Require(name, "grown descriptor",
|
||||
added && ImageHeapTestAccess::ElementCount(heap) <= elements + 1u,
|
||||
"a grown table missed its new descriptor or interned its old ones again");
|
||||
Require(name, "shrunk table",
|
||||
acquire(sampled) && view.slot_count == table_size / 4u &&
|
||||
filtered.slots.back() == 0u && textures.GetImage(added).heap_refs.load() == 0u,
|
||||
"a shrunk table kept a descriptor past its end");
|
||||
Require(name, "one size per draw",
|
||||
heap.Acquire(grown, view) && view.slot_count == table_size / 4u,
|
||||
"a draw saw two sizes of one table");
|
||||
draw();
|
||||
Require(name, "regrown table", heap.Acquire(grown, view), "a regrown table was declined");
|
||||
draw();
|
||||
Require(name, "regrown descriptor",
|
||||
ImageHeapTestAccess::ImageOf(heap, filtered.slots[table_size / 4u]) == added,
|
||||
"a table grown again after shrinking missed its descriptor");
|
||||
|
||||
heap.UnmapMemory(table, table_size);
|
||||
Require(name, "unmapped table",
|
||||
ImageHeapTestAccess::MemoryCount(heap) == 0u &&
|
||||
@@ -5518,6 +5614,13 @@ public:
|
||||
acquire(sampled, idle_table) && view.kinds.empty() &&
|
||||
ImageHeapTestAccess::MemoryCount(heap) == 1u,
|
||||
"an all-null table was not kept resident");
|
||||
// A write past a table's end, in its watched tail page, must unwatch the page.
|
||||
constexpr uint64_t unaligned_table = base + 0x1a0800;
|
||||
Require(name, "tail page fault",
|
||||
acquire(sampled, unaligned_table) &&
|
||||
context.HandleFault(PageFaultAccess::Write, unaligned_table + table_size) &&
|
||||
ImageHeapTestAccess::TailPageDirty(heap),
|
||||
"a write fault next to a table left its page watched");
|
||||
draw();
|
||||
context.UnmapMemory(base, idle_table - base);
|
||||
scheduler.Finish();
|
||||
@@ -7594,6 +7697,40 @@ public:
|
||||
verify_array();
|
||||
}
|
||||
|
||||
// Image heaps keep a BC6H array and a raw block view of its first cube resident together.
|
||||
// That raw read views the BC image and creates its view; a raw writer or a raw image of
|
||||
// other levels, whose layers lie elsewhere, still replaces it.
|
||||
for (uint32_t scenario = 0; scenario < 3; ++scenario) {
|
||||
const uint64_t address = base + 0x1b00000 + scenario * 0x180000;
|
||||
auto bc = MakeLinearDesc(address, 0, vk::Format::eBc6HUfloatBlock,
|
||||
Prospero::BufferFormat::kBc6UFloat, Prospero::ImageType::kColor2D,
|
||||
{256, 256, 1}, 12, 16, 1);
|
||||
auto raw = MakeLinearDesc(address, 0, vk::Format::eR32G32B32A32Uint,
|
||||
Prospero::BufferFormat::k32_32_32_32UInt, Prospero::ImageType::kColor2D,
|
||||
{64, 64, 1}, 6, 16, 1);
|
||||
bc.info.resources.levels = 6;
|
||||
raw.info.resources.levels = scenario == 2 ? 1 : 6;
|
||||
for (auto *desc : {&bc, &raw}) {
|
||||
desc->info.tile_mode = Prospero::TileMode::kStandard4KB;
|
||||
desc->info.UpdateSize();
|
||||
desc->view_info.level_count = desc->info.resources.levels;
|
||||
}
|
||||
if (scenario == 1) {
|
||||
raw.type = BindingType::Storage;
|
||||
raw.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
const auto bc_image = texture_cache.FindImage(bc);
|
||||
(void)texture_cache.FindTexture(bc_image, bc);
|
||||
const auto raw_image = texture_cache.FindImage(raw);
|
||||
const bool viewed = scenario == 0;
|
||||
Require(name, "raw block view of a BC image",
|
||||
(raw_image == bc_image) == viewed &&
|
||||
texture_cache.GetImage(bc_image).registered == viewed,
|
||||
"a raw block read did not view the clean BC image, or a raw writer or "
|
||||
"raw image of other levels kept it");
|
||||
if (viewed) (void)texture_cache.FindTexture(raw_image, raw);
|
||||
}
|
||||
|
||||
// A write through a separate buffer must not replace untouched image
|
||||
// texels with stale backing bytes. Exact-base writes still refresh it.
|
||||
constexpr uint64_t buffer_alias_offset = 0x1a00000;
|
||||
@@ -10547,6 +10684,98 @@ public:
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckColorResolvePlacedTarget() {
|
||||
constexpr const char *name = "ColorResolvePlacedTarget";
|
||||
constexpr uintptr_t base = 0x0000000209000000ull;
|
||||
constexpr uint64_t destination = base + 0x100000;
|
||||
constexpr uint32_t extent = 256;
|
||||
constexpr uint64_t allocation_alignment = 0x10000;
|
||||
constexpr uint64_t allocation_size = 0x200000;
|
||||
EnsureRuntimeContext();
|
||||
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
||||
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
||||
"resolve direct-memory allocation failed");
|
||||
void *mapped = reinterpret_cast<void *>(base);
|
||||
Require(name, "direct mapping",
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
||||
allocation_alignment) == 0 &&
|
||||
mapped == reinterpret_cast<void *>(base),
|
||||
"resolve fixed mapping failed");
|
||||
|
||||
for (const bool mip : {false, true}) {
|
||||
std::memset(mapped, 0, allocation_size);
|
||||
RenderContext context(m_runtime_context);
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
HW::Shader shaders{};
|
||||
const auto set_target = [&](uint32_t slot, uint64_t address, uint32_t side,
|
||||
uint32_t layers, uint32_t levels) {
|
||||
registers.SetColorBase(slot, {.addr = address});
|
||||
registers.SetColorInfo(slot, {.format = Prospero::ChannelLayout::k8_8_8_8,
|
||||
.channel_type = Prospero::ChannelType::kUNorm,
|
||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||
registers.SetColorView(slot, {.last_array_slice_index = layers - 1});
|
||||
registers.SetColorAttrib2(slot, {.height = side - 1, .width = side - 1,
|
||||
.num_mip_levels = levels - 1});
|
||||
registers.SetColorAttrib3(slot, {.tile_mode = Prospero::TileMode::kRenderTarget,
|
||||
.dimension = 1,
|
||||
.metadata_pipe_aligned = true});
|
||||
};
|
||||
set_target(1, destination, extent, mip ? 1 : 2, mip ? 2 : 1);
|
||||
registers.SetRenderTargetMask(0xff);
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
context.MapMemory(base, allocation_size);
|
||||
|
||||
RenderColorInfo whole{};
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(executor, scheduler.Current(),
|
||||
whole, 1);
|
||||
const auto &layout = whole.desc.info.mip_layout;
|
||||
const auto side = mip ? extent / 2 : extent;
|
||||
set_target(1, destination + (mip ? layout[1].offset : layout[0].size / 2), side, 1, 1);
|
||||
set_target(0, base, side, 1, 1);
|
||||
RenderColorInfo placed{};
|
||||
RenderColorInfo source{};
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(executor, scheduler.Current(),
|
||||
placed, 1);
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(executor, scheduler.Current(),
|
||||
source, 0);
|
||||
vk::ClearValue red{};
|
||||
red.color.float32 = std::array{1.0f, 0.0f, 0.0f, 1.0f};
|
||||
TextureCacheTestAccess::ClearImage(context.GetTextureCache(), scheduler.Current(),
|
||||
source.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, red);
|
||||
registers.SetColorControl({.mode = 3});
|
||||
Require(name, "resolve",
|
||||
RenderExecutorTestAccess::ResolveColorTargets(executor, scheduler.Current()),
|
||||
"the resolve draw was not consumed");
|
||||
Require(name, "placed subresource",
|
||||
ReadCachedTexel(name, context, whole.image_id, {}, {1, 1, 1}, mip ? 0 : 1,
|
||||
mip ? 1 : 0) == std::vector<u32>{0xff0000ffu} &&
|
||||
ReadCachedTexel(name, context, whole.image_id) == std::vector<u32>{0u},
|
||||
"the resolve wrote the register level and layer, not the placed ones");
|
||||
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
context.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
|
||||
Require(name, "unmap direct backing",
|
||||
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
||||
"resolve direct mapping release failed");
|
||||
Require(name, "release direct backing",
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
||||
direct_offset, allocation_size) == 0,
|
||||
"resolve direct-memory allocation release failed");
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckRenderExecutorColorVolumeDiscovery() {
|
||||
constexpr const char *name = "RenderExecutorColorVolumeDiscovery";
|
||||
constexpr uintptr_t base = 0x0000000203e00000ull;
|
||||
@@ -11317,7 +11546,7 @@ public:
|
||||
constexpr uintptr_t base = 0x0000000204c00000ull;
|
||||
constexpr uint64_t size = 0x10000;
|
||||
constexpr auto output = base + 0x2000;
|
||||
constexpr u32 value = 0x80000578u;
|
||||
constexpr u32 value = 0x80000578u, fill = 0xa5a5a5a5u;
|
||||
std::array<std::vector<u32>, 2> codes;
|
||||
for (u32 scalar = 0; scalar < codes.size(); ++scalar) {
|
||||
auto &code = codes[scalar];
|
||||
@@ -11356,15 +11585,18 @@ public:
|
||||
context.MapMemory(base, size);
|
||||
auto &cache = context.GetBufferCache();
|
||||
auto &shaders = processor.GetShCtx();
|
||||
struct Case { u32 mode, records; bool scalar; u32 expected; };
|
||||
constexpr Case cases[]{{2, 1, false, value}, {2, 0, false, 0},
|
||||
// The first case stores through a NumRecords=0 output, which binds the shared null
|
||||
// buffer; a later NumRecords=0 load must still read zero.
|
||||
struct Case { u32 mode, records; bool scalar; u32 expected; bool null_output = false; };
|
||||
constexpr Case cases[]{{3, 4, false, fill, true},
|
||||
{2, 1, false, value}, {2, 0, false, 0},
|
||||
{3, 1, false, 0}, {3, 4, false, value},
|
||||
{2, 1, true, 0}, {2, 4, true, value}};
|
||||
for (const auto wave : {32u, 64u}) {
|
||||
for (const auto prefix : {0u, 8u}) {
|
||||
const auto address = base + 0x1000 + prefix;
|
||||
for (const auto test : cases) {
|
||||
cache.FillBuffer(output, wave * sizeof(u32), 0xa5a5a5a5u, false);
|
||||
cache.FillBuffer(output, wave * sizeof(u32), fill, false);
|
||||
shaders.SetCsShader({
|
||||
.data_addr = reinterpret_cast<uint64_t>(codes[test.scalar].data()),
|
||||
.num_thread_x = wave, .num_thread_y = 1, .num_thread_z = 1,
|
||||
@@ -11376,7 +11608,7 @@ public:
|
||||
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u) |
|
||||
(test.mode << 28u);
|
||||
out.UpdateAddress48(output);
|
||||
out.fields[2] = wave * sizeof(u32);
|
||||
out.fields[2] = test.null_output ? 0u : wave * sizeof(u32);
|
||||
out.fields[3] = DstSel(4, 5, 6, 7) |
|
||||
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u) | (3u << 28u);
|
||||
for (u32 i = 0; i < 4; ++i) {
|
||||
@@ -12274,6 +12506,8 @@ public:
|
||||
PackedCase{vk::Format::eR16G16Unorm, 0x0000ffffu, {1.0f, 0.0f}},
|
||||
PackedCase{vk::Format::eR16G16Unorm, 0x80000001u,
|
||||
{1.0f / 65535.0f, 32768.0f / 65535.0f}},
|
||||
PackedCase{vk::Format::eR16G16B16A16Unorm, 0x000180000000ffffull,
|
||||
{1.0f, 0.0f, 32768.0f / 65535.0f, 1.0f / 65535.0f}},
|
||||
PackedCase{vk::Format::eR8Unorm, 0xffffff80u, {128.0f / 255.0f}},
|
||||
PackedCase{vk::Format::eR8G8Unorm, 0xffff40ffu, {1.0f, 64.0f / 255.0f}},
|
||||
};
|
||||
@@ -12309,11 +12543,19 @@ public:
|
||||
};
|
||||
for (const auto &test : {UintCase{vk::Format::eR8Uint, 0xffffff05u, {5u}},
|
||||
UintCase{vk::Format::eR8G8Uint, 0xffff0a05u, {5u, 10u}},
|
||||
UintCase{vk::Format::eR16Uint, 0xffff1234u, {0x1234u}}}) {
|
||||
Require(name, "packed unsigned color clear",
|
||||
UintCase{vk::Format::eR16Uint, 0xffff1234u, {0x1234u}},
|
||||
UintCase{vk::Format::eR16G16B16A16Uint, 0x4444333322221111ull,
|
||||
{0x1111u, 0x2222u, 0x3333u, 0x4444u}},
|
||||
UintCase{vk::Format::eR16G16B16A16Sint, 0x7fff0001ffff8000ull,
|
||||
{0xffff8000u, 0xffffffffu, 1u, 0x7fffu}},
|
||||
UintCase{vk::Format::eR32G32Uint, 0x89abcdef01234567ull,
|
||||
{0x01234567u, 0x89abcdefu}},
|
||||
UintCase{vk::Format::eR32G32Sint, 0x80000000ffffffffull,
|
||||
{0xffffffffu, 0x80000000u}}}) {
|
||||
Require(name, "packed integer color clear",
|
||||
DecodePackedColorClear(test.format, test.word, clear) &&
|
||||
std::bit_cast<std::array<uint32_t, 4>>(clear.uint32) == test.channels,
|
||||
"narrow unsigned clear lost a channel or kept bits of the next one");
|
||||
"integer clear lost a channel, its sign or bits of the next one");
|
||||
}
|
||||
Require(name, "R8 DWORD fill",
|
||||
!DecodeColorDwordFill(vk::Format::eR8Uint, 0x01020304u, clear) &&
|
||||
@@ -12329,6 +12571,10 @@ public:
|
||||
DecodeColorDwordFill(vk::Format::eR32G32Sfloat, 0x3f800000u, clear) &&
|
||||
clear.float32[0] == 1.0f && clear.float32[1] == 1.0f,
|
||||
"a repeated DWORD fill was decoded as a two-register clear");
|
||||
Require(name, "RGBA16 DWORD fill",
|
||||
DecodeColorDwordFill(vk::Format::eR16G16B16A16Uint, 0x22221111u, clear) &&
|
||||
clear.uint32[2] == 0x1111u && clear.uint32[3] == 0x2222u,
|
||||
"a DWORD fill did not repeat across the 64-bit pixel");
|
||||
constexpr uintptr_t base = 0x0000000204100000ull;
|
||||
constexpr uint64_t allocation_size = 0x1000000;
|
||||
constexpr uint64_t allocation_alignment = 0x10000;
|
||||
@@ -17755,7 +18001,7 @@ public:
|
||||
}
|
||||
|
||||
std::vector<u32> ReadBuffer(const char *shader_name, const Buffer &buffer,
|
||||
size_t dword_count) {
|
||||
size_t dword_count, size_t first_dword = 0) {
|
||||
if (!buffer.coherent) {
|
||||
vk::MappedMemoryRange range{};
|
||||
range.sType = vk::StructureType::eMappedMemoryRange;
|
||||
@@ -17772,7 +18018,7 @@ public:
|
||||
m_device.mapMemory(buffer.memory, 0, buffer.size, {}, &data),
|
||||
"vkMapMemory");
|
||||
std::vector<u32> ret(dword_count, 0);
|
||||
std::memcpy(ret.data(), data, dword_count * sizeof(u32));
|
||||
std::memcpy(ret.data(), static_cast<const u32 *>(data) + first_dword, dword_count * sizeof(u32));
|
||||
m_device.unmapMemory(buffer.memory);
|
||||
return ret;
|
||||
}
|
||||
@@ -18002,6 +18248,7 @@ public:
|
||||
Require(test.name, "dispatch", info.range <= buffer.size,
|
||||
"storage buffer descriptor range exceeds backing buffer");
|
||||
}
|
||||
shader_data[layout.MemorySizeDword() + index] = static_cast<u32>(info.range);
|
||||
}
|
||||
vk::WriteDescriptorSet write{};
|
||||
write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
@@ -19672,6 +19919,40 @@ public:
|
||||
}
|
||||
Require(name, "packed rounding program distinction", half_up_program != truncate_program,
|
||||
"half-up and truncate exports shared one shader specialization");
|
||||
// UINT and SINT targets give their exports integer outputs.
|
||||
registers.Reset();
|
||||
registers.SetViewportTransformControl(0x300);
|
||||
registers.SetRenderTargetMask(0xffu);
|
||||
registers.SetShaderMask(0xffu);
|
||||
shaders.SetPsShaderBase(pixel_address);
|
||||
for (const auto [slot, type] : {std::pair{0u, Prospero::ChannelType::kUInt},
|
||||
std::pair{1u, Prospero::ChannelType::kSInt}}) {
|
||||
registers.SetTargetOutputMode(slot, 9);
|
||||
registers.SetColorBase(slot, {.addr = depth_address + 0x10000 * (slot + 1)});
|
||||
registers.SetColorInfo(slot, {.format = Prospero::ChannelLayout::k32, .channel_type = type,
|
||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||
registers.SetColorAttrib2(slot, {.height = extent - 1, .width = extent - 1});
|
||||
registers.SetColorAttrib3(slot, {.tile_mode = Prospero::TileMode::kLinear, .dimension = 1});
|
||||
}
|
||||
native_programs();
|
||||
Require(name, "integer target outputs",
|
||||
pixel.target_uint_mask == 1u && pixel.target_sint_mask == 2u,
|
||||
"UINT and SINT targets did not select integer export outputs");
|
||||
// MRT1 is the second blend source of a dual-source float target 0, not integer target 1.
|
||||
HW::BlendControl dual_blend{};
|
||||
dual_blend.enable = true;
|
||||
dual_blend.color_srcblend = static_cast<uint8_t>(Prospero::BlendFactor::kSrc1Color);
|
||||
registers.SetBlendControl(0, dual_blend);
|
||||
for (const auto [slot, type] : {std::pair{0u, Prospero::ChannelType::kFloat},
|
||||
std::pair{1u, Prospero::ChannelType::kUInt}}) {
|
||||
registers.SetColorInfo(slot, {.format = Prospero::ChannelLayout::k32, .channel_type = type,
|
||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||
native_programs();
|
||||
Require(name, "dual-source float outputs",
|
||||
pixel.dual_source_blending && pixel.target_uint_mask == 0u &&
|
||||
pixel.target_sint_mask == 0u,
|
||||
"the second blend source kept the integer type of render target 1");
|
||||
}
|
||||
DestroyBuffer(&packed_output);
|
||||
m_device.destroySampler(packed_sampler);
|
||||
DestroyBuffer(&stencil_readback);
|
||||
@@ -19749,8 +20030,40 @@ public:
|
||||
push_constant_range.size = ShaderRecompiler::IR::NativePushConstantSize;
|
||||
}
|
||||
|
||||
using Kind = ShaderRecompiler::IR::DescriptorBindingKind;
|
||||
const bool uses_gds = !test.gds_initial.empty();
|
||||
const auto gds_bit = uint64_t{1} << static_cast<u32>(Kind::Gds);
|
||||
Require(test.name, "graphics", fragment_bind.descriptor_mask == (uses_gds ? gds_bit : 0u),
|
||||
"a graphics case binds GDS only, and only with initial GDS data");
|
||||
Buffer gds_buffer;
|
||||
vk::DescriptorBufferInfo gds_info{};
|
||||
vk::WriteDescriptorSet gds_write{};
|
||||
vk::DescriptorSetLayout descriptor_layout = nullptr;
|
||||
if (uses_gds) {
|
||||
gds_buffer = CreateStorageBuffer(test.name, test.gds_initial, test.gds_initial.size());
|
||||
gds_info = {gds_buffer.buffer, 0, gds_buffer.size};
|
||||
const vk::DescriptorSetLayoutBinding binding{
|
||||
ShaderRecompiler::IR::NativeBinding(ShaderType::Pixel, Kind::Gds),
|
||||
vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment};
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info{};
|
||||
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
layout_info.bindingCount = 1;
|
||||
layout_info.pBindings = &binding;
|
||||
RequireVk(test.name, "graphics",
|
||||
m_device.createDescriptorSetLayout(&layout_info, nullptr, &descriptor_layout),
|
||||
"vkCreateDescriptorSetLayout");
|
||||
gds_write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
gds_write.dstBinding = binding.binding;
|
||||
gds_write.descriptorCount = 1;
|
||||
gds_write.descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
gds_write.pBufferInfo = &gds_info;
|
||||
}
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info{};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.setLayoutCount = uses_gds ? 1u : 0u;
|
||||
pipeline_layout_info.pSetLayouts = &descriptor_layout;
|
||||
pipeline_layout_info.pushConstantRangeCount =
|
||||
push_constant_range.size != 0 ? 1u : 0u;
|
||||
pipeline_layout_info.pPushConstantRanges =
|
||||
@@ -19907,6 +20220,10 @@ public:
|
||||
}
|
||||
cmd.beginRendering(rendering);
|
||||
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
if (uses_gds) {
|
||||
cmd.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, 1,
|
||||
&gds_write);
|
||||
}
|
||||
if (push_constant_range.size != 0) {
|
||||
ShaderRecompiler::IR::PushData push_data;
|
||||
if (object_id) {
|
||||
@@ -19934,6 +20251,8 @@ public:
|
||||
|
||||
m_device.destroyPipeline(pipeline, nullptr);
|
||||
m_device.destroyPipelineLayout(pipeline_layout, nullptr);
|
||||
m_device.destroyDescriptorSetLayout(descriptor_layout, nullptr);
|
||||
DestroyBuffer(&gds_buffer);
|
||||
m_device.destroyShaderModule(fragment_module, nullptr);
|
||||
m_device.destroyShaderModule(vertex_module, nullptr);
|
||||
DestroyBuffer(&vertex_buffer);
|
||||
@@ -21423,8 +21742,9 @@ private:
|
||||
"shaderStorageImageWriteWithoutFormat is not supported");
|
||||
Require("VulkanHarness", "dispatch",
|
||||
available_features.shaderImageGatherExtended == true &&
|
||||
available_features.shaderResourceMinLod == true &&
|
||||
available_derivatives.computeDerivativeGroupQuads == true,
|
||||
"image gather or compute derivative quads are not supported");
|
||||
"image gather, MinLod or compute derivative quads are not supported");
|
||||
Require("VulkanHarness", "dispatch",
|
||||
available_features11.storageBuffer16BitAccess == true &&
|
||||
available_features12.storageBuffer8BitAccess == true,
|
||||
@@ -21468,6 +21788,7 @@ private:
|
||||
available_features.drawIndirectFirstInstance &&
|
||||
available_features.multiDrawIndirect &&
|
||||
available_features.depthBounds &&
|
||||
available_features.fragmentStoresAndAtomics &&
|
||||
available_depth_clip.depthClipEnable &&
|
||||
available_clip_control.depthClipControl &&
|
||||
available_color_write.colorWriteEnable &&
|
||||
@@ -21558,6 +21879,7 @@ private:
|
||||
device_features.shaderStorageImageWriteWithoutFormat = true;
|
||||
device_features.shaderStorageImageMultisample = available_features.shaderStorageImageMultisample;
|
||||
device_features.shaderImageGatherExtended = true;
|
||||
device_features.shaderResourceMinLod = true;
|
||||
device_features.sampleRateShading = true;
|
||||
device_features.shaderInt64 = true;
|
||||
device_features.shaderFloat64 = available_features.shaderFloat64;
|
||||
@@ -21566,6 +21888,7 @@ private:
|
||||
device_features.drawIndirectFirstInstance = m_rasterization_supported;
|
||||
device_features.multiDrawIndirect = m_rasterization_supported;
|
||||
device_features.depthBounds = m_rasterization_supported;
|
||||
device_features.fragmentStoresAndAtomics = m_rasterization_supported;
|
||||
device_info.pEnabledFeatures = &device_features;
|
||||
std::vector<const char *> device_extensions{
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
||||
@@ -22700,6 +23023,23 @@ TestCase Rdna2ScalarOpcodes() {
|
||||
O::S_SLEEP, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase ScalarApertureLimits() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
code.push_back(EncodeSop1(0x04, 20, 236));
|
||||
AppendStoreSgprPair(&code, 20, 0);
|
||||
code.push_back(EncodeSop1(0x04, 20, 238));
|
||||
AppendStoreSgprPair(&code, 20, 2);
|
||||
AppendEnd(&code);
|
||||
|
||||
return {"ScalarApertureLimits",
|
||||
code,
|
||||
{},
|
||||
{0xffffffffu, 0x80000000u, 0xffffffffu, 0x70000000u},
|
||||
{O::S_MOV_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase ScalarTrapDisabledFallsThroughExactRaw() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -39088,6 +39428,48 @@ TestCase ImageGatherExplicitLod() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ImageSampleLodClamp() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
// _cl clamps the computed LOD from below: compute's implicit LOD and zero
|
||||
// derivatives would both select mip 0, so a clamp of 1 must read mip 1.
|
||||
std::vector<u32> code;
|
||||
AppendVMovU32(&code, 20, 0);
|
||||
AppendVMovU32(&code, 21, 0);
|
||||
AppendVMovU32(&code, 22, 0);
|
||||
AppendVMovU32(&code, 23, 0);
|
||||
AppendVMovLiteral(&code, 24, 0x3f000000u);
|
||||
AppendVMovLiteral(&code, 25, 0x3f000000u);
|
||||
AppendVMovLiteral(&code, 26, 0x3f800000u);
|
||||
code.push_back(EncodeMimg0(0x21, 0x1));
|
||||
code.push_back(EncodeMimg1(0, 24, 0, 2));
|
||||
code.push_back(EncodeMimg0(0x23, 0x1));
|
||||
code.push_back(EncodeMimg1(1, 20, 0, 2));
|
||||
AppendStoreVgpr(&code, 0, 0);
|
||||
AppendStoreVgpr(&code, 1, 1);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "ImageSampleLodClamp";
|
||||
test.code = std::move(code);
|
||||
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.expected = std::vector<u32>(2, 0x41000000u);
|
||||
test.sampled_image_rgba_mips = {std::vector<u32>(4 * 4 * 4, 0x3f800000u),
|
||||
std::vector<u32>(2 * 2 * 4, 0x41000000u)};
|
||||
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32_32_32_32Float);
|
||||
test.user_data[1] |= 3u << 30u;
|
||||
test.user_data[2] = 3u << 14u;
|
||||
test.user_data[3] |= 1u << 16u;
|
||||
test.user_data[8] = 0x92u;
|
||||
test.user_data[9] = 0xfff000u;
|
||||
test.user_data[10] = 0x05000000u;
|
||||
test.user_data[50] = test.expected.size() * sizeof(u32);
|
||||
test.user_data[51] = 3u << 28u;
|
||||
test.has_user_data = true;
|
||||
test.required_spirv = {"MinLod"};
|
||||
return test;
|
||||
}
|
||||
|
||||
template <u32 mode> TestCase ImageGatherLodPerLane() {
|
||||
using O = ShaderOpcode;
|
||||
constexpr std::array names{
|
||||
@@ -40595,6 +40977,50 @@ void CheckFiniteBufferSelection(VulkanHarness &vulkan) {
|
||||
}
|
||||
}
|
||||
|
||||
void CheckStorageBufferBindingOver2GiB(VulkanHarness &vulkan) {
|
||||
// OpArrayLength reads 0 for ranges of 2 GiB or more on NVIDIA; bounds use the pushed binding size.
|
||||
constexpr u32 bytes = 0x80000010u, end = bytes / 4u, last = end - 1u;
|
||||
TestCase test;
|
||||
test.name = "StorageBufferBindingOver2GiB";
|
||||
Require(test.name, "device limit",
|
||||
vulkan.RuntimeContext().GetPhysicalDeviceProperties().limits.maxStorageBufferRange >= bytes,
|
||||
"storage buffer range cannot cover 2 GiB");
|
||||
AppendVMovLiteral(&test.code, 4, last * 4u);
|
||||
AppendVMovLiteral(&test.code, 6, end * 4u);
|
||||
test.code.insert(test.code.end(), {EncodeMubuf0(0x0c), EncodeMubuf1(5, 0, 4),
|
||||
EncodeMubuf0(0x0c), EncodeMubuf1(7, 0, 6),
|
||||
EncodeMubuf0(0x1c), EncodeMubuf1(5, 0, 6), EncodeSopp(0x0c, 0)});
|
||||
AppendStoreVgpr(&test.code, 5, 4);
|
||||
AppendStoreVgpr(&test.code, 7, 5);
|
||||
AppendEnd(&test.code);
|
||||
test.user_data = MakeStructuredStorageBufferData(0, bytes);
|
||||
test.user_data[50] = 1u << 20u;
|
||||
test.user_data[51] = 3u << 28u;
|
||||
test.has_user_data = true;
|
||||
test.forbidden_spirv = {"OpArrayLength"};
|
||||
const auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||
for (const auto &value : compiled.resources.buffers) {
|
||||
ShaderBufferResource descriptor{};
|
||||
std::copy_n(value.dwords.begin(), 4u, descriptor.fields);
|
||||
test.storage_buffer_ranges.emplace_back(descriptor.Base48(), u32(descriptor.GetSize()));
|
||||
}
|
||||
VulkanHarness::Buffer buffer;
|
||||
{
|
||||
std::vector<u32> initial(end + 4u, 0u);
|
||||
initial[5] = 0xdeadbeefu;
|
||||
initial[last] = 0x44444444u;
|
||||
initial[end] = 0xcafebabeu;
|
||||
buffer = vulkan.CreateStorageBuffer(test.name, initial, initial.size());
|
||||
}
|
||||
vulkan.Dispatch(test, compiled, buffer);
|
||||
const auto loads = vulkan.ReadBuffer(test.name, buffer, 2u, 4u);
|
||||
const auto canary = vulkan.ReadBuffer(test.name, buffer, 1u, end);
|
||||
vulkan.DestroyBuffer(&buffer);
|
||||
CompareWords(test, "a load at the last dword, a zero load and a dropped store at the 2 GiB range end",
|
||||
{0x44444444u, 0u, 0xcafebabeu}, {loads[0], loads[1], canary[0]});
|
||||
std::printf("[compute] %-32s ok\n", test.name);
|
||||
}
|
||||
|
||||
void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
TestCase test;
|
||||
@@ -40707,23 +41133,20 @@ void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
||||
test.required_spirv = {"StorageBufferArrayNonUniformIndexing"};
|
||||
test.forbidden_spirv.push_back("OpSwitch");
|
||||
std::set<u32> nonuniform;
|
||||
std::vector<u32> accessed_pointers;
|
||||
std::vector<u32> stored_pointers;
|
||||
u32 prefix_extracts = 0;
|
||||
for (size_t offset = 5; offset < compiled.spirv.size();) {
|
||||
const auto words = std::span(compiled.spirv).subspan(offset, compiled.spirv[offset] >> 16u);
|
||||
const auto opcode = static_cast<spv::Op>(words[0] & 0xffffu);
|
||||
if (opcode == spv::OpDecorate && words[2] == spv::DecorationNonUniform)
|
||||
nonuniform.insert(words[1]);
|
||||
if (opcode == spv::OpArrayLength) accessed_pointers.push_back(words[3]);
|
||||
if (opcode == spv::OpStore) accessed_pointers.push_back(words[1]);
|
||||
if (opcode == spv::OpStore) stored_pointers.push_back(words[1]);
|
||||
if (opcode == spv::OpBitFieldUExtract) ++prefix_extracts;
|
||||
offset += words.size();
|
||||
}
|
||||
Require(test.name, "shared store and nonuniform pointers",
|
||||
prefix_extracts == 1u && accessed_pointers.size() == 2u &&
|
||||
std::ranges::all_of(accessed_pointers, [&](u32 pointer) {
|
||||
return nonuniform.contains(pointer);
|
||||
}),
|
||||
prefix_extracts == 1u && stored_pointers.size() == 1u &&
|
||||
nonuniform.contains(stored_pointers.front()),
|
||||
"indirect store retained static prefixes, was duplicated or lost a NonUniform pointer decoration");
|
||||
} else if (single) {
|
||||
test.forbidden_spirv.push_back("OpSwitch");
|
||||
@@ -43826,6 +44249,92 @@ GraphicsCase GraphicsDsAddtidScratchExport() {
|
||||
O::DS_READ_ADDTID_B32, O::EXP, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
// Covers only pixel 1 of a 2x1 target, so the rest of its quad runs as helper invocations, and
|
||||
// starts a GDS counter at 10 in dword 5.
|
||||
void CoverOnePixelOfQuad(GraphicsCase *test) {
|
||||
test->width = 2;
|
||||
test->vertices = {0x00000000u, 0xbf800000u, 0, 0, 0, 0,
|
||||
0x40400000u, 0xbf800000u, 0, 0, 0, 0,
|
||||
0x00000000u, 0x40400000u, 0, 0, 0, 0};
|
||||
test->gds_initial = {0, 0, 0, 0, 0, 10};
|
||||
}
|
||||
|
||||
GraphicsCase GraphicsDsAppendSkipsHelperLanes(bool consume) {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
// The EXEC and VCC words exclude the helper lanes. Exports the first counter value, the second
|
||||
// minus the first minus s_bcnt1(exec) (reversed for consume), s_bcnt1(exec), and the EXEC plus
|
||||
// VCC counts after an if/endif on a compare of the interpolant (zero in every lane).
|
||||
const u32 op = consume ? 0x3d : 0x3e;
|
||||
std::vector<u32> code;
|
||||
code.push_back(EncodeVintrp(0x00, 7, 0, 0, 0));
|
||||
code.push_back(EncodeVintrp(0x01, 7, 0, 0, 1));
|
||||
AppendSMovLiteral(&code, 124, 0x100);
|
||||
code.push_back(EncodeDs0(op, 20, true));
|
||||
code.push_back(EncodeDs1(1, 0, 0));
|
||||
code.push_back(EncodeDs0(op, 20, true));
|
||||
code.push_back(EncodeDs1(2, 0, 0));
|
||||
code.push_back(EncodeSop1(0x10, 0, 126));
|
||||
code.push_back(EncodeVop2(0x26, 3, Vgpr(consume ? 1 : 2), consume ? 2 : 1));
|
||||
code.push_back(EncodeVop2(0x27, 3, 0, 3));
|
||||
code.push_back(EncodeVop1(0x06, 4, Vgpr(1)));
|
||||
code.push_back(EncodeVop1(0x05, 5, Vgpr(3)));
|
||||
code.push_back(EncodeVop1(0x06, 6, 0));
|
||||
code.push_back(EncodeVopc(0xc2, InlineU32(0), 7));
|
||||
code.push_back(EncodeSop1(0x24, 2, 106));
|
||||
code.push_back(EncodeSop2(0x11, 126, 126, 2));
|
||||
code.push_back(EncodeSop1(0x10, 1, 126));
|
||||
code.push_back(EncodeSop1(0x10, 4, 106));
|
||||
code.push_back(EncodeSop2(0x00, 1, 1, 4));
|
||||
code.push_back(EncodeVop1(0x06, 7, 1));
|
||||
code.push_back(EncodeExp0(0x00, 0xf));
|
||||
code.push_back(EncodeExp1(4, 5, 6, 7));
|
||||
AppendEnd(&code);
|
||||
|
||||
GraphicsCase test;
|
||||
test.name = consume ? "GraphicsDsConsumeSkipsHelperLanes" : "GraphicsDsAppendSkipsHelperLanes";
|
||||
test.fragment_code = std::move(code);
|
||||
CoverOnePixelOfQuad(&test);
|
||||
test.expected_pixel = {0, 0, 0, 0, 0x41200000u, 0, 0x3f800000u, 0x40000000u};
|
||||
test.opcodes = {O::V_INTERP_P1_F32, O::V_INTERP_P2_F32, O::S_MOV_B32,
|
||||
consume ? O::DS_CONSUME : O::DS_APPEND, O::S_BCNT1_I32_B64,
|
||||
O::V_SUB_NC_U32, O::V_SUBREV_NC_U32, O::V_CVT_F32_U32, O::V_CVT_F32_I32,
|
||||
O::V_CMP_EQ_U32, O::S_AND_SAVEEXEC_B64, O::S_OR_B64, O::S_ADD_U32, O::EXP,
|
||||
O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
GraphicsCase GraphicsDsOrderedCountSkipsHelperLanes() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
// A live lane adds the first lane's ADDR value (1) for the wave. Exports the first counter
|
||||
// value and the difference to the second.
|
||||
std::vector<u32> code;
|
||||
code.push_back(EncodeVintrp(0x00, 7, 0, 0, 0));
|
||||
code.push_back(EncodeVintrp(0x01, 7, 0, 0, 1));
|
||||
code.push_back(EncodeVop1(0x01, 2, InlineU32(1)));
|
||||
AppendSMovLiteral(&code, 124, 0x00100000u); // M0 base 0x10; offset0 adds 4: dword 5.
|
||||
code.push_back(EncodeDs0(0x3f, 7, true));
|
||||
code.push_back(EncodeDs1(1, 2, 2));
|
||||
code.push_back(EncodeDs0(0x3f, 7, true));
|
||||
code.push_back(EncodeDs1(3, 2, 2));
|
||||
code.push_back(EncodeVop2(0x26, 4, Vgpr(3), 1));
|
||||
code.push_back(EncodeVop1(0x06, 5, Vgpr(1)));
|
||||
code.push_back(EncodeVop1(0x06, 6, Vgpr(4)));
|
||||
code.push_back(EncodeExp0(0x00, 0xf));
|
||||
code.push_back(EncodeExp1(5, 6, 6, 6));
|
||||
AppendEnd(&code);
|
||||
|
||||
GraphicsCase test;
|
||||
test.name = "GraphicsDsOrderedCountSkipsHelperLanes";
|
||||
test.fragment_code = std::move(code);
|
||||
CoverOnePixelOfQuad(&test);
|
||||
test.expected_pixel = {0, 0, 0, 0, 0x41200000u, 0x3f800000u, 0x3f800000u, 0x3f800000u};
|
||||
test.opcodes = {O::V_INTERP_P1_F32, O::V_INTERP_P2_F32, O::V_MOV_B32, O::S_MOV_B32,
|
||||
O::DS_ORDERED_COUNT, O::V_SUB_NC_U32, O::V_CVT_F32_U32, O::EXP, O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
GraphicsCase GraphicsDirectSgprPushConstantExport() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -44091,6 +44600,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ExactPushConstantExtent);
|
||||
AddCase(ScalarShiftCountsMaskLowBits);
|
||||
AddCase(Rdna2ScalarOpcodes);
|
||||
AddCase(ScalarApertureLimits);
|
||||
AddCase(ScalarTrapDisabledFallsThroughExactRaw);
|
||||
AddCase(ScalarWaitcntDepctrCapturedVmVsrc);
|
||||
AddCase(ScalarExtendedArithmetic);
|
||||
@@ -44611,6 +45121,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ImageSampleAndGather);
|
||||
AddCase(ImageCubeGradientsPreserveDerivatives);
|
||||
AddCase(ImageGatherExplicitLod);
|
||||
AddCase(ImageSampleLodClamp);
|
||||
AddCase(ImageGatherLodPerLane<0>);
|
||||
AddCase(ImageGatherLodPerLane<1>);
|
||||
AddCase(ImageGatherLodPerLane<2>);
|
||||
@@ -44696,6 +45207,9 @@ std::vector<GraphicsCase> MakeGraphicsCases() {
|
||||
GraphicsFrontFaceBits(true, true),
|
||||
GraphicsFlatInterpolatorExport(),
|
||||
GraphicsDsAddtidScratchExport(),
|
||||
GraphicsDsAppendSkipsHelperLanes(false),
|
||||
GraphicsDsAppendSkipsHelperLanes(true),
|
||||
GraphicsDsOrderedCountSkipsHelperLanes(),
|
||||
GraphicsDirectSgprPushConstantExport(),
|
||||
GraphicsInlineSrtScalarPromotionExport(),
|
||||
GraphicsNullVmExportDiscardsInactiveExec(),
|
||||
@@ -49787,6 +50301,13 @@ void CheckPm4DrawIndirectMultiPacket(RenderContext &renderer) {
|
||||
KYTY_PM4(10, Pm4::IT_DRAW_INDIRECT_MULTI, 0), 0,
|
||||
auto_packet[2], auto_packet[3], Pm4::SH_NOP, 2, 0, 0, 16, 2};
|
||||
check_offsets(auto_multi_packet, hs, {53, 15}, "non-indexed multi-draw offsets");
|
||||
// Word 4 bit 31 has the CP write each subdraw's index, even with no offsets to patch.
|
||||
shaders.SetHsUserSgpr(11, 77);
|
||||
auto_multi_packet[2] = auto_multi_packet[3] = Pm4::SH_NOP;
|
||||
auto_multi_packet[4] = (1u << 31u) | (Pm4::SPI_SHADER_USER_DATA_HS_0 + 11);
|
||||
Require("Pm4DrawIndirectMulti", "multi-draw index",
|
||||
execute(auto_multi_packet.data(), auto_multi_packet.size()) && hs[11] == 1,
|
||||
"multi-draw did not write the last subdraw index");
|
||||
arguments = {0, 1, 99, 101};
|
||||
auto_packet[2] = Pm4::SH_NOP;
|
||||
check_offsets(auto_packet, hs, {53, 101}, "independent destinations");
|
||||
@@ -50686,6 +51207,13 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, Vop3pIntegerNegationCapturedAndSelectedHalves());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--helper-lanes-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunGraphicsCase(&vulkan, GraphicsDsAppendSkipsHelperLanes(false));
|
||||
RunGraphicsCase(&vulkan, GraphicsDsAppendSkipsHelperLanes(true));
|
||||
RunGraphicsCase(&vulkan, GraphicsDsOrderedCountSkipsHelperLanes());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--dpp-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunGraphicsCase(&vulkan, GraphicsSparseWaveMinimum());
|
||||
@@ -51021,6 +51549,7 @@ int main(int argc, char **argv) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckComparisonDepthTexture();
|
||||
RunCase(&vulkan, ImageGatherExplicitLod());
|
||||
RunCase(&vulkan, ImageSampleLodClamp());
|
||||
RunCase(&vulkan, ImageGatherLodPerLane<0>());
|
||||
RunCase(&vulkan, ImageGatherLodPerLane<1>());
|
||||
RunCase(&vulkan, ImageGatherLodPerLane<2>());
|
||||
@@ -51758,6 +52287,7 @@ int main(int argc, char **argv) {
|
||||
CheckGuardedWaterfallBufferRecords(vulkan);
|
||||
CheckGpuSelectedFormattedRecordStore(vulkan);
|
||||
CheckFiniteBufferSelection(vulkan);
|
||||
CheckStorageBufferBindingOver2GiB(vulkan);
|
||||
CheckRuntimeBufferRecords(vulkan);
|
||||
CheckSharedIndirectBufferLoads(vulkan);
|
||||
CheckComputeThreadDimensions(vulkan);
|
||||
@@ -51797,6 +52327,7 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckGraphicsPushConstantBank();
|
||||
vulkan.CheckRenderExecutorColorDiscovery();
|
||||
vulkan.CheckRenderExecutorColor1DArrayDiscovery();
|
||||
vulkan.CheckColorResolvePlacedTarget();
|
||||
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
||||
vulkan.CheckRenderExecutorColorMetadataClear();
|
||||
vulkan.CheckSampledDccClear();
|
||||
|
||||
+129
-21
@@ -1847,6 +1847,31 @@ void TestShaderStageBarriers() {
|
||||
"SPIR-V barrier does not use workgroup acquire-release memory semantics");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
|
||||
// s_barrier also acquires other waves' buffer and image stores.
|
||||
const auto acquires = [](std::span<const uint32_t> code,
|
||||
const ShaderRecompiler::CompileOptions &options) {
|
||||
const auto result = RecompileForTest(code, options);
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
// AcquireRelease | UniformMemory | WorkgroupMemory | ImageMemory.
|
||||
return DisassembleSpirvBinary(result.spirv).find(
|
||||
"OpControlBarrier %uint_2 %uint_2 %uint_2376") != std::string::npos;
|
||||
};
|
||||
const uint32_t buffer_store[] = {
|
||||
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(1, 0, 0), // buffer_store_dword
|
||||
EncodeSopp(0x0a), EncodeSopp(0x01),
|
||||
};
|
||||
Check(acquires(buffer_store, MakeCompileOptions(ShaderType::Compute)),
|
||||
"s_barrier after buffer stores does not acquire buffer memory");
|
||||
const uint32_t image_store[] = {
|
||||
EncodeMimg0(0x08, 0xf), EncodeMimg1(20, 0, 0, 4), // image_store
|
||||
EncodeSopp(0x0a), EncodeSopp(0x01),
|
||||
};
|
||||
const auto image_user_data = ImageTestUserData();
|
||||
auto image_options = MakeCompileOptions(ShaderType::Compute);
|
||||
image_options.user_data = image_user_data;
|
||||
Check(acquires(image_store, image_options),
|
||||
"s_barrier after image stores does not acquire image memory");
|
||||
|
||||
const auto vertex_result =
|
||||
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
|
||||
CheckSpirvBinaryValidates(vertex_result.spirv);
|
||||
@@ -7464,7 +7489,7 @@ void TestNewShaderRecompilerMubufFormatTranslation() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
|
||||
void TestNewShaderRecompilerFormattedStoreUsesRuntimeBindingSizeOnly() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeMubuf0(0x04),
|
||||
EncodeMubuf1(0, 0, 1), // buffer_store_format_x
|
||||
@@ -7491,8 +7516,9 @@ void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
Check((source.find("OpArrayLength") != std::string::npos),
|
||||
"formatted store SPIR-V lacks runtime storage-buffer bounds check");
|
||||
Check(source.find("OpULessThan") != std::string::npos &&
|
||||
source.find("OpArrayLength") == std::string::npos,
|
||||
"formatted store SPIR-V lacks a bounds check against its pushed binding size");
|
||||
Check(
|
||||
!SpirvSourceHasInstructionUsing(source, "OpULessThan", "%uint_5"),
|
||||
"formatted store SPIR-V baked descriptor NumRecords into a store guard");
|
||||
@@ -9553,6 +9579,28 @@ void TestNativeConstantSamplerSelection() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestPackedZeroHighSamplerTableKey() {
|
||||
// s_pack_ll_b32_b16 with a zero high lane zero-extends a GPU-selected table key, so the
|
||||
// sampler table stays bounded to 64Ki records.
|
||||
const uint32_t shader[] = {
|
||||
EncodeMubuf0(0x0c, 0, true), EncodeMubuf1(1, 3, 0), EncodeSopp(0x0c, 0),
|
||||
EncodeVop1(0x02, 28, 257), EncodeSop2(0x32, 29, 28, 128),
|
||||
EncodeSop2(0x1e, 30, 29, 132),
|
||||
EncodeSmem0(0x0a, 24, 4), 30u << 25u, EncodeSopp(0x0c, 0),
|
||||
EncodeMimg0(0x20, 0x1), EncodeMimg1(0, 4, 6, 2),
|
||||
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = ImageTestUserData();
|
||||
const auto translated = ShaderRecompiler::TranslateProgram(shader, options);
|
||||
const auto plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
const auto& indirect =
|
||||
plan.descriptor_sources[plan.info.samplers.at(0).source].indirect_descriptor;
|
||||
Check(indirect && indirect->table_stride == 16u && indirect->table_maximum == 0xffffu,
|
||||
"a zero-extended sampler table key lost its 16-bit bound");
|
||||
}
|
||||
|
||||
void TestBoundedMaterialBufferStores(bool scalar_key) {
|
||||
// Captured batch search and descriptor waterfall, with deformation arithmetic removed.
|
||||
const uint32_t shader[] = {
|
||||
@@ -10616,19 +10664,18 @@ void TestNewShaderRecompilerBufferLoadsGuardedByExec() {
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
const auto exec_branch =
|
||||
source.find("OpBranchConditional", 0);
|
||||
const auto array_length =
|
||||
source.find("OpArrayLength", 0);
|
||||
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
||||
const auto bounds = source.find("OpULessThan", 0);
|
||||
const auto bounds_branch = source.find("OpBranchConditional", bounds);
|
||||
const auto element_access = source.find("OpAccessChain %_ptr_StorageBuffer_uint", 0);
|
||||
Check(exec_branch != std::string::npos,
|
||||
"buffer load SPIR-V lacks EXEC guard branch");
|
||||
Check(array_length != std::string::npos,
|
||||
"buffer load SPIR-V lacks storage buffer array-length bounds check");
|
||||
Check(bounds != std::string::npos,
|
||||
"buffer load SPIR-V lacks storage buffer bounds check");
|
||||
Check(bounds_branch != std::string::npos,
|
||||
"buffer load SPIR-V lacks storage buffer bounds branch");
|
||||
Check(element_access != std::string::npos,
|
||||
"buffer load SPIR-V lacks storage element access");
|
||||
Check(exec_branch < array_length,
|
||||
Check(exec_branch < bounds,
|
||||
"buffer load bounds check was emitted outside EXEC guard");
|
||||
Check(bounds_branch < element_access,
|
||||
"buffer load storage element pointer was formed before bounds guard");
|
||||
@@ -10661,13 +10708,12 @@ void TestNewShaderRecompilerBufferAtomicsGuardedByBounds() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
const auto array_length =
|
||||
source.find("OpArrayLength", 0);
|
||||
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
||||
const auto bounds = source.find("OpULessThan", 0);
|
||||
const auto bounds_branch = source.find("OpBranchConditional", bounds);
|
||||
const auto atomic = source.find(test.spirv, 0);
|
||||
const auto memory_barrier =
|
||||
source.find("OpMemoryBarrier", atomic);
|
||||
Check(array_length != std::string::npos &&
|
||||
Check(bounds != std::string::npos &&
|
||||
bounds_branch != std::string::npos &&
|
||||
atomic != std::string::npos &&
|
||||
memory_barrier != std::string::npos,
|
||||
@@ -10716,14 +10762,13 @@ void TestCapturedBufferAtomicsX2() {
|
||||
"64-bit buffer atomic storage view does not use eight-byte elements");
|
||||
Check(CountSourceOccurrences(source, "Aliased") == 2u,
|
||||
"both storage-buffer views must declare that they alias");
|
||||
const auto array_length =
|
||||
source.find("OpArrayLength", 0);
|
||||
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
||||
const auto bounds = source.find("OpULessThan", 0);
|
||||
const auto bounds_branch = source.find("OpBranchConditional", bounds);
|
||||
const auto atomic =
|
||||
source.find(test.spirv_name, 0);
|
||||
const auto memory_barrier =
|
||||
source.find("OpMemoryBarrier", atomic);
|
||||
Check(array_length != std::string::npos &&
|
||||
Check(bounds != std::string::npos &&
|
||||
bounds_branch != std::string::npos &&
|
||||
atomic != std::string::npos && bounds_branch < atomic,
|
||||
"64-bit buffer atomic was not guarded by storage-buffer bounds");
|
||||
@@ -11002,6 +11047,18 @@ void TestMemoryFedScalarLeafCall() {
|
||||
ExpectFatal([&] { (void)PrepareShaderSource(multiple, options); },
|
||||
"a native return pair was accepted as a later call target");
|
||||
#endif
|
||||
// A lane read and a wave32 lane mask write one SGPR, so neither s13 writer defines s14.
|
||||
for (const auto& [wave_size, writer] : {std::pair{64u, 0x360u}, std::pair{32u, 0xc2u}}) {
|
||||
const uint32_t neighbor[] = {EncodeVop3Word0(writer, 13), EncodeVop3Word1(256, 128, 0),
|
||||
EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)};
|
||||
auto neighbor_options = options;
|
||||
neighbor_options.wave_size = wave_size;
|
||||
const auto neighbor_source = PrepareShaderSource(neighbor, neighbor_options);
|
||||
const auto* target = neighbor_source.calls.front().target[0].TryInstruction();
|
||||
Check(target != nullptr && target->GetOpcode() == IR::ValueOpcode::GetUserData &&
|
||||
IR::RegIndex(target->Arg(0).ScalarRegister()) == 14u,
|
||||
"a single-SGPR vector write was taken as the next SGPR's call-target definition");
|
||||
}
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerSetpcBranch() {
|
||||
@@ -11730,7 +11787,7 @@ void TestMeshInputAssembly() {
|
||||
options.user_data_count = 0;
|
||||
options.input_info.vertex = &input;
|
||||
auto program = Frontend::TranslateProgram(decoded, graph, options);
|
||||
const uint32_t draw[] = {test.count, test.base_vertex, 7, test.width,
|
||||
const uint32_t draw[] = {test.count, test.base_vertex, test.width,
|
||||
test.address_low, 0x12};
|
||||
Inst *load = nullptr;
|
||||
for (auto &inst : *program.blocks.front()) {
|
||||
@@ -11775,7 +11832,7 @@ void TestMeshInputAssembly() {
|
||||
Check(sgprs[2] == test.group_info && sgprs[3] == test.wave_info &&
|
||||
vgprs[0] == ((test.first << 2) | (test.second << 18)) &&
|
||||
vgprs[1] == test.third * 4 && vgprs[5] == test.vertex_id &&
|
||||
vgprs[test.fast_launch ? 6 : 8] == 9u,
|
||||
vgprs[test.fast_launch ? 6 : 8] == 2u,
|
||||
"mesh prolog changed input assembly, wave counts, vertex ID, or instance ID");
|
||||
}
|
||||
}
|
||||
@@ -13602,6 +13659,34 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
|
||||
}
|
||||
}
|
||||
|
||||
// The wave32 live mask projects to the entry predicate, and VCC keeps its compare predicate
|
||||
// after WQM.
|
||||
const uint32_t live_mask_shader[] = {
|
||||
EncodeSop1(0x03, 0, 126), // s_mov_b32 s0, exec_lo
|
||||
EncodeSop2(0x0e, 126, 126, 0), // s_and_b32 exec_lo, exec_lo, s0
|
||||
EncodeVop1(0x01, 0, 242), // v_mov_b32 v0, 1.0
|
||||
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
const uint32_t wqm_vcc_shader[] = {
|
||||
EncodeSop1(0x09, 126, 126), // s_wqm_b32 exec_lo, exec_lo
|
||||
EncodeVop2(0x23, 0, 193, 1), // v_mbcnt_lo_u32_b32 v0, -1, v1
|
||||
EncodeVopc(0xc1, 129, 0), // v_cmp_lt_u32 vcc_lo, 1, v0
|
||||
EncodeVop2(0x01, 2, 128, 0), // v_cndmask_b32 v2, 0, v0, vcc_lo
|
||||
EncodeExp0(0x00, 0x1), EncodeExp1(2, 2, 2, 2),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
auto wave32_pixel_options = MakeCompileOptions(ShaderType::Pixel);
|
||||
wave32_pixel_options.wave_size = 32u;
|
||||
for (const std::span<const uint32_t> shader : {std::span<const uint32_t>(live_mask_shader),
|
||||
std::span<const uint32_t>(wqm_vcc_shader)}) {
|
||||
const auto pixel_result = RecompileForTest(shader, wave32_pixel_options);
|
||||
CheckSpirvBinaryValidates(pixel_result.spirv);
|
||||
Check(DisassembleSpirvBinary(pixel_result.spirv).find("OpGroupNonUniformBallot") ==
|
||||
std::string::npos,
|
||||
"wave32 pixel mask rebuilt a known predicate from a ballot");
|
||||
}
|
||||
|
||||
const uint32_t local_shader[] = {
|
||||
EncodeVopc(0xc1, 5 + 256, 8), // v_cmp_lt_u32 vcc, v5, v8
|
||||
EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc
|
||||
@@ -13716,6 +13801,7 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
||||
|
||||
ShaderPixelInputInfo uint16_info;
|
||||
uint16_info.target_output_mode[0] = 7;
|
||||
uint16_info.target_uint_mask = 1;
|
||||
options.input_info.pixel = &uint16_info;
|
||||
auto uint16_result = RecompileForTest(shader, options);
|
||||
const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv);
|
||||
@@ -13730,6 +13816,18 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
||||
"compressed UINT16 MRT export was incorrectly decoded as FP16");
|
||||
CheckSpirvBinaryValidates(uint16_result.spirv);
|
||||
|
||||
ShaderPixelInputInfo sint16_info;
|
||||
sint16_info.target_output_mode[0] = 8;
|
||||
sint16_info.target_sint_mask = 1;
|
||||
options.input_info.pixel = &sint16_info;
|
||||
auto sint16_result = RecompileForTest(shader, options);
|
||||
const auto sint16_source = DisassembleSpirvBinary(sint16_result.spirv);
|
||||
Check(sint16_source.find("OpVariable %_ptr_Output_v4int Output") != std::string::npos &&
|
||||
CountSourceOccurrences(sint16_source, "OpBitFieldSExtract") == 4u &&
|
||||
!SpirvContainsExtInst(sint16_result.spirv, 62),
|
||||
"compressed SINT16 MRT export did not sign-extend into a signed output");
|
||||
CheckSpirvBinaryValidates(sint16_result.spirv);
|
||||
|
||||
ShaderPixelInputInfo unorm16_info;
|
||||
unorm16_info.target_output_mode[0] = 5;
|
||||
options.input_info.pixel = &unorm16_info;
|
||||
@@ -13757,6 +13855,7 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
||||
|
||||
ShaderPixelInputInfo uint_info;
|
||||
uint_info.target_output_mode[0] = 7;
|
||||
uint_info.target_uint_mask = 1;
|
||||
options.input_info.pixel = &uint_info;
|
||||
auto partial_uint_result =
|
||||
RecompileForTest(partial_shader, options);
|
||||
@@ -13783,6 +13882,15 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
||||
"compressed UINT16 BA-only export did not read and unpack VSRC1");
|
||||
CheckSpirvBinaryValidates(compressed_ba_result.spirv);
|
||||
|
||||
// 32-bit exports keep their raw bits in a UINT target's integer output.
|
||||
uint_info.target_output_mode[0] = 9;
|
||||
auto raw_uint_result = RecompileForTest(partial_shader, options);
|
||||
const auto raw_uint_source = DisassembleSpirvBinary(raw_uint_result.spirv);
|
||||
Check(raw_uint_source.find("OpVariable %_ptr_Output_v4uint Output") != std::string::npos &&
|
||||
raw_uint_source.find("OpBitcast %v4float") == std::string::npos,
|
||||
"32-bit MRT export to a UINT target did not use an unsigned integer output");
|
||||
CheckSpirvBinaryValidates(raw_uint_result.spirv);
|
||||
|
||||
unorm16_info.target_output_mode[0] = 5;
|
||||
options.input_info.pixel = &unorm16_info;
|
||||
auto unorm16_ba_result =
|
||||
@@ -16543,7 +16651,6 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
|
||||
.variables = 2,
|
||||
.loads = 9,
|
||||
.stores = 4,
|
||||
.array_lengths = 2,
|
||||
.phis = 5,
|
||||
.labels = 34,
|
||||
.selection_merges = 10,
|
||||
@@ -16744,7 +16851,7 @@ int main() {
|
||||
TestNewShaderRecompilerNativeWideBufferIr();
|
||||
TestNewShaderRecompilerScalarB64LaneTranslation();
|
||||
TestNewShaderRecompilerMubufFormatTranslation();
|
||||
TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly();
|
||||
TestNewShaderRecompilerFormattedStoreUsesRuntimeBindingSizeOnly();
|
||||
TestNewShaderRecompilerTypedBufferTranslation();
|
||||
TestNewShaderRecompilerDsReadWrite2Translation();
|
||||
TestNewShaderRecompilerDsWideAndAtomicTranslation();
|
||||
@@ -16815,6 +16922,7 @@ int main() {
|
||||
TestNativeGuardedSamplerSource(false);
|
||||
TestNativeGuardedSamplerSource(true);
|
||||
TestNativeConstantSamplerSelection();
|
||||
TestPackedZeroHighSamplerTableKey();
|
||||
for (const bool scalar_key : {false, true}) TestBoundedMaterialBufferStores(scalar_key);
|
||||
TestNativeDescriptorProvenanceKeepsGpuSelection();
|
||||
TestCfgSiblingSharedExit();
|
||||
|
||||
Reference in new issue
Block a user