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:
|
windows:
|
||||||
name: Build KytyPS5 (Windows)
|
name: Build KytyPS5 (Windows)
|
||||||
needs: metadata
|
needs: metadata
|
||||||
runs-on: windows-2022
|
runs-on: windows-2025
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct IndirectDrawRegisters {
|
|||||||
uint32_t vertex_offset;
|
uint32_t vertex_offset;
|
||||||
uint32_t instance_offset;
|
uint32_t instance_offset;
|
||||||
uint32_t index_offset;
|
uint32_t index_offset;
|
||||||
|
uint32_t draw_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Pm4Execution {
|
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.
|
// The native indexed packet enables the high-half first-index destination in word 3.
|
||||||
const IndirectDrawRegisters registers {
|
const IndirectDrawRegisters registers {
|
||||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
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);
|
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 stride_in_bytes = buffer[7];
|
||||||
const auto draw_initiator = buffer[8];
|
const auto draw_initiator = buffer[8];
|
||||||
const bool indexed = (cmd_id == 0xc0083800);
|
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 {
|
const IndirectDrawRegisters registers {
|
||||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
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) {
|
if (count_indirect == 0) {
|
||||||
count_addr = nullptr;
|
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,
|
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||||
uint32_t vertex_offset, uint32_t instance_offset,
|
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) {
|
const auto write = [&](uint32_t location, uint32_t value) {
|
||||||
if (location == Pm4::SH_NOP) {
|
if (location == Pm4::SH_NOP) {
|
||||||
return;
|
return;
|
||||||
@@ -801,6 +801,7 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
|||||||
write(registers.vertex_offset, vertex_offset);
|
write(registers.vertex_offset, vertex_offset);
|
||||||
write(registers.instance_offset, instance_offset);
|
write(registers.instance_offset, instance_offset);
|
||||||
write(registers.index_offset, first_index);
|
write(registers.index_offset, first_index);
|
||||||
|
write(registers.draw_index, draw_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
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.
|
// while they are zero.
|
||||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
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(),
|
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||||
{.index_count = m_index_buffer_size,
|
{.index_count = m_index_buffer_size,
|
||||||
.index_addr = IndexAddress(0),
|
.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;
|
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
||||||
if (!indexed) {
|
if (!indexed) {
|
||||||
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
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(),
|
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
||||||
{.vertex_count = args.vertexCount,
|
{.vertex_count = args.vertexCount,
|
||||||
.instance_count = args.instanceCount,
|
.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);
|
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
||||||
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
||||||
args.firstInstance, args.firstIndex);
|
args.firstInstance, args.firstIndex, i);
|
||||||
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||||
{.index_count = std::min(args.indexCount, max_indices),
|
{.index_count = std::min(args.indexCount, max_indices),
|
||||||
.index_addr = IndexAddress(args.firstIndex),
|
.index_addr = IndexAddress(args.firstIndex),
|
||||||
|
|||||||
+22
-1
@@ -783,7 +783,15 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
|||||||
if (!cached.registered) {
|
if (!cached.registered) {
|
||||||
return {merged_id};
|
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);
|
BcAlias::Resolve(*this, cached_id, merged_id);
|
||||||
return {merged_id};
|
return {merged_id};
|
||||||
}
|
}
|
||||||
@@ -1302,6 +1310,19 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
|||||||
view_layer = overlap.layer;
|
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) {
|
} else if (block_alias) {
|
||||||
for (const auto candidate: candidates) {
|
for (const auto candidate: candidates) {
|
||||||
const auto& owner = m_slot_images[candidate];
|
const auto& owner = m_slot_images[candidate];
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public:
|
|||||||
ImageInfo info;
|
ImageInfo info;
|
||||||
ImageViewInfo view_info;
|
ImageViewInfo view_info;
|
||||||
BindingType type = BindingType::Texture;
|
BindingType type = BindingType::Texture;
|
||||||
|
bool heap = false; // PS5: resolved for a resident image heap.
|
||||||
};
|
};
|
||||||
|
|
||||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
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 =
|
const auto destination_height =
|
||||||
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
||||||
const bool copy = source.backing.samples == 1;
|
const bool copy = source.backing.samples == 1;
|
||||||
EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
|
EXIT_IF(layers == 0 ||
|
||||||
info.extent.width > destination_width || info.extent.height > destination_height ||
|
|
||||||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
||||||
: source.backing.format != backing.format));
|
: source.backing.format != backing.format));
|
||||||
auto resolved_source_range = source_range;
|
auto resolved_source_range = source_range;
|
||||||
auto resolved_destination_range = destination_range;
|
auto resolved_destination_range = destination_range;
|
||||||
resolved_source_range.layer_count = layers;
|
resolved_source_range.layer_count = layers;
|
||||||
resolved_destination_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();
|
m_scheduler.EndRendering();
|
||||||
auto command = m_scheduler.Current().Handle();
|
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;
|
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);
|
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 float16 = [](uint32_t value) {
|
||||||
const auto sign = (value & 0x8000u) << 16u;
|
const auto sign = (value & 0x8000u) << 16u;
|
||||||
const auto exponent = (value >> 10u) & 0x1fu;
|
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);
|
next.float32[1] = float16(packed >> 16u);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break;
|
case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break;
|
||||||
case vk::Format::eR16G16Unorm:
|
case vk::Format::eR16G16B16A16Unorm:
|
||||||
next.float32[1] = static_cast<float>(packed >> 16u) / 65535.0f;
|
next.float32[2] = unorm16(clear_words >> 32u);
|
||||||
|
next.float32[3] = unorm16(clear_words >> 48u);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case vk::Format::eR16Unorm:
|
case vk::Format::eR16G16Unorm:
|
||||||
next.float32[0] = static_cast<float>(packed & 0xffffu) / 65535.0f;
|
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;
|
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:
|
case vk::Format::eR32G32Sfloat:
|
||||||
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
|
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::eR32G32Uint:
|
||||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
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)
|
// 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;
|
// 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.
|
// 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);
|
view_levels, desc.info.resources.layers);
|
||||||
desc.view_info.base_level = view_base;
|
desc.view_info.base_level = view_base;
|
||||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
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);
|
auto* image = &texture_cache.GetImage(id);
|
||||||
const bool stencil_association = static_cast<bool>(image->depth_id);
|
const bool stencil_association = static_cast<bool>(image->depth_id);
|
||||||
if (stencil_association) {
|
if (stencil_association) {
|
||||||
@@ -801,6 +806,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
|||||||
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
||||||
resource, buffer_offset);
|
resource, buffer_offset);
|
||||||
pack_memory_offset(index, 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) {
|
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] != 0) {
|
||||||
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "kernel/memory.h"
|
#include "kernel/memory.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <bit>
|
||||||
#include <xxhash.h>
|
#include <xxhash.h>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
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);
|
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.
|
// 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) {
|
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);
|
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) {
|
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto memory = std::ranges::find_if(m_memories, [&](const auto& candidate) {
|
auto memory = std::ranges::find_if(
|
||||||
return candidate->base == request.base && candidate->size == size;
|
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()) {
|
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||||
if (!m_context.IsMapped(request.base, size)) return false;
|
if (!m_context.IsMapped(request.base, size)) return false;
|
||||||
auto created = std::make_unique<Memory>();
|
if (memory == m_memories.end()) {
|
||||||
created->base = request.base;
|
std::scoped_lock lock {m_mutex};
|
||||||
created->size = size;
|
memory = m_memories.insert(m_memories.end(),
|
||||||
created->words.resize(size / 4u);
|
std::make_unique<Memory>(Memory {.base = request.base}));
|
||||||
created->dead.resize(size / 4u);
|
}
|
||||||
created->dirty_pages.assign((Common::AlignUp(request.base + size, TRACKER_PAGE_SIZE) -
|
Resize(**memory, 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));
|
|
||||||
}
|
}
|
||||||
// A draw refreshes a table and a view takes its changes once, so all programs of the draw see
|
// 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.
|
// one snapshot and one set of kinds.
|
||||||
@@ -146,27 +156,28 @@ bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView&
|
|||||||
(*memory)->draw = m_draw;
|
(*memory)->draw = m_draw;
|
||||||
}
|
}
|
||||||
auto& views = (*memory)->views;
|
auto& views = (*memory)->views;
|
||||||
|
const auto bytes = (*memory)->size;
|
||||||
const auto profile = IR::ImageHeapResource(*request.root, 0u);
|
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) {
|
auto view = std::ranges::find_if(views, [&](const auto& candidate) {
|
||||||
return candidate->step == request.step && candidate->first == request.first &&
|
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()) {
|
if (view == views.end()) {
|
||||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
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,
|
auto created = std::make_unique<View>(View {.memory = **memory,
|
||||||
.step = request.step,
|
.step = request.step,
|
||||||
.first = request.first,
|
.first = request.first,
|
||||||
.count = request.count,
|
.count = count,
|
||||||
|
.limit = limit,
|
||||||
.profile = profile,
|
.profile = profile,
|
||||||
.resolve_epoch = epoch,
|
.resolve_epoch = epoch,
|
||||||
.layout_epoch = epoch});
|
.layout_epoch = epoch});
|
||||||
created->slots.resize(size / 4u + 1u);
|
created->slots.resize(bytes / 4u + 1u);
|
||||||
created->buffer = std::make_unique<Buffer>(
|
created->buffer = SlotBuffer(m_context, bytes);
|
||||||
m_context.GetGraphics(), m_context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
UpdateProbes(*created, 0u, bytes);
|
||||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
|
||||||
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
|
||||||
size + 4u);
|
|
||||||
UpdateProbes(*created, 0u, size);
|
|
||||||
// The first upload also clears the slots no probe selects.
|
// The first upload also clears the slots no probe selects.
|
||||||
created->dirty_begin = 0;
|
created->dirty_begin = 0;
|
||||||
created->dirty_end = created->slots.size();
|
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) {
|
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||||
std::scoped_lock lock {m_mutex};
|
std::scoped_lock lock {m_mutex};
|
||||||
for (auto& memory: m_memories) {
|
for (auto& memory: m_memories) {
|
||||||
const auto begin = std::max(address, memory->base);
|
// Watches cover whole pages, so a write next to the table must unwatch its page too.
|
||||||
const auto end = std::min(address + size, memory->base + memory->size);
|
|
||||||
if (begin >= end) continue;
|
|
||||||
const auto origin = Common::AlignDown(memory->base, TRACKER_PAGE_SIZE);
|
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,
|
MarkDirty(*memory, (begin - origin) / TRACKER_PAGE_SIZE,
|
||||||
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
||||||
}
|
}
|
||||||
@@ -381,15 +394,56 @@ uint32_t ImageHeap::Allocate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImageHeap::Retire(View& view) {
|
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();
|
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
||||||
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
||||||
view.released.clear();
|
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) {
|
void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
auto& textures = m_context.GetTextureCache();
|
auto& textures = m_context.GetTextureCache();
|
||||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
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) {
|
for (auto* view: m_active) {
|
||||||
bool stale = false;
|
bool stale = false;
|
||||||
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
||||||
@@ -418,6 +472,19 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
|||||||
mark(index);
|
mark(index);
|
||||||
return true;
|
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) {
|
if (stale) {
|
||||||
// A changed image needs a new element; pending draws still read the old one. The new
|
// 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.
|
// 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->dcc.push_back(index);
|
||||||
}
|
}
|
||||||
view->away.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_elements[index].binding = std::move(binding);
|
||||||
m_writes.push_back(index);
|
m_writes.push_back(index);
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ class RenderExecutor;
|
|||||||
struct ImageHeapTestAccess;
|
struct ImageHeapTestAccess;
|
||||||
|
|
||||||
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
// 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
|
// sampled-image array. A table is keyed by its base and follows its descriptor's size. Each view
|
||||||
// select an array element. CPU writes to a table fault in its snapshot, which each draw refreshes
|
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||||
// once; a view takes the changes when a draw first acquires it. Changes to the images behind
|
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||||
// elements bump Image::heap_epoch.
|
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||||
|
// Image::heap_epoch.
|
||||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||||
public:
|
public:
|
||||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||||
@@ -70,6 +71,7 @@ private:
|
|||||||
uint64_t step = 0;
|
uint64_t step = 0;
|
||||||
uint64_t first = 0;
|
uint64_t first = 0;
|
||||||
uint64_t count = 0;
|
uint64_t count = 0;
|
||||||
|
uint64_t limit = 0; // Probes a key domain allows, if fewer.
|
||||||
ShaderRecompiler::IR::ImageResource profile;
|
ShaderRecompiler::IR::ImageResource profile;
|
||||||
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
||||||
std::vector<uint32_t> kind_keys;
|
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> 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> away; // Not yet moved to the read layout.
|
||||||
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
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;
|
std::unique_ptr<Buffer> buffer;
|
||||||
size_t dirty_begin = 0; // Slots to upload.
|
size_t dirty_begin = 0; // Slots to upload.
|
||||||
size_t dirty_end = 0;
|
size_t dirty_end = 0;
|
||||||
@@ -86,6 +89,7 @@ private:
|
|||||||
size_t changed_end = 0;
|
size_t changed_end = 0;
|
||||||
uint64_t resolve_epoch = 0;
|
uint64_t resolve_epoch = 0;
|
||||||
uint64_t layout_epoch = 0;
|
uint64_t layout_epoch = 0;
|
||||||
|
uint64_t held_serial = 0; // Image::serials when held was last retried.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Memory {
|
struct Memory {
|
||||||
@@ -113,6 +117,7 @@ private:
|
|||||||
Image* ImageOf(const Element& element) const;
|
Image* ImageOf(const Element& element) const;
|
||||||
uint32_t Allocate();
|
uint32_t Allocate();
|
||||||
void Retire(View& view);
|
void Retire(View& view);
|
||||||
|
void Resize(Memory& memory, uint64_t size);
|
||||||
void WriteDescriptors();
|
void WriteDescriptors();
|
||||||
|
|
||||||
RenderContext& m_context;
|
RenderContext& m_context;
|
||||||
|
|||||||
@@ -711,8 +711,13 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
|||||||
mesh.max_vertices > limits.maxMeshOutputVertices ||
|
mesh.max_vertices > limits.maxMeshOutputVertices ||
|
||||||
mesh.max_primitives > limits.maxMeshOutputPrimitives ||
|
mesh.max_primitives > limits.maxMeshOutputPrimitives ||
|
||||||
mesh.lds_size_dwords * sizeof(uint32_t) > limits.maxMeshSharedMemorySize) {
|
mesh.lds_size_dwords * sizeof(uint32_t) > limits.maxMeshSharedMemorySize) {
|
||||||
EXIT("mesh shader exceeds host limits: threads=%u vertices=%u primitives=%u LDS=%u\n",
|
static std::atomic_bool warned = false;
|
||||||
host_threads, mesh.max_vertices, mesh.max_primitives, mesh.lds_size_dwords);
|
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;
|
ShaderParams pixel_params;
|
||||||
@@ -736,6 +741,11 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
|||||||
const auto& rt = context.GetRenderTarget(slot);
|
const auto& rt = context.GetRenderTarget(slot);
|
||||||
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
|
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
|
||||||
if (rt.base.addr == 0 || target_mask == 0u) continue;
|
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);
|
const auto format = Prospero::ResolveRenderTargetFormat(rt.info.format, rt.info.channel_type);
|
||||||
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
|
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
|
||||||
const auto mapping = pixel_info.target_export_mapping[export_index];
|
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) ||
|
(blend.separate_alpha_blend && (BlendFactorIsDualSource(blend.alpha_srcblend) ||
|
||||||
BlendFactorIsDualSource(blend.alpha_destblend))));
|
BlendFactorIsDualSource(blend.alpha_destblend))));
|
||||||
if (pixel_info.dual_source_blending) {
|
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_output_mode[1] = pixel_info.target_output_mode[0];
|
||||||
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[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 &&
|
} else if (blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||||
pixel_info.target_output_mode[0] != 0 && pixel_info.target_output_mode[0] != 7 &&
|
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,
|
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");
|
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||||
}
|
}
|
||||||
m_context.GetPipelineCache().GetGraphicsPrograms(buffer, state);
|
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()) {
|
if (draw.IsIndexed()) {
|
||||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||||
}
|
}
|
||||||
@@ -1156,7 +1161,6 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
|||||||
if (mesh_active) {
|
if (mesh_active) {
|
||||||
draw_data = {draw.index_count,
|
draw_data = {draw.index_count,
|
||||||
static_cast<uint32_t>(emit.vertex_offset),
|
static_cast<uint32_t>(emit.vertex_offset),
|
||||||
emit.first_instance,
|
|
||||||
index_source.guest_element_size,
|
index_source.guest_element_size,
|
||||||
static_cast<uint32_t>(index_source.address),
|
static_cast<uint32_t>(index_source.address),
|
||||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
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);
|
cache.MarkGpuWritten(dst.image_id);
|
||||||
auto& source = cache.GetImage(src.image_id);
|
auto& source = cache.GetImage(src.image_id);
|
||||||
auto& destination = cache.GetImage(dst.image_id);
|
auto& destination = cache.GetImage(dst.image_id);
|
||||||
destination.Resolve(source, {src.guest_mip_level, 1, src.guest_array_layer, 1},
|
destination.Resolve(source,
|
||||||
{dst.guest_mip_level, 1, dst.guest_array_layer, 1});
|
{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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
|||||||
|
|
||||||
check_feature(device_features2.features.shaderImageGatherExtended,
|
check_feature(device_features2.features.shaderImageGatherExtended,
|
||||||
"shaderImageGatherExtended");
|
"shaderImageGatherExtended");
|
||||||
|
check_feature(device_features2.features.shaderResourceMinLod, "shaderResourceMinLod");
|
||||||
|
|
||||||
check_feature(device_features2.features.independentBlend, "independentBlend");
|
check_feature(device_features2.features.independentBlend, "independentBlend");
|
||||||
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
||||||
@@ -636,6 +637,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
|||||||
device_features.shaderStorageImageMultisample =
|
device_features.shaderStorageImageMultisample =
|
||||||
supported_features2.features.shaderStorageImageMultisample;
|
supported_features2.features.shaderStorageImageMultisample;
|
||||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||||
|
device_features.shaderResourceMinLod = VK_TRUE;
|
||||||
device_features.independentBlend = VK_TRUE;
|
device_features.independentBlend = VK_TRUE;
|
||||||
device_features.dualSrcBlend = VK_TRUE;
|
device_features.dualSrcBlend = VK_TRUE;
|
||||||
device_features.tessellationShader = 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);
|
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
||||||
create_info.pNext = &conditional_rendering;
|
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.pQueueCreateInfos = &queue_create_info;
|
||||||
create_info.queueCreateInfoCount = 1;
|
create_info.queueCreateInfoCount = 1;
|
||||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
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_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
||||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_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)) {
|
if (HasExtension(available_extensions, extension)) {
|
||||||
device_extensions.push_back(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) {
|
void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||||
using namespace IR;
|
using namespace IR;
|
||||||
EXIT_NOT_IMPLEMENTED(options.stage != ShaderType::Compute);
|
EXIT_NOT_IMPLEMENTED(options.stage != ShaderType::Compute);
|
||||||
|
source.wave_size = options.wave_size;
|
||||||
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
||||||
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
||||||
source.calls.push_back({.instruction = i});
|
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 {
|
Register = [&](uint32_t before, uint32_t code) -> Value {
|
||||||
std::vector<uint32_t> crossed_calls;
|
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) {
|
for (const auto crossed: crossed_calls) {
|
||||||
std::ranges::find(source.calls, crossed, &ShaderSource::Call::instruction)->preserved_registers.set(code);
|
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);
|
EXIT_IF(function_pc > UINT32_MAX - function_size);
|
||||||
const auto function_offset = static_cast<uint32_t>(linked.code.size());
|
const auto function_offset = static_cast<uint32_t>(linked.code.size());
|
||||||
for (auto inst: function) {
|
for (auto inst: function) {
|
||||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg) ||
|
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg, source.wave_size) ||
|
||||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u));
|
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u, source.wave_size));
|
||||||
for (uint32_t code = 0; code < site.preserved_registers.size(); ++code) {
|
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);
|
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);
|
linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count);
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ struct ShaderSource {
|
|||||||
IR::ResourcePlan call_targets;
|
IR::ResourcePlan call_targets;
|
||||||
std::vector<Call> calls;
|
std::vector<Call> calls;
|
||||||
uint32_t unsupported_call_pc = UINT32_MAX;
|
uint32_t unsupported_call_pc = UINT32_MAX;
|
||||||
|
uint32_t wave_size = 64;
|
||||||
std::optional<Linked> linked;
|
std::optional<Linked> linked;
|
||||||
uint64_t revision = 0;
|
uint64_t revision = 0;
|
||||||
std::vector<std::pair<uint64_t, uint64_t>> reads;
|
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);
|
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) {
|
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
||||||
return state.per_vertex_variable;
|
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,
|
uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp,
|
||||||
bool uint_output) {
|
uint32_t vector_type) {
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
if (exp.compr && !uint_output) {
|
const bool integer_output = vector_type != TypeF32Vector(state, 4);
|
||||||
|
if (exp.compr && !integer_output) {
|
||||||
auto unpack = GLSLstd450UnpackHalf2x16;
|
auto unpack = GLSLstd450UnpackHalf2x16;
|
||||||
switch (MrtOutputMode(state, exp)) {
|
switch (MrtOutputMode(state, exp)) {
|
||||||
case 5: unpack = GLSLstd450UnpackUnorm2x16; break;
|
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, 0),
|
||||||
ConstantU32(state, 0),
|
ConstantU32(state, 0),
|
||||||
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
|
ConstantU32(state, integer_output ? 1u : 0x3f800000u),
|
||||||
};
|
};
|
||||||
if (exp.compr) {
|
if (exp.compr) {
|
||||||
|
const auto extract = vector_type == TypeU32Vector(state, 4) ? spv::OpBitFieldUExtract
|
||||||
|
: spv::OpBitFieldSExtract;
|
||||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||||
continue;
|
continue;
|
||||||
@@ -344,8 +347,8 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
raw[component] = state.builder.AllocateId();
|
raw[component] = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
state.builder.AddFunction(extract, TypeU32(state), raw[component], packed,
|
||||||
packed, ConstantU32(state, lane * 16u),
|
ConstantU32(state, lane * 16u),
|
||||||
ConstantU32(state, 16));
|
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,
|
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), data,
|
||||||
raw[0], raw[1], raw[2], raw[3]);
|
raw[0], raw[1], raw[2], raw[3]);
|
||||||
}
|
}
|
||||||
if (uint_output) {
|
if (vector_type == TypeU32Vector(state, 4)) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
const auto vector = state.builder.AllocateId();
|
return Unary(state, spv::OpBitcast, vector_type, data);
|
||||||
state.builder.AddFunction(spv::OpBitcast, TypeF32Vector(state, 4), vector, data);
|
|
||||||
return vector;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t DistancePointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
|
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) {
|
if (state.program.stage != ShaderType::Mesh && variable == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool uint_output = MrtOutputMode(state, exp) == 7u;
|
const auto vector_type = MrtOutputMode(state, exp) != 0u ? MrtOutputType(state, exp.index)
|
||||||
const auto vector_type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
: TypeF32Vector(state, 4);
|
||||||
auto value = ExportVector(ctx, data, exp, uint_output);
|
auto value = ExportVector(ctx, data, exp, vector_type);
|
||||||
if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
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) {
|
state.input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None) {
|
||||||
// Broadcast logical alpha before swizzling the primary output.
|
// Broadcast logical alpha before swizzling the primary output.
|
||||||
const auto blend_output =
|
const auto blend_output =
|
||||||
@@ -711,9 +712,16 @@ void EmitBarrier(EmitterState& state) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
||||||
const auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
||||||
? spv::MemorySemanticsUniformMemoryMask
|
? spv::MemorySemanticsUniformMemoryMask
|
||||||
: spv::MemorySemanticsWorkgroupMemoryMask;
|
: 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
|
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
|
||||||
: spv::MemorySemanticsAcquireReleaseMask | memory;
|
: spv::MemorySemanticsAcquireReleaseMask | memory;
|
||||||
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
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);
|
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) {
|
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||||
const auto lane = ctx.FirstLane(ballot);
|
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));
|
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& state = ctx.state;
|
||||||
auto value = ctx.Arg(inst, 0);
|
auto value = ctx.Arg(inst, 0);
|
||||||
if (ctx.other_half != nullptr) {
|
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();
|
const auto result = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(spv::OpGroupNonUniformUMin, TypeU32(state), result,
|
state.builder.AddFunction(Reduce, TypeU32(state), result,
|
||||||
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce, value);
|
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce,
|
||||||
|
value);
|
||||||
return result;
|
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) {
|
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
const auto hit = state.builder.AllocateId();
|
const auto hit = state.builder.AllocateId();
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
|||||||
layout.coord = cursor;
|
layout.coord = cursor;
|
||||||
cursor += info.coordinate_components;
|
cursor += info.coordinate_components;
|
||||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
||||||
|
if (HasFlag(mem, Decoder::ImageSampleFlagLodClamp)) layout.clamp = cursor++;
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1069,7 +1070,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
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;
|
size_t operand_count = 0;
|
||||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||||
operands[0] = spv::ImageOperandsGradMask;
|
operands[0] = spv::ImageOperandsGradMask;
|
||||||
@@ -1081,6 +1087,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||||
lod = AddressF32(ctx, mem, *address, layout.lod);
|
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[0] = spv::ImageOperandsLodMask;
|
||||||
operands[1] = lod;
|
operands[1] = lod;
|
||||||
operand_count = 2;
|
operand_count = 2;
|
||||||
@@ -1104,6 +1115,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
sample_operands[sample_operand_count++] = PackedOffset(
|
sample_operands[sample_operand_count++] = PackedOffset(
|
||||||
ctx, offset_value, candidate.dimension, sample_operands[0]);
|
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 =
|
const auto coord =
|
||||||
CoordF32(ctx, mem, *address, layout.coord,
|
CoordF32(ctx, mem, *address, layout.coord,
|
||||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
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 EmitWqmU64(EmitterState& state, uint32_t value);
|
||||||
uint32_t EmitLaneId(EmitterState& state);
|
uint32_t EmitLaneId(EmitterState& state);
|
||||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||||
|
uint32_t EmitLiveMask(EmitterState& state);
|
||||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitReadFirstLane(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 EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitSubgroupUMinU32(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 EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitGetAttribute(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;
|
uint32_t lane_half = 0;
|
||||||
std::array<BufferDefinition, 4> storage_buffers {};
|
std::array<BufferDefinition, 4> storage_buffers {};
|
||||||
std::vector<uint32_t> memory_byte_offsets;
|
std::vector<uint32_t> memory_byte_offsets;
|
||||||
|
std::vector<uint32_t> memory_byte_sizes;
|
||||||
uint32_t bda_pagetable_variable = 0;
|
uint32_t bda_pagetable_variable = 0;
|
||||||
uint32_t fault_buffer_variable = 0;
|
uint32_t fault_buffer_variable = 0;
|
||||||
uint32_t bda_pointer_function = 0;
|
uint32_t bda_pointer_function = 0;
|
||||||
@@ -145,6 +146,7 @@ struct EmitterState {
|
|||||||
const IR::Block* current_block = nullptr;
|
const IR::Block* current_block = nullptr;
|
||||||
uint32_t pixel_valid_mask_variable = 0;
|
uint32_t pixel_valid_mask_variable = 0;
|
||||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||||
|
uint32_t helper_invocation_variable = 0;
|
||||||
uint32_t per_vertex_variable = 0;
|
uint32_t per_vertex_variable = 0;
|
||||||
uint32_t point_size_variable = 0;
|
uint32_t point_size_variable = 0;
|
||||||
uint32_t clip_distance_variable = 0;
|
uint32_t clip_distance_variable = 0;
|
||||||
@@ -282,6 +284,7 @@ struct ImageSampleLayout {
|
|||||||
uint32_t bias = NoImageComponent;
|
uint32_t bias = NoImageComponent;
|
||||||
uint32_t coord = 0;
|
uint32_t coord = 0;
|
||||||
uint32_t lod = NoImageComponent;
|
uint32_t lod = NoImageComponent;
|
||||||
|
uint32_t clamp = NoImageComponent;
|
||||||
uint32_t grad_x = NoImageComponent;
|
uint32_t grad_x = NoImageComponent;
|
||||||
uint32_t grad_y = 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 OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
|
||||||
|
uint32_t MrtOutputType(EmitterState& state, uint32_t index);
|
||||||
|
|
||||||
uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
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);
|
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
|
||||||
|
|
||||||
void EmitMemoryOffsets(EmitterState& state);
|
void EmitMemoryBindings(EmitterState& state);
|
||||||
|
|
||||||
uint32_t LdsDwordCount(const EmitterState& state);
|
uint32_t LdsDwordCount(const EmitterState& state);
|
||||||
void EnsureLdsStorage(EmitterState& state);
|
void EnsureLdsStorage(EmitterState& state);
|
||||||
|
|||||||
@@ -1541,16 +1541,16 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
const auto index =
|
const auto index =
|
||||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||||
const auto exec = ctx.Arg(inst, 1);
|
// Elect and count from the guest EXEC words, which V_MBCNT also ranks lanes by.
|
||||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
const auto low = ctx.Arg(inst, 1);
|
||||||
const auto low = state.builder.AllocateId();
|
const auto high = ctx.Arg(inst, 2);
|
||||||
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);
|
|
||||||
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||||
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
||||||
Unary(state, spv::OpBitCount, TypeU32(state), high));
|
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 =
|
const auto source_lane =
|
||||||
state.lane_count == 2
|
state.lane_count == 2
|
||||||
? Binary(state, spv::OpBitwiseAnd, TypeU32(state), first, ConstantU32(state, 31))
|
? 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(
|
const auto condition = AndCondition(
|
||||||
state, is_first,
|
state, is_first,
|
||||||
AndCondition(state,
|
AndCondition(state,
|
||||||
state.lane_count == 2 ? Binary(state, spv::OpINotEqual, TypeBool(state), count,
|
Binary(state, spv::OpINotEqual, TypeBool(state), count, ConstantU32(state, 0)),
|
||||||
ConstantU32(state, 0))
|
|
||||||
: exec,
|
|
||||||
bounds));
|
bounds));
|
||||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||||
const auto value = state.builder.AllocateId();
|
const auto value = state.builder.AllocateId();
|
||||||
|
|||||||
@@ -154,9 +154,10 @@ uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resour
|
|||||||
return representative;
|
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)];
|
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
|
||||||
state.memory_byte_offsets.resize(count);
|
state.memory_byte_offsets.resize(count);
|
||||||
|
state.memory_byte_sizes.resize(count);
|
||||||
std::vector<bool> dynamic_offsets(count);
|
std::vector<bool> dynamic_offsets(count);
|
||||||
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
||||||
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
||||||
@@ -174,6 +175,8 @@ void EmitMemoryOffsets(EmitterState& state) {
|
|||||||
if (dynamic_offsets[i + lane]) continue;
|
if (dynamic_offsets[i + lane]) continue;
|
||||||
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||||
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
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::CapabilityShaderNonUniform);
|
||||||
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
||||||
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
||||||
const auto offsets_start = state.program.bindings.memory_offset_dword +
|
const auto& bindings = state.program.bindings;
|
||||||
(state.program.bindings.UsesPushData() ? state.program.bindings.push_data_start_dword : 0u);
|
const auto data_start = bindings.UsesPushData() ? bindings.push_data_start_dword : 0u;
|
||||||
const auto word = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
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))));
|
Binary(state, spv::OpShiftRightLogical, TypeU32(state), array_index, ConstantU32(state, 2))));
|
||||||
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
||||||
ConstantU32(state, 3));
|
ConstantU32(state, 3));
|
||||||
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||||
state, word, shift, ConstantU32(state, 8));
|
state, word, shift, ConstantU32(state, 8));
|
||||||
|
access.length = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||||
|
state, ConstantU32(state, bindings.MemorySizeDword() + data_start), array_index));
|
||||||
} else {
|
} else {
|
||||||
access.byte_offset = state.memory_byte_offsets[static_index];
|
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;
|
return access;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -595,6 +595,13 @@ void DefineInputs(EmitterState& state) {
|
|||||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationFlat);
|
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) {
|
void DefineOutputs(EmitterState& state) {
|
||||||
@@ -691,13 +698,15 @@ void DefineOutputs(EmitterState& state) {
|
|||||||
break;
|
break;
|
||||||
case IR::StageOutputKind::Parameter:
|
case IR::StageOutputKind::Parameter:
|
||||||
case IR::StageOutputKind::Mrt: {
|
case IR::StageOutputKind::Mrt: {
|
||||||
const bool uint_output =
|
auto type = TypeF32Vector(state, 4);
|
||||||
binding.kind == IR::StageOutputKind::Mrt &&
|
if (binding.kind == IR::StageOutputKind::Mrt &&
|
||||||
state.program.stage == ShaderType::Pixel &&
|
state.program.stage == ShaderType::Pixel && binding.index < 8u) {
|
||||||
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
|
// Packed UNorm stores its encoded bits.
|
||||||
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
|
const auto bit = 1u << binding.index;
|
||||||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
|
type = (state.input_info.pixel->target_packed_unorm_mask & bit) != 0u
|
||||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
? TypeU32Vector(state, 4)
|
||||||
|
: MrtOutputType(state, binding.index);
|
||||||
|
}
|
||||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||||
binding.debug_name.c_str());
|
binding.debug_name.c_str());
|
||||||
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
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();
|
const auto op = inst.GetOpcode();
|
||||||
if (ctx.half != 0 &&
|
if (ctx.half != 0 &&
|
||||||
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
|
(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))) {
|
(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.
|
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
|
||||||
ctx.Define(inst, ctx.other_half->Result(inst));
|
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,
|
state.lds_base_dwords = EmitBinaryU32(state, spv::OpIMul, index,
|
||||||
ConstantU32(state, LdsDwordCount(state)));
|
ConstantU32(state, LdsDwordCount(state)));
|
||||||
}
|
}
|
||||||
EmitMemoryOffsets(state);
|
EmitMemoryBindings(state);
|
||||||
if (program.blocks.empty()) {
|
if (program.blocks.empty()) {
|
||||||
EmitReturn(ctx);
|
EmitReturn(ctx);
|
||||||
} else if (state.program.dispatcher_fallback) {
|
} else if (state.program.dispatcher_fallback) {
|
||||||
|
|||||||
@@ -1710,21 +1710,25 @@ Graph Structurize(const Graph& graph) {
|
|||||||
return GotoStructurizer(graph).Run();
|
return GotoStructurizer(graph).Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code) {
|
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size) {
|
||||||
// Vector comparisons and carry results can write a scalar pair in wave64.
|
// 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 ||
|
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::VOPC || inst.family == Decoder::Family::VOP3 ||
|
||||||
inst.family == Decoder::Family::VOP3P) {
|
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;
|
uint32_t destination = 0;
|
||||||
for (const auto* operand: {&inst.dst, &inst.dst2})
|
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 true;
|
||||||
}
|
}
|
||||||
return InstructionWritesScalarCode(inst, code);
|
return InstructionWritesScalarCode(inst, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
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());
|
EXIT_IF(before >= program.instructions.size());
|
||||||
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
||||||
return block.inst_begin <= before && before < block.inst_end;
|
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;) {
|
for (uint32_t i = point.second; i > block->inst_begin;) {
|
||||||
const auto& inst = program.instructions[--i];
|
const auto& inst = program.instructions[--i];
|
||||||
if (inst.opcode == Opcode::S_SWAPPC_B64) {
|
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);
|
crossed_calls.push_back(i);
|
||||||
continue;
|
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) {
|
if (index != UINT32_MAX || block->id == graph.entry_block) {
|
||||||
EXIT_IF(definition && *definition != index);
|
EXIT_IF(definition && *definition != index);
|
||||||
|
|||||||
@@ -113,9 +113,10 @@ struct Graph {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Graph BuildGraph(const Decoder::Program& program);
|
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 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.
|
// Returns structured control flow or failure diagnostics without changing the native graph.
|
||||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||||
Graph Structurize(const Graph& graph);
|
Graph Structurize(const Graph& graph);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension d
|
|||||||
if ((flags & ImageSampleFlagBias) != 0) {
|
if ((flags & ImageSampleFlagBias) != 0) {
|
||||||
components++;
|
components++;
|
||||||
}
|
}
|
||||||
if ((flags & ImageSampleFlagLod) != 0) {
|
if ((flags & (ImageSampleFlagLod | ImageSampleFlagLodClamp)) != 0) {
|
||||||
components++;
|
components++;
|
||||||
}
|
}
|
||||||
if ((flags & ImageSampleFlagDerivative) != 0) {
|
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 126u: operand.kind = OperandKind::ExecLo; return;
|
||||||
case 127u: operand.kind = OperandKind::ExecHi; return;
|
case 127u: operand.kind = OperandKind::ExecHi; return;
|
||||||
case 235u: operand.kind = OperandKind::SharedBase; return;
|
case 235u: operand.kind = OperandKind::SharedBase; return;
|
||||||
|
case 236u: operand.kind = OperandKind::SharedLimit; return;
|
||||||
case 237u: operand.kind = OperandKind::PrivateBase; return;
|
case 237u: operand.kind = OperandKind::PrivateBase; return;
|
||||||
|
case 238u: operand.kind = OperandKind::PrivateLimit; return;
|
||||||
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
|
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
|
||||||
case 248u:
|
case 248u:
|
||||||
operand.kind = OperandKind::FloatInlineConstant;
|
operand.kind = OperandKind::FloatInlineConstant;
|
||||||
@@ -459,7 +461,9 @@ std::string OperandToString(const Operand& operand) {
|
|||||||
case OperandKind::M0: text = "m0"; break;
|
case OperandKind::M0: text = "m0"; break;
|
||||||
case OperandKind::PopsExitingWaveId: text = "pops_exiting_wave_id"; break;
|
case OperandKind::PopsExitingWaveId: text = "pops_exiting_wave_id"; break;
|
||||||
case OperandKind::SharedBase: text = "shared_base"; break;
|
case OperandKind::SharedBase: text = "shared_base"; break;
|
||||||
|
case OperandKind::SharedLimit: text = "shared_limit"; break;
|
||||||
case OperandKind::PrivateBase: text = "private_base"; break;
|
case OperandKind::PrivateBase: text = "private_base"; break;
|
||||||
|
case OperandKind::PrivateLimit: text = "private_limit"; break;
|
||||||
case OperandKind::Null: text = "null"; break;
|
case OperandKind::Null: text = "null"; break;
|
||||||
default: text = "unknown"; break;
|
default: text = "unknown"; break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -766,7 +766,9 @@ enum class OperandKind {
|
|||||||
M0,
|
M0,
|
||||||
PopsExitingWaveId,
|
PopsExitingWaveId,
|
||||||
SharedBase,
|
SharedBase,
|
||||||
|
SharedLimit,
|
||||||
PrivateBase,
|
PrivateBase,
|
||||||
|
PrivateLimit,
|
||||||
Null,
|
Null,
|
||||||
Vgpr,
|
Vgpr,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,30 +5,20 @@
|
|||||||
|
|
||||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||||
|
|
||||||
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar,
|
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar) {
|
||||||
bool cmpx) {
|
|
||||||
if (scalar) {
|
if (scalar) {
|
||||||
ir.SetScc(value);
|
ir.SetScc(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto masked = ir.LogicalAnd(ir.GetExec(), value);
|
WriteVectorMask(inst.dst, value);
|
||||||
if (cmpx) {
|
|
||||||
const auto mask = BallotMask(masked);
|
|
||||||
ir.SetExec(masked);
|
|
||||||
ir.SetExecLo(mask[0]);
|
|
||||||
ir.SetExecHi(mask[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
WriteMask(inst.dst, masked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar,
|
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar) {
|
||||||
bool cmpx) {
|
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar);
|
||||||
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar, cmpx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
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.
|
// The equality compares share their IR opcode with the unsigned ones.
|
||||||
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
||||||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
||||||
@@ -49,18 +39,18 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
|
|||||||
if (swap_operands) {
|
if (swap_operands) {
|
||||||
std::swap(lhs, rhs);
|
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,
|
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 lhs = ReadU16AsU32(inst.src0, signed_value);
|
||||||
const auto rhs = ReadU16AsU32(inst.src1, 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,
|
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 type = IR::ArgTypeOf(opcode, 0);
|
||||||
const auto lhs =
|
const auto lhs =
|
||||||
half ? IR::Value(ReadF16AsF32(inst.src0)) : ReadOperand(inst.src0, type);
|
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);
|
half ? IR::Value(ReadF16AsF32(inst.src1)) : ReadOperand(inst.src1, type);
|
||||||
const IR::FPCompareFlags flags{
|
const IR::FPCompareFlags flags{
|
||||||
.flush_input_denorms = !half && type == IR::Type::F32 && flush_f32_inputs};
|
.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) {
|
bool is64) {
|
||||||
const auto type = is64 ? IR::Type::F64 : IR::Type::F32;
|
const auto type = is64 ? IR::Type::F64 : IR::Type::F32;
|
||||||
const auto is_nan_op = is64 ? IR::ValueOpcode::FPIsNan64 : IR::ValueOpcode::FPIsNan32;
|
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 rhs = ReadOperand(inst.src1, type);
|
||||||
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(is_nan_op, {lhs})),
|
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(is_nan_op, {lhs})),
|
||||||
IR::U1(ir.Emit(is_nan_op, {rhs})));
|
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))
|
const auto value = half ? IR::Value(Read16LaneBits(inst.src0, false))
|
||||||
: ReadOperand(inst.src0, IR::Type::F32);
|
: ReadOperand(inst.src0, IR::Type::F32);
|
||||||
const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false))
|
const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false))
|
||||||
: ReadOperand(inst.src1, IR::Type::U32);
|
: ReadOperand(inst.src1, IR::Type::U32);
|
||||||
const auto opcode = half ? IR::ValueOpcode::FPCmpClass16 : IR::ValueOpcode::FPCmpClass32;
|
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
|
} // 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,
|
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
|
||||||
bool negate_exec, bool negate_source, bool write_64) {
|
bool negate_exec, bool negate_source, bool write_64) {
|
||||||
if (!write_64) {
|
const auto combine = [&](IR::U32 old, IR::U32 src) {
|
||||||
// Read the encoded scalar word and preserve EXEC_HI, including in wave32.
|
|
||||||
const auto old = ir.GetExecLo();
|
|
||||||
const auto src = ReadU32(inst.src0);
|
|
||||||
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
|
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
|
||||||
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
|
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
|
||||||
IR::U32 result;
|
return operation == IR::ValueOpcode::LogicalAnd ? ir.BitwiseAnd(lhs, rhs)
|
||||||
switch (operation) {
|
: ir.BitwiseOr(lhs, rhs);
|
||||||
case IR::ValueOpcode::LogicalAnd: result = ir.BitwiseAnd(lhs, rhs); break;
|
};
|
||||||
case IR::ValueOpcode::LogicalOr: result = ir.BitwiseOr(lhs, rhs); break;
|
if (!write_64) {
|
||||||
default: EXIT("unsupported SAVEEXEC operation");
|
// 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(inst.dst, old);
|
||||||
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result);
|
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result);
|
||||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto old = ir.GetExec();
|
// EXEC words combine bitwise; the per-lane EXEC combines the per-lane masks, so helper lanes
|
||||||
const auto src = ReadMask(inst.src0);
|
// stay enabled.
|
||||||
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
const auto old = ir.GetExec();
|
||||||
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
const auto src = ReadMask(inst.src0);
|
||||||
const auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
|
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
||||||
WriteMask(inst.dst, old, true);
|
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
||||||
const auto mask = BallotMask(result);
|
const auto src_mask = ReadU32Pair(inst.src0);
|
||||||
ir.SetExec(result);
|
const std::array old_mask {ir.GetExecLo(), ir.GetExecHi()};
|
||||||
ir.SetExecLo(mask[0]);
|
const std::array mask {combine(old_mask[0], src_mask[0]), combine(old_mask[1], src_mask[1])};
|
||||||
ir.SetExecHi(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))));
|
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);
|
WriteOperand(DestinationOperand(inst), result);
|
||||||
if (vector) {
|
if (vector) {
|
||||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), carry));
|
WriteVectorMask(inst.dst2, carry);
|
||||||
} else {
|
} else {
|
||||||
ir.SetScc(carry);
|
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);
|
const auto borrow = ir.UGreaterThan(rhs, lhs);
|
||||||
WriteOperand(DestinationOperand(inst), result);
|
WriteOperand(DestinationOperand(inst), result);
|
||||||
if (vector) {
|
if (vector) {
|
||||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
WriteVectorMask(inst.dst2, borrow);
|
||||||
} else {
|
} else {
|
||||||
ir.SetScc(borrow);
|
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);
|
const auto borrow = ir.LogicalOr(borrow0, borrow1);
|
||||||
WriteOperand(DestinationOperand(inst), result);
|
WriteOperand(DestinationOperand(inst), result);
|
||||||
if (vector) {
|
if (vector) {
|
||||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
WriteVectorMask(inst.dst2, borrow);
|
||||||
} else {
|
} else {
|
||||||
ir.SetScc(borrow);
|
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) {
|
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(
|
return IR::U32(
|
||||||
ir.Emit(IR::ValueOpcode::BitFieldInsert, {low, high, IR::Value(16u), IR::Value(16u)}));
|
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;
|
default: return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
const auto lhs = ReadU32Pair(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));
|
|
||||||
}
|
|
||||||
std::array<IR::U32, 2> result;
|
std::array<IR::U32, 2> result;
|
||||||
if (unary) {
|
if (unary) {
|
||||||
result = {ir.BitwiseNot(lhs[0]), ir.BitwiseNot(lhs[1])};
|
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);
|
result[component] = negate_result ? ir.BitwiseNot(IR::U32(value)) : IR::U32(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WriteU32Pair(inst.dst, result);
|
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
||||||
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
|
||||||
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
WriteMask(inst.dst, invocation_result, result, true);
|
||||||
ir.SetThreadBitScalarReg(dst, invocation_result);
|
} else {
|
||||||
ir.SetScalarMaskTag(dst, mask_valid);
|
// 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))));
|
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 &&
|
if (inst.dst2.kind != Decoder::OperandKind::Null &&
|
||||||
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
||||||
// The 32x32 product fits in 64 bits, so only the addition can carry.
|
// 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 offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||||
const auto bit =
|
const auto bit =
|
||||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {value, offset, IR::Value(1u)}));
|
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) {
|
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]);
|
ir.Select(ir.ULessThan(offset, IR::U32(IR::Value(32u))), value[0], value[1]);
|
||||||
const auto bit =
|
const auto bit =
|
||||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
|
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) {
|
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) {
|
void Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||||
const auto memory = MemoryInfoFromDecoded(inst);
|
const auto memory = MemoryInfoFromDecoded(inst);
|
||||||
WriteOperand(inst.dst, ir.Emit(opcode,
|
const auto exec = ExecMask();
|
||||||
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec()},
|
WriteOperand(inst.dst,
|
||||||
AddMemoryInfo(memory, inst.pc)));
|
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) {
|
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)));
|
const auto packer = ir.BitwiseAnd(m0, IR::U32(IR::Value(0xfu)));
|
||||||
address = ir.IAdd(address, ir.ShiftLeftLogical(packer, IR::U32(IR::Value(2u))));
|
address = ir.IAdd(address, ir.ShiftLeftLogical(packer, IR::U32(IR::Value(2u))));
|
||||||
}
|
}
|
||||||
const auto exec = ir.GetExec();
|
const auto [low, high] = ExecMask();
|
||||||
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 active = ir.INotEqual(ir.BitwiseOr(low, high), IR::U32(IR::Value(0u)));
|
const auto active = ir.INotEqual(ir.BitwiseOr(low, high), IR::U32(IR::Value(0u)));
|
||||||
const auto first = ir.Select(
|
const auto first = ir.Select(
|
||||||
ir.INotEqual(low, IR::U32(IR::Value(0u))),
|
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_U32:
|
||||||
case O::S_CMP_EQ_I32:
|
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;
|
return;
|
||||||
case O::S_CMP_LG_U32:
|
case O::S_CMP_LG_U32:
|
||||||
case O::S_CMP_LG_I32:
|
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;
|
return;
|
||||||
case O::S_CMP_GT_U32:
|
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;
|
return;
|
||||||
case O::S_CMP_GE_U32:
|
case O::S_CMP_GE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true);
|
||||||
false);
|
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LT_U32:
|
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;
|
return;
|
||||||
case O::S_CMP_LE_U32:
|
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;
|
return;
|
||||||
case O::S_CMP_GT_I32:
|
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;
|
return;
|
||||||
case O::S_CMP_GE_I32:
|
case O::S_CMP_GE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true);
|
||||||
false);
|
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LT_I32:
|
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;
|
return;
|
||||||
case O::S_CMP_LE_I32:
|
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;
|
return;
|
||||||
case O::S_CMP_EQ_U64:
|
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;
|
return;
|
||||||
case O::S_CMP_LG_U64:
|
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;
|
return;
|
||||||
|
|
||||||
case O::S_AND_B64:
|
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))};
|
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) {
|
IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
||||||
switch (operand.kind) {
|
switch (operand.kind) {
|
||||||
case Decoder::OperandKind::LiteralConstant:
|
case Decoder::OperandKind::LiteralConstant:
|
||||||
@@ -138,6 +152,8 @@ IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
|||||||
case Decoder::OperandKind::SharedBase:
|
case Decoder::OperandKind::SharedBase:
|
||||||
case Decoder::OperandKind::PrivateBase:
|
case Decoder::OperandKind::PrivateBase:
|
||||||
case Decoder::OperandKind::PopsExitingWaveId: return IR::U32(IR::Value(0u));
|
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:
|
case Decoder::OperandKind::Sgpr:
|
||||||
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg));
|
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg));
|
||||||
case Decoder::OperandKind::Vgpr:
|
case Decoder::OperandKind::Vgpr:
|
||||||
@@ -388,21 +404,9 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
|||||||
switch (operand.kind) {
|
switch (operand.kind) {
|
||||||
case Decoder::OperandKind::Scc: ir.SetScc(IR::U1(value)); return;
|
case Decoder::OperandKind::Scc: ir.SetScc(IR::U1(value)); return;
|
||||||
case Decoder::OperandKind::ExecLo:
|
case Decoder::OperandKind::ExecLo:
|
||||||
case Decoder::OperandKind::ExecHi: {
|
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::VccLo:
|
case Decoder::OperandKind::VccLo:
|
||||||
case Decoder::OperandKind::VccHi: {
|
case Decoder::OperandKind::VccHi: EXIT("lane masks are written through WriteMask");
|
||||||
const auto mask = BallotMask(IR::U1(value));
|
|
||||||
ir.SetVcc(IR::U1(value));
|
|
||||||
ir.SetVccLo(mask[0]);
|
|
||||||
ir.SetVccHi(mask[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
WriteRawU32(operand, ir.Select(IR::U1(value), IR::U32(IR::Value(1u)),
|
WriteRawU32(operand, ir.Select(IR::U1(value), IR::U32(IR::Value(1u)),
|
||||||
IR::U32(IR::Value(0u))));
|
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) {
|
std::array<IR::U32, 2> Translator::ReadU32Pair(const Decoder::Operand& operand) {
|
||||||
if (operand.kind == Decoder::OperandKind::PrivateBase ||
|
// Each aperture limit is its base plus 0xffffffff.
|
||||||
operand.kind == Decoder::OperandKind::SharedBase) {
|
switch (operand.kind) {
|
||||||
return {IR::U32(IR::Value(0u)), IR::U32(IR::Value(
|
case Decoder::OperandKind::SharedBase:
|
||||||
operand.kind == Decoder::OperandKind::PrivateBase ? Decoder::PrivateApertureHigh
|
case Decoder::OperandKind::SharedLimit:
|
||||||
: Decoder::SharedApertureHigh))};
|
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::SharedApertureHigh))};
|
||||||
}
|
case Decoder::OperandKind::PrivateBase:
|
||||||
if (operand.kind == Decoder::OperandKind::ExecLo) {
|
case Decoder::OperandKind::PrivateLimit:
|
||||||
return {ir.GetExecLo(), ir.GetExecHi()};
|
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::PrivateApertureHigh))};
|
||||||
}
|
case Decoder::OperandKind::ExecLo: return {ir.GetExecLo(), ir.GetExecHi()};
|
||||||
if (operand.kind == Decoder::OperandKind::VccLo) {
|
case Decoder::OperandKind::VccLo: return {ir.GetVccLo(), ir.GetVccHi()};
|
||||||
return {ir.GetVccLo(), ir.GetVccHi()};
|
default: break;
|
||||||
}
|
}
|
||||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||||
const auto bits =
|
const auto bits =
|
||||||
@@ -639,7 +643,7 @@ IR::U1 Translator::ReadMask(const Decoder::Operand& operand) {
|
|||||||
}
|
}
|
||||||
case Decoder::OperandKind::ExecLo:
|
case Decoder::OperandKind::ExecLo:
|
||||||
case Decoder::OperandKind::ExecHi: return ir.GetExec();
|
case Decoder::OperandKind::ExecHi: return ir.GetExec();
|
||||||
case Decoder::OperandKind::VccLo:
|
case Decoder::OperandKind::VccLo: return ir.GetVcc();
|
||||||
case Decoder::OperandKind::VccHi:
|
case Decoder::OperandKind::VccHi:
|
||||||
return program.wave_size == 32u
|
return program.wave_size == 32u
|
||||||
? ThreadBit({ReadRawU32(operand), IR::U32(IR::Value(0u))})
|
? 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,
|
void Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||||
bool write_64) {
|
const std::array<IR::U32, 2>& mask, bool write_64) {
|
||||||
const auto mask = BallotMask(value);
|
|
||||||
switch (operand.kind) {
|
switch (operand.kind) {
|
||||||
case Decoder::OperandKind::Sgpr: {
|
case Decoder::OperandKind::Sgpr: {
|
||||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
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.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
||||||
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
||||||
}
|
}
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
case Decoder::OperandKind::ExecLo:
|
case Decoder::OperandKind::ExecLo:
|
||||||
case Decoder::OperandKind::ExecHi: {
|
case Decoder::OperandKind::ExecHi: {
|
||||||
ir.SetExec(value);
|
ir.SetExec(value);
|
||||||
ir.SetExecLo(mask[0]);
|
ir.SetExecLo(mask[0]);
|
||||||
ir.SetExecHi(mask[1]);
|
ir.SetExecHi(mask[1]);
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
case Decoder::OperandKind::VccLo:
|
case Decoder::OperandKind::VccLo:
|
||||||
case Decoder::OperandKind::VccHi: {
|
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]);
|
WriteRawU32(operand, mask[0]);
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
ir.SetVcc(value);
|
ir.SetVcc(value);
|
||||||
ir.SetVccLo(mask[0]);
|
ir.SetVccLo(mask[0]);
|
||||||
ir.SetVccHi(mask[1]);
|
if (write_64 || program.wave_size == 64u) {
|
||||||
return mask;
|
ir.SetVccHi(mask[1]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
case Decoder::OperandKind::Scc: ir.SetScc(value); return mask;
|
case Decoder::OperandKind::Scc: ir.SetScc(value); return;
|
||||||
default:
|
default:
|
||||||
WriteRawU32(operand, mask[0]);
|
WriteRawU32(operand, mask[0]);
|
||||||
WriteRawU32(OffsetOperand(operand, 1), mask[1]);
|
WriteRawU32(OffsetOperand(operand, 1), mask[1]);
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 value) {
|
void Translator::WriteVectorMask(const Decoder::Operand& operand, IR::U1 value) {
|
||||||
if (operand.kind == Decoder::OperandKind::Scc) {
|
|
||||||
WriteOperand(operand, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// VALU instructions only write lanes enabled by the incoming EXEC mask. This is
|
// 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
|
// especially important for V_CMPX: replacing EXEC with the ungated comparison would
|
||||||
// reactivate lanes disabled by an enclosing divergent region.
|
// 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,
|
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);
|
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_lo = entry_ir.CompositeExtract(initial_mask, 0);
|
||||||
const auto initial_hi = options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1)
|
const auto initial_hi = options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1)
|
||||||
: IR::U32(IR::Value(0u));
|
: 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(
|
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
||||||
primitive_count, u32(8)),
|
primitive_count, u32(8)),
|
||||||
vertex_count)));
|
vertex_count)));
|
||||||
|
// Instance IDs exclude the start instance, which shaders add from user data.
|
||||||
if (mesh.fast_launch) {
|
if (mesh.fast_launch) {
|
||||||
// Fast launch broadcasts the subgroup's base vertex and instance to every lane.
|
// 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>(5), entry_ir.IAdd(draw(1), group));
|
||||||
entry_ir.SetVectorReg(
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(6),
|
||||||
static_cast<IR::VectorReg>(6),
|
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
|
||||||
} else {
|
} else {
|
||||||
// GS adjacency addresses local ES records in LDS. Fans retain the draw's
|
// GS adjacency addresses local ES records in LDS. Fans retain the draw's
|
||||||
// center in every subgroup; strip winding follows the global primitive.
|
// 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.ShiftLeftLogical(second, u32(18))));
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
|
||||||
entry_ir.ShiftLeftLogical(third, u32(2)));
|
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 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)),
|
const auto byte_offset = entry_ir.IAdd(entry_ir.BitwiseAnd(index_low, u32(3)),
|
||||||
entry_ir.IMul(input_vertex, index_bytes));
|
entry_ir.IMul(input_vertex, index_bytes));
|
||||||
const auto index_resource = entry_ir.Emit(
|
const auto index_resource = entry_ir.Emit(
|
||||||
IR::ValueOpcode::GetAddressResource,
|
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());
|
const auto memory_index = static_cast<uint32_t>(result.memory_info.size());
|
||||||
result.memory_info.push_back({.kind = IR::ResourceKind::Global});
|
result.memory_info.push_back({.kind = IR::ResourceKind::Global});
|
||||||
const auto packed_index = entry_ir.Emit(
|
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.IMul(index_bytes, u32(8))}));
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||||
entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex)));
|
entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex)));
|
||||||
entry_ir.SetVectorReg(
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||||
static_cast<IR::VectorReg>(8),
|
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
|
||||||
}
|
}
|
||||||
} else if (options.stage == ShaderType::Local) {
|
} else if (options.stage == ShaderType::Local) {
|
||||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
|
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 ScalarDestinationOperand(const Decoder::Operand& operand, uint32_t offset);
|
||||||
Decoder::Operand PlainOperand(const Decoder::Operand& operand);
|
Decoder::Operand PlainOperand(const Decoder::Operand& operand);
|
||||||
std::array<IR::U32, 2> BallotMask(IR::U1 value);
|
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 ReadRawU32(const Decoder::Operand& operand);
|
||||||
IR::U32 ReadScalarCode(uint32_t code);
|
IR::U32 ReadScalarCode(uint32_t code);
|
||||||
IR::U32 ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value);
|
IR::U32 ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value);
|
||||||
@@ -52,9 +54,9 @@ private:
|
|||||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||||
IR::U1 ReadMask(const Decoder::Operand& operand);
|
IR::U1 ReadMask(const Decoder::Operand& operand);
|
||||||
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
||||||
std::array<IR::U32, 2> WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
void WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||||
bool write_64 = false);
|
const std::array<IR::U32, 2>& mask, bool write_64 = false);
|
||||||
void WriteCompareResult(const Decoder::Operand& operand, IR::U1 value);
|
void WriteVectorMask(const Decoder::Operand& operand, IR::U1 value);
|
||||||
|
|
||||||
IR::MemoryFlags AddMemoryInfo(const IR::MemoryInfo& memory, uint32_t pc);
|
IR::MemoryFlags AddMemoryInfo(const IR::MemoryInfo& memory, uint32_t pc);
|
||||||
IR::ExportFlags AddExportInfo(const Decoder::Instruction& inst);
|
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::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
||||||
IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high);
|
IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high);
|
||||||
IR::U32 Saturate16(IR::U32 value, bool signed_value);
|
IR::U32 Saturate16(IR::U32 value, bool signed_value);
|
||||||
void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 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, bool cmpx);
|
void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar);
|
||||||
void EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
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,
|
void EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||||
bool signed_value, bool cmpx);
|
bool signed_value);
|
||||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
|
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half);
|
||||||
bool cmpx);
|
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool is64 = false);
|
||||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx,
|
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool half = false);
|
||||||
bool is64 = false);
|
|
||||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half = false);
|
|
||||||
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
|
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_U32(const Decoder::Instruction& inst);
|
||||||
void V_CVT_F32_I32(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_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
|
||||||
|
|
||||||
case O::V_CMP_F_I32:
|
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_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_U32:
|
||||||
case O::V_CMP_EQ_I32:
|
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_U32:
|
||||||
case O::V_CMPX_EQ_I32:
|
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;
|
return;
|
||||||
case O::V_CMP_NE_U32:
|
case O::V_CMP_NE_U32:
|
||||||
case O::V_CMP_NE_I32:
|
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_U32:
|
||||||
case O::V_CMPX_NE_I32:
|
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;
|
return;
|
||||||
case O::V_CMP_GT_U32:
|
case O::V_CMP_GT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_U32:
|
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;
|
return;
|
||||||
case O::V_CMP_GE_U32:
|
case O::V_CMP_GE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_U32:
|
case O::V_CMPX_GE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_U32:
|
case O::V_CMP_LT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_U32:
|
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;
|
return;
|
||||||
case O::V_CMP_LE_U32:
|
case O::V_CMP_LE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_U32:
|
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;
|
return;
|
||||||
case O::V_CMP_GT_I32:
|
case O::V_CMP_GT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_I32:
|
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;
|
return;
|
||||||
case O::V_CMP_GE_I32:
|
case O::V_CMP_GE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_I32:
|
case O::V_CMPX_GE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_I32:
|
case O::V_CMP_LT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_I32:
|
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;
|
return;
|
||||||
case O::V_CMP_LE_I32:
|
case O::V_CMP_LE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_I32:
|
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;
|
return;
|
||||||
case O::V_CMP_F_I64:
|
case O::V_CMP_F_I64:
|
||||||
case O::V_CMP_F_U64: EmitCompareConstant(inst, false, false, false); return;
|
case O::V_CMP_F_U64:
|
||||||
case O::V_CMP_T_I64:
|
|
||||||
case O::V_CMP_T_U64: EmitCompareConstant(inst, true, false, false); return;
|
|
||||||
case O::V_CMPX_F_I64:
|
case O::V_CMPX_F_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_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_I64:
|
||||||
case O::V_CMP_EQ_U64:
|
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_I64:
|
||||||
case O::V_CMPX_EQ_U64:
|
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;
|
return;
|
||||||
case O::V_CMP_LT_U64:
|
case O::V_CMP_LT_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_U64:
|
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;
|
return;
|
||||||
case O::V_CMP_GT_U64:
|
case O::V_CMP_GT_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_U64:
|
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;
|
return;
|
||||||
case O::V_CMP_NE_I64:
|
case O::V_CMP_NE_I64:
|
||||||
case O::V_CMP_NE_U64:
|
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_I64:
|
||||||
case O::V_CMPX_NE_U64:
|
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;
|
return;
|
||||||
|
|
||||||
case O::V_CMP_LT_I64:
|
case O::V_CMP_LT_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_I64:
|
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;
|
return;
|
||||||
case O::V_CMP_LE_I64:
|
case O::V_CMP_LE_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_I64:
|
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;
|
return;
|
||||||
// The IR has no signed 64-bit greater-than: a > b is b < a, and a >= b is b <= a.
|
// 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:
|
case O::V_CMP_GT_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false,
|
|
||||||
true);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_I64:
|
case O::V_CMPX_GT_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_I64:
|
case O::V_CMP_GE_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false,
|
|
||||||
true);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_I64:
|
case O::V_CMPX_GE_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_U64:
|
case O::V_CMP_LE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_U64:
|
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;
|
return;
|
||||||
case O::V_CMP_GE_U64:
|
case O::V_CMP_GE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_U64:
|
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;
|
return;
|
||||||
|
|
||||||
case O::V_CMP_EQ_U16:
|
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;
|
return;
|
||||||
case O::V_CMP_EQ_I16:
|
case O::V_CMP_EQ_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_I16:
|
case O::V_CMPX_EQ_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NE_U16:
|
case O::V_CMP_NE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NE_U16:
|
case O::V_CMPX_NE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NE_I16:
|
case O::V_CMP_NE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NE_I16:
|
case O::V_CMPX_NE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_U16:
|
case O::V_CMP_GT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_U16:
|
case O::V_CMPX_GT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_U16:
|
case O::V_CMP_GE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
|
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_U16:
|
case O::V_CMP_LT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_U16:
|
case O::V_CMPX_LT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false);
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_U16:
|
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_U16:
|
case O::V_CMP_LE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false);
|
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_I16:
|
case O::V_CMP_GT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_I16:
|
case O::V_CMPX_GT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_I16:
|
case O::V_CMP_GE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_I16:
|
case O::V_CMPX_GE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_I16:
|
case O::V_CMP_LT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_I16:
|
case O::V_CMPX_LT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_I16:
|
case O::V_CMP_LE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_I16:
|
case O::V_CMPX_LE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return;
|
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false); return;
|
||||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return;
|
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false); return;
|
||||||
case O::V_CMP_EQ_F32:
|
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;
|
return;
|
||||||
case O::V_CMP_EQ_F64:
|
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;
|
return;
|
||||||
case O::V_CMP_LE_F64:
|
case O::V_CMP_LE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_F64:
|
case O::V_CMPX_LE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false);
|
||||||
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);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_F64:
|
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;
|
return;
|
||||||
case O::V_CMP_NLG_F64:
|
case O::V_CMP_NLG_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLG_F64:
|
case O::V_CMPX_NLG_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NEQ_F64:
|
case O::V_CMP_NEQ_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NEQ_F64:
|
case O::V_CMPX_NEQ_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLE_F64:
|
case O::V_CMP_NLE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLE_F64:
|
case O::V_CMPX_NLE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLT_F64:
|
case O::V_CMP_NLT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLT_F64:
|
case O::V_CMPX_NLT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGE_F64:
|
case O::V_CMP_NGE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGE_F64:
|
case O::V_CMPX_NGE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGT_F64:
|
case O::V_CMP_NGT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGT_F64:
|
case O::V_CMPX_NGT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false);
|
||||||
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);
|
|
||||||
return;
|
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:
|
case O::V_CMP_LG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LG_F32:
|
case O::V_CMPX_LG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_F32:
|
case O::V_CMP_GT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_F32:
|
case O::V_CMPX_GT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_F32:
|
case O::V_CMP_GE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_F32:
|
case O::V_CMPX_GE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_F32:
|
case O::V_CMP_LT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_F32:
|
case O::V_CMPX_LT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_F32:
|
case O::V_CMP_LE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_F32:
|
case O::V_CMPX_LE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLG_F32:
|
case O::V_CMP_NLG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLG_F32:
|
case O::V_CMPX_NLG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NEQ_F32:
|
case O::V_CMP_NEQ_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NEQ_F32:
|
case O::V_CMPX_NEQ_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLE_F32:
|
case O::V_CMP_NLE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLE_F32:
|
case O::V_CMPX_NLE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLT_F32:
|
case O::V_CMP_NLT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLT_F32:
|
case O::V_CMPX_NLT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGE_F32:
|
case O::V_CMP_NGE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGE_F32:
|
case O::V_CMPX_NGE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGT_F32:
|
case O::V_CMP_NGT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGT_F32:
|
case O::V_CMPX_NGT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_EQ_F16:
|
case O::V_CMP_EQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_F16:
|
case O::V_CMPX_EQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LG_F16:
|
case O::V_CMP_LG_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true, false);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_F16:
|
case O::V_CMP_GT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_F16:
|
case O::V_CMPX_GT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_F16:
|
case O::V_CMP_GE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_F16:
|
case O::V_CMPX_GE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_F16:
|
case O::V_CMP_LT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_F16:
|
case O::V_CMPX_LT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_F16:
|
case O::V_CMP_LE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_F16:
|
case O::V_CMPX_LE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGE_F16:
|
case O::V_CMP_NGE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true, false);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGT_F16:
|
case O::V_CMP_NGT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGT_F16:
|
case O::V_CMPX_NGT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMPX_NLE_F16:
|
case O::V_CMPX_NLE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NEQ_F16:
|
case O::V_CMP_NEQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NEQ_F16:
|
case O::V_CMPX_NEQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLT_F16:
|
case O::V_CMP_NLT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLT_F16:
|
case O::V_CMPX_NLT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true, false); return;
|
case O::V_CMP_O_F32:
|
||||||
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false, false); return;
|
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true); return;
|
||||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
|
case O::V_CMP_U_F32:
|
||||||
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false, true); return;
|
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false); return;
|
||||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
|
case O::V_CMP_CLASS_F32:
|
||||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
|
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst); return;
|
||||||
case O::V_CMP_CLASS_F16: EmitFloatClassCompare(inst, false, true); return;
|
case O::V_CMP_CLASS_F16:
|
||||||
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true, true); return;
|
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_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return;
|
||||||
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); 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(StageInput, a, b, c, d, e)
|
||||||
KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
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,
|
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::CountedRows, a, b)
|
||||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j)
|
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)
|
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 {
|
struct PushData {
|
||||||
static constexpr uint32_t DwordCount = 32;
|
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;
|
static constexpr uint32_t NoStart = UINT32_MAX;
|
||||||
std::array<uint32_t, DwordCount> dwords {};
|
std::array<uint32_t, DwordCount> dwords {};
|
||||||
|
|
||||||
@@ -495,10 +495,16 @@ struct Bindings {
|
|||||||
uint32_t dispatch_thread_dword = PushData::NoStart;
|
uint32_t dispatch_thread_dword = PushData::NoStart;
|
||||||
uint32_t memory_offset_dword = 0;
|
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 +
|
return memory_offset_dword +
|
||||||
(descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)] + 3u) / 4u;
|
(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 {
|
[[nodiscard]] bool UsesPushData() const {
|
||||||
return push_data_start_dword != PushData::NoStart;
|
return push_data_start_dword != PushData::NoStart;
|
||||||
}
|
}
|
||||||
@@ -541,6 +547,7 @@ struct ShaderInfo {
|
|||||||
bool subgroup_shuffle = false;
|
bool subgroup_shuffle = false;
|
||||||
bool subgroup_arithmetic = false;
|
bool subgroup_arithmetic = false;
|
||||||
bool subgroup_local_invocation_id = false;
|
bool subgroup_local_invocation_id = false;
|
||||||
|
bool helper_invocation = false;
|
||||||
bool compute_derivatives = false;
|
bool compute_derivatives = false;
|
||||||
bool image_gather_extended = false;
|
bool image_gather_extended = false;
|
||||||
bool function_lds = false;
|
bool function_lds = false;
|
||||||
|
|||||||
@@ -219,9 +219,11 @@ VALUE_OPCODE(FPFract64, F64, F64)
|
|||||||
|
|
||||||
VALUE_OPCODE(LaneId, U32)
|
VALUE_OPCODE(LaneId, U32)
|
||||||
VALUE_OPCODE(Ballot, U32x4, U1)
|
VALUE_OPCODE(Ballot, U32x4, U1)
|
||||||
|
VALUE_OPCODE(LiveMask, U32x4)
|
||||||
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
||||||
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
||||||
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
|
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
|
||||||
|
VALUE_OPCODE(SubgroupUMaxU32, U32, U32)
|
||||||
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
||||||
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
|
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
|
||||||
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
|
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(SharedAtomicOr64, Void, U32, U64, U1)
|
||||||
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(SharedAtomicMaskedOr32, Void, U32, U32, U32, U1)
|
VALUE_OPCODE(SharedAtomicMaskedOr32, Void, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(DataAppend, U32, U32, U1)
|
VALUE_OPCODE(DataAppend, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(DataConsume, U32, U32, U1)
|
VALUE_OPCODE(DataConsume, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
||||||
|
|
||||||
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
||||||
|
|||||||
@@ -167,6 +167,16 @@ bool ReplaceBinaryIdentity(Inst& inst, Type type, uint64_t identity) {
|
|||||||
return false;
|
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) {
|
bool FoldSelect(Inst& inst) {
|
||||||
const auto condition = Arg(inst, 0);
|
const auto condition = Arg(inst, 0);
|
||||||
const auto true_value = Arg(inst, 1);
|
const auto true_value = Arg(inst, 1);
|
||||||
@@ -626,8 +636,9 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
|||||||
case ValueOpcode::BitwiseXor32:
|
case ValueOpcode::BitwiseXor32:
|
||||||
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
|
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
|
||||||
return opcode == ValueOpcode::BitwiseOr32 ? a | b : a ^ 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;
|
return;
|
||||||
case ValueOpcode::BitwiseNot32: {
|
case ValueOpcode::BitwiseNot32: {
|
||||||
@@ -802,6 +813,8 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
|||||||
Replace(inst, lhs.U1() ? lhs : rhs);
|
Replace(inst, lhs.U1() ? lhs : rhs);
|
||||||
} else if (IsImmediate(rhs, Type::U1)) {
|
} else if (IsImmediate(rhs, Type::U1)) {
|
||||||
Replace(inst, rhs.U1() ? rhs : lhs);
|
Replace(inst, rhs.U1() ? rhs : lhs);
|
||||||
|
} else if (IsComplementPair(inst, ValueOpcode::LogicalNot)) {
|
||||||
|
Replace(inst, Value(true));
|
||||||
} else {
|
} else {
|
||||||
const auto* left = lhs.TryInstruction();
|
const auto* left = lhs.TryInstruction();
|
||||||
const auto* right = rhs.TryInstruction();
|
const auto* right = rhs.TryInstruction();
|
||||||
@@ -892,8 +905,10 @@ private:
|
|||||||
if (inst.GetOpcode() != ValueOpcode::CompositeExtractU32x4 ||
|
if (inst.GetOpcode() != ValueOpcode::CompositeExtractU32x4 ||
|
||||||
!Immediate(Arg(inst, 1), 0u)) return {};
|
!Immediate(Arg(inst, 1), 0u)) return {};
|
||||||
const auto* source = Arg(inst, 0).TryInstruction();
|
const auto* source = Arg(inst, 0).TryInstruction();
|
||||||
return source != nullptr && source->GetOpcode() == ValueOpcode::Ballot
|
if (source == nullptr) return {};
|
||||||
? Arg(*source, 0) : Value {};
|
// 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) {
|
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);
|
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.
|
// Fragment subgroups may have no physical invocation at either endpoint.
|
||||||
class UMinReductionMatcher {
|
class WaveReductionMatcher {
|
||||||
public:
|
public:
|
||||||
explicit UMinReductionMatcher(Block* block): block(block) {}
|
WaveReductionMatcher(const Block* block, ValueOpcode operation)
|
||||||
|
: block(block), operation(operation) {}
|
||||||
|
|
||||||
Value Match(const Inst& minimum) {
|
Value Match(const Inst& reduction, uint32_t wave_size) const {
|
||||||
const auto* low = Get(minimum.Arg(0), ValueOpcode::ReadLane);
|
const auto* low = Get(reduction.Arg(0), ValueOpcode::ReadLane);
|
||||||
const auto* high = Get(minimum.Arg(1), ValueOpcode::ReadLane);
|
const auto* high = Get(reduction.Arg(1), ValueOpcode::ReadLane);
|
||||||
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
|
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto lane = [](Value value, uint32_t expected) {
|
const auto lane = [&](Value value, uint32_t expected) {
|
||||||
value = value.Resolve();
|
value = value.Resolve();
|
||||||
return value.IsImmediate() && value.GetType() == Type::U32 &&
|
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)) ||
|
const auto half = wave_size / 2u;
|
||||||
(lane(low->Arg(1), 63) && lane(high->Arg(1), 31)))) {
|
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 {};
|
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 {};
|
if (joined == nullptr) return {};
|
||||||
for (size_t index = 0; index < 2; index++) {
|
for (size_t index = 0; index < 2; index++) {
|
||||||
auto attempt = *this;
|
const auto* cross = Get(joined->Arg(index), ValueOpcode::Permlane16U32);
|
||||||
const auto* cross = attempt.Get(attempt.Unwrap(joined->Arg(index)), ValueOpcode::Permlane16U32);
|
|
||||||
if (cross == nullptr) continue;
|
if (cross == nullptr) continue;
|
||||||
const auto flags = cross->Flags<PermlaneFlags>();
|
const auto flags = cross->Flags<PermlaneFlags>();
|
||||||
auto prefix = attempt.Unwrap(joined->Arg(1 - index));
|
const auto prefix = joined->Arg(1 - index).Resolve();
|
||||||
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
|
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
|
||||||
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
|
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
|
||||||
cross->Arg(2).Resolve() != Value(UINT32_MAX) ||
|
cross->Arg(2).Resolve() != Value(UINT32_MAX) || !FullExec(cross->Arg(3)) ||
|
||||||
!attempt.FullExec(cross->Arg(3)) || attempt.Unwrap(cross->Arg(0)) != prefix) {
|
cross->Arg(0).Resolve() != prefix) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
if (const auto seed = MatchScan(prefix); !seed.IsEmpty()) return seed;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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 {
|
Inst* Get(Value value, ValueOpcode opcode) const {
|
||||||
auto* inst = value.Resolve().TryInstruction();
|
auto* inst = value.Resolve().TryInstruction();
|
||||||
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
|
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
|
||||||
? inst : nullptr;
|
? inst : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FullExec(Value predicate) {
|
// Constant propagation folds the x | ~x EXEC of s_orn2_saveexec to true.
|
||||||
predicate = predicate.Resolve();
|
static bool FullExec(Value predicate) { return predicate.Resolve() == Value(true); }
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsRowShift(const Inst& inst, uint32_t shift) {
|
static bool IsRowShift(const Inst& inst, uint32_t shift) {
|
||||||
const auto flags = inst.Flags<DppMoveFlags>();
|
const auto flags = inst.Flags<DppMoveFlags>();
|
||||||
@@ -189,22 +166,24 @@ private:
|
|||||||
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
|
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value MatchRow(Value value, uint32_t shift) {
|
Value MatchRow(Value value, uint32_t shift) const {
|
||||||
const auto* update = Get(Unwrap(value), ValueOpcode::DppUpdateU32);
|
const auto* update = Get(value, ValueOpcode::DppUpdateU32);
|
||||||
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
|
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
|
||||||
const auto* minimum = Get(update->Arg(0), ValueOpcode::UMin32);
|
const auto* combined = Get(update->Arg(0), operation);
|
||||||
if (minimum == nullptr) return {};
|
if (combined == nullptr) return {};
|
||||||
for (size_t index = 0; index < 2; index++) {
|
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;
|
if (move == nullptr || !IsRowShift(*move, shift) || !FullExec(move->Arg(1))) continue;
|
||||||
const auto previous = Unwrap(minimum->Arg(1 - index));
|
const auto previous = combined->Arg(1 - index).Resolve();
|
||||||
if (Unwrap(move->Arg(0)) == previous && Unwrap(update->Arg(1)) == previous) return previous;
|
if (move->Arg(0).Resolve() == previous && update->Arg(1).Resolve() == previous) {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Block* block;
|
const Block* block;
|
||||||
Value saved_exec;
|
ValueOpcode operation;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -217,18 +196,22 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
|
|||||||
|
|
||||||
for (auto* block: program.blocks) {
|
for (auto* block: program.blocks) {
|
||||||
for (auto& inst: *block) {
|
for (auto& inst: *block) {
|
||||||
if (wave_size == 64u && inst.GetOpcode() == ValueOpcode::UMin32) {
|
const auto opcode = inst.GetOpcode();
|
||||||
const auto seed = UMinReductionMatcher(block).Match(inst);
|
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
|
||||||
|
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
|
||||||
if (!seed.IsEmpty()) {
|
if (!seed.IsEmpty()) {
|
||||||
const auto position = std::find_if(block->begin(), block->end(),
|
const auto position = std::find_if(block->begin(), block->end(),
|
||||||
[&](const Inst& candidate) { return &candidate == &inst; });
|
[&](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));
|
inst.ReplaceUsesWith(Value(&reduction));
|
||||||
stats.rewritten_reductions++;
|
stats.rewritten_reductions++;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (inst.GetOpcode() != ValueOpcode::ReadLane) {
|
if (opcode != ValueOpcode::ReadLane) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto selector = inst.Arg(1).Resolve();
|
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 &&
|
info.function_lds |= kind == ResourceKind::Lds &&
|
||||||
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
|
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
|
||||||
if (shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
if (shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
||||||
info.subgroup_ballot = true;
|
|
||||||
info.subgroup_shuffle = true;
|
info.subgroup_shuffle = true;
|
||||||
info.subgroup_local_invocation_id = 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;
|
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||||
break;
|
break;
|
||||||
case ValueOpcode::Ballot: info.subgroup_ballot = true; 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::DppMoveU32:
|
||||||
case ValueOpcode::ReadFirstLane:
|
case ValueOpcode::ReadFirstLane:
|
||||||
case ValueOpcode::ReadLane: {
|
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.has_bitwise_xor = info.uses_lds = info.uses_gds = info.uses_flattened_srt =
|
||||||
info.uses_dispatch_threads = info.uses_swizzle = false;
|
info.uses_dispatch_threads = info.uses_swizzle = false;
|
||||||
info.bvh = info.subgroup_ballot = info.subgroup_vote = info.subgroup_barrier =
|
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.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.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;
|
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.object_primitive_id));
|
||||||
key.push_back(static_cast<uint32_t>(info.alpha_blend_source));
|
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.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;
|
const auto mask = info.target_shader_mask;
|
||||||
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
|
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
|
||||||
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
|
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;
|
uint32_t target_shader_mask = UINT32_MAX;
|
||||||
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
||||||
uint8_t target_packed_unorm_mask = 0;
|
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.
|
// CB round mode per converted export: zero adds half an LSB, one truncates.
|
||||||
uint8_t target_round_mode = 0;
|
uint8_t target_round_mode = 0;
|
||||||
uint32_t scratch_size_dwords = 0;
|
uint32_t scratch_size_dwords = 0;
|
||||||
|
|||||||
@@ -96,6 +96,13 @@ int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info) {
|
|||||||
return OK;
|
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,
|
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
||||||
uint32_t count, const void* const ir[],
|
uint32_t count, const void* const ir[],
|
||||||
const float* gain, void* const out[]) {
|
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);
|
AcmBatchError* batch_error, AcmBatchId* batch);
|
||||||
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
|
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
|
||||||
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
|
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,
|
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
||||||
uint32_t count, const void* const ir[],
|
uint32_t count, const void* const ir[],
|
||||||
const float* gain, void* const out[]);
|
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_LIMITATION_OVER = -2137063401; /* 0x809F0017 */
|
||||||
constexpr int SAVE_DATA_ERROR_EVENT_BUSY = -2137063400; /* 0x809F0018 */
|
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_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
|
} // namespace SaveData
|
||||||
|
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ LIB_DEFINE(InitAudio_1_Acm) {
|
|||||||
LIB_FUNC("8fe55ktlNVo", Acm::AcmBatchStartBuffers);
|
LIB_FUNC("8fe55ktlNVo", Acm::AcmBatchStartBuffers);
|
||||||
LIB_FUNC("RLN3gRlXJLE", Acm::AcmBatchWait);
|
LIB_FUNC("RLN3gRlXJLE", Acm::AcmBatchWait);
|
||||||
LIB_FUNC("r7z5YQFZo+U", Acm::AcmBatchJobNotification);
|
LIB_FUNC("r7z5YQFZo+U", Acm::AcmBatchJobNotification);
|
||||||
|
LIB_FUNC("GTfJ64qFCPw", Acm::AcmBatchJobPriority);
|
||||||
LIB_FUNC("u70oWo92SYQ", Acm::AcmConvReverbSharedInput);
|
LIB_FUNC("u70oWo92SYQ", Acm::AcmConvReverbSharedInput);
|
||||||
LIB_FUNC("9nLbWmRDpa8", Acm::AcmConvReverbSharedIr);
|
LIB_FUNC("9nLbWmRDpa8", Acm::AcmConvReverbSharedIr);
|
||||||
LIB_FUNC("KovqaFbmtsM", Acm::AcmFft);
|
LIB_FUNC("KovqaFbmtsM", Acm::AcmFft);
|
||||||
|
|||||||
@@ -231,6 +231,15 @@ struct SaveDataDelete {
|
|||||||
int32_t pad2;
|
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 {
|
struct SaveDataEvent {
|
||||||
uint32_t type;
|
uint32_t type;
|
||||||
int32_t error_code;
|
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;
|
return directory / title_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name,
|
static bool is_memory_dir_name(std::string_view dir_name) {
|
||||||
int32_t user_id) {
|
|
||||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||||
if (dir_name == memory_dir_name(slot)) {
|
if (dir_name == memory_dir_name(slot)) {
|
||||||
return std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name /
|
return true;
|
||||||
std::to_string(user_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
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;
|
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();
|
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) {
|
if (event == nullptr) {
|
||||||
return SAVE_DATA_ERROR_PARAMETER;
|
return SAVE_DATA_ERROR_PARAMETER;
|
||||||
}
|
}
|
||||||
@@ -1180,6 +1191,56 @@ int KYTY_SYSV_ABI SaveDataGetEventResult(const void* event_param, SaveDataEvent*
|
|||||||
return OK;
|
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) {
|
int KYTY_SYSV_ABI SaveDataBackup(const SaveDataBackup* backup) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
@@ -1313,6 +1374,7 @@ LIB_DEFINE(InitSaveDataNative_1) {
|
|||||||
LIB_FUNC("cduy9v4YmT4", ::Libs::SaveData::SaveDataSetSaveDataMemory2);
|
LIB_FUNC("cduy9v4YmT4", ::Libs::SaveData::SaveDataSetSaveDataMemory2);
|
||||||
LIB_FUNC("wiT9jeC7xPw", ::Libs::SaveData::SaveDataSyncSaveDataMemory);
|
LIB_FUNC("wiT9jeC7xPw", ::Libs::SaveData::SaveDataSyncSaveDataMemory);
|
||||||
LIB_FUNC("j8xKtiFj0SY", ::Libs::SaveData::SaveDataGetEventResult);
|
LIB_FUNC("j8xKtiFj0SY", ::Libs::SaveData::SaveDataGetEventResult);
|
||||||
|
LIB_FUNC("2mfSRGdshtk", ::Libs::SaveData::SaveDataConvert);
|
||||||
LIB_FUNC("WAzWTZm1H+I", ::Libs::SaveData::SaveDataTransferringMount);
|
LIB_FUNC("WAzWTZm1H+I", ::Libs::SaveData::SaveDataTransferringMount);
|
||||||
LIB_FUNC("RjMlsR8EXrw", ::Libs::SaveData::SaveDataTransferringMount);
|
LIB_FUNC("RjMlsR8EXrw", ::Libs::SaveData::SaveDataTransferringMount);
|
||||||
LIB_FUNC("z1JA8-iJt3k", ::Libs::SaveData::SaveDataBackup);
|
LIB_FUNC("z1JA8-iJt3k", ::Libs::SaveData::SaveDataBackup);
|
||||||
@@ -1343,6 +1405,7 @@ LIB_DEFINE(InitSaveData_1) {
|
|||||||
LIB_FUNC("cduy9v4YmT4", SaveData::SaveDataSetSaveDataMemory2);
|
LIB_FUNC("cduy9v4YmT4", SaveData::SaveDataSetSaveDataMemory2);
|
||||||
LIB_FUNC("wiT9jeC7xPw", SaveData::SaveDataSyncSaveDataMemory);
|
LIB_FUNC("wiT9jeC7xPw", SaveData::SaveDataSyncSaveDataMemory);
|
||||||
LIB_FUNC("j8xKtiFj0SY", SaveData::SaveDataGetEventResult);
|
LIB_FUNC("j8xKtiFj0SY", SaveData::SaveDataGetEventResult);
|
||||||
|
LIB_FUNC("2mfSRGdshtk", SaveData::SaveDataConvert);
|
||||||
LIB_FUNC("WAzWTZm1H+I", SaveData::SaveDataTransferringMount);
|
LIB_FUNC("WAzWTZm1H+I", SaveData::SaveDataTransferringMount);
|
||||||
LIB_FUNC("RjMlsR8EXrw", SaveData::SaveDataTransferringMount);
|
LIB_FUNC("RjMlsR8EXrw", SaveData::SaveDataTransferringMount);
|
||||||
LIB_FUNC("z1JA8-iJt3k", SaveData::SaveDataBackup);
|
LIB_FUNC("z1JA8-iJt3k", SaveData::SaveDataBackup);
|
||||||
|
|||||||
@@ -396,6 +396,38 @@ void TestClassicSavePaths() {
|
|||||||
CHECK(Search(1) == names);
|
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() {
|
void TestGuestSaveIsolation() {
|
||||||
Reset("MULTIUSER");
|
Reset("MULTIUSER");
|
||||||
const auto name = DirName("slot");
|
const auto name = DirName("slot");
|
||||||
@@ -670,6 +702,7 @@ int main(int argc, char** argv) {
|
|||||||
TestIsolationAndSync();
|
TestIsolationAndSync();
|
||||||
TestFailedWritePreservesSave();
|
TestFailedWritePreservesSave();
|
||||||
TestClassicSavePaths();
|
TestClassicSavePaths();
|
||||||
|
TestConvert();
|
||||||
TestGuestSaveIsolation();
|
TestGuestSaveIsolation();
|
||||||
TestSaveAllocations();
|
TestSaveAllocations();
|
||||||
TestClassicSaveParams();
|
TestClassicSaveParams();
|
||||||
|
|||||||
@@ -628,42 +628,45 @@ void TestReadLaneElimination() {
|
|||||||
"dynamic-lane read was rewritten unsafely");
|
"dynamic-lane read was rewritten unsafely");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestCompleteUMinReduction() {
|
void TestCompleteWaveReduction() {
|
||||||
enum class Case {
|
enum class Case {
|
||||||
Masked, Subset, Literal, LiteralMasked, LiteralMove, Wave32,
|
Masked, ScalarExec, Wave32, Wave32Rows, Wave32Maximum, RowMask, BankMask,
|
||||||
UnrelatedSeed, UnmaskedSymbolic, WrongNeutral, RowMask, BankMask,
|
|
||||||
Shift, FetchInactive, BoundControl, Dpp8, MoveExec, UpdateExec,
|
Shift, FetchInactive, BoundControl, Dpp8, MoveExec, UpdateExec,
|
||||||
Previous, DppSource, CrossExec, CrossRow, CrossSelectorLow,
|
Previous, DppSource, CrossExec, CrossRow, CrossSelectorLow,
|
||||||
CrossSelectorHigh, CrossFetch, CrossBound, CrossSource, FinalExec,
|
CrossSelectorHigh, CrossFetch, CrossBound, CrossSource, FinalExec,
|
||||||
Endpoint, EndpointSource, DifferentWitness, CrossBlock, WrongOperation
|
Endpoint, EndpointSource, CrossBlock, WrongOperation
|
||||||
};
|
};
|
||||||
for (const auto test : {Case::Masked, Case::Subset, Case::Literal,
|
for (const auto test : {Case::Masked, Case::ScalarExec, Case::Wave32,
|
||||||
Case::LiteralMasked, Case::LiteralMove, Case::Wave32, Case::UnrelatedSeed,
|
Case::Wave32Rows, Case::Wave32Maximum, Case::RowMask, Case::BankMask,
|
||||||
Case::UnmaskedSymbolic, Case::WrongNeutral, Case::RowMask, Case::BankMask,
|
|
||||||
Case::Shift, Case::FetchInactive, Case::BoundControl, Case::Dpp8,
|
Case::Shift, Case::FetchInactive, Case::BoundControl, Case::Dpp8,
|
||||||
Case::MoveExec, Case::UpdateExec, Case::Previous, Case::DppSource,
|
Case::MoveExec, Case::UpdateExec, Case::Previous, Case::DppSource,
|
||||||
Case::CrossExec, Case::CrossRow, Case::CrossSelectorLow,
|
Case::CrossExec, Case::CrossRow, Case::CrossSelectorLow,
|
||||||
Case::CrossSelectorHigh, Case::CrossFetch, Case::CrossBound,
|
Case::CrossSelectorHigh, Case::CrossFetch, Case::CrossBound,
|
||||||
Case::CrossSource, Case::FinalExec, Case::Endpoint, Case::EndpointSource,
|
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);
|
Fixture fixture(test == Case::CrossBlock ? 2 : 1);
|
||||||
const auto source = fixture.Emit(ValueOpcode::LaneId);
|
const auto source = fixture.Emit(ValueOpcode::LaneId);
|
||||||
const auto mask = fixture.Emit(ValueOpcode::ULessThan32, {source, Value(19u)});
|
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 auto negated = fixture.Emit(ValueOpcode::LogicalNot, {mask});
|
||||||
const bool literal = test == Case::Literal || test == Case::LiteralMasked;
|
auto enabled = fixture.Emit(ValueOpcode::LogicalOr, {negated, mask});
|
||||||
const auto enabled = literal ? Value(true) :
|
if (test == Case::ScalarExec) {
|
||||||
fixture.Emit(ValueOpcode::LogicalOr, {negated, mask});
|
// s_orn2_saveexec of a mask that is not a ballot, read back as a lane bit.
|
||||||
auto seed_mask = test == Case::UnrelatedSeed ? other_mask : mask;
|
const auto exec = fixture.Emit(ValueOpcode::GetUserData, {Value(static_cast<ScalarReg>(2))});
|
||||||
if (test == Case::Subset)
|
const auto full = fixture.Emit(ValueOpcode::BitwiseOr32,
|
||||||
seed_mask = fixture.Emit(ValueOpcode::LogicalAnd, {other_mask, mask});
|
{fixture.Emit(ValueOpcode::BitwiseNot32, {exec}), exec});
|
||||||
const auto seed = test == Case::Literal || test == Case::UnmaskedSymbolic ? source :
|
const auto bit = fixture.Emit(ValueOpcode::BitwiseAnd32, {source, Value(31u)});
|
||||||
fixture.Emit(ValueOpcode::SelectU32,
|
const auto shifted = fixture.Emit(ValueOpcode::ShiftRightLogical32, {full, bit});
|
||||||
{seed_mask, source, Value(test == Case::WrongNeutral ? 0u : UINT32_MAX)});
|
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});
|
auto prefix = fixture.Emit(ValueOpcode::SelectU32, {enabled, seed, source});
|
||||||
Value preserved;
|
Value preserved;
|
||||||
const auto operation = test == Case::WrongOperation ? ValueOpcode::BitwiseOr32 :
|
auto operation = maximum ? ValueOpcode::UMax32 : ValueOpcode::UMin32;
|
||||||
ValueOpcode::UMin32;
|
if (test == Case::WrongOperation) operation = ValueOpcode::BitwiseOr32;
|
||||||
for (const uint32_t shift : {1u, 2u, 4u, 8u}) {
|
for (const uint32_t shift : {1u, 2u, 4u, 8u}) {
|
||||||
DppMoveFlags flags{.control = 0x110u + shift};
|
DppMoveFlags flags{.control = 0x110u + shift};
|
||||||
if (shift == 2) {
|
if (shift == 2) {
|
||||||
@@ -675,8 +678,7 @@ void TestCompleteUMinReduction() {
|
|||||||
if (test == Case::Dpp8) flags.dpp8 = true;
|
if (test == Case::Dpp8) flags.dpp8 = true;
|
||||||
}
|
}
|
||||||
const auto move = fixture.Emit(ValueOpcode::DppMoveU32,
|
const auto move = fixture.Emit(ValueOpcode::DppMoveU32,
|
||||||
{test == Case::DppSource ? source : prefix,
|
{test == Case::DppSource ? source : prefix, test == Case::MoveExec ? mask : enabled});
|
||||||
test == Case::MoveExec ? mask : test == Case::LiteralMove ? Value(true) : enabled});
|
|
||||||
move.Instruction()->SetFlags(flags);
|
move.Instruction()->SetFlags(flags);
|
||||||
const auto combined = fixture.Emit(operation, {move, prefix});
|
const auto combined = fixture.Emit(operation, {move, prefix});
|
||||||
const auto update = fixture.Emit(ValueOpcode::DppUpdateU32,
|
const auto update = fixture.Emit(ValueOpcode::DppUpdateU32,
|
||||||
@@ -686,11 +688,7 @@ void TestCompleteUMinReduction() {
|
|||||||
prefix = update;
|
prefix = update;
|
||||||
if (shift == 2) preserved = fixture.Emit(ValueOpcode::ReferenceU32, {prefix});
|
if (shift == 2) preserved = fixture.Emit(ValueOpcode::ReferenceU32, {prefix});
|
||||||
}
|
}
|
||||||
auto cross_exec = test == Case::CrossExec ? mask : enabled;
|
const 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 = fixture.Emit(ValueOpcode::Permlane16U32,
|
const auto cross = fixture.Emit(ValueOpcode::Permlane16U32,
|
||||||
{test == Case::CrossSource ? source : prefix,
|
{test == Case::CrossSource ? source : prefix,
|
||||||
Value(test == Case::CrossSelectorLow ? 0xfffffff0u : UINT32_MAX),
|
Value(test == Case::CrossSelectorLow ? 0xfffffff0u : UINT32_MAX),
|
||||||
@@ -704,10 +702,16 @@ void TestCompleteUMinReduction() {
|
|||||||
const auto final = fixture.Emit(ValueOpcode::SelectU32,
|
const auto final = fixture.Emit(ValueOpcode::SelectU32,
|
||||||
{test == Case::FinalExec ? mask : enabled, combined, prefix});
|
{test == Case::FinalExec ? mask : enabled, combined, prefix});
|
||||||
const uint32_t result_block = test == Case::CrossBlock ? 1u : 0u;
|
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,
|
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,
|
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 result = fixture.Emit(operation, {high, low}, 0, result_block);
|
||||||
const auto use = fixture.Emit(ValueOpcode::ReferenceU32, {result}, 0, result_block);
|
const auto use = fixture.Emit(ValueOpcode::ReferenceU32, {result}, 0, result_block);
|
||||||
using Terminator = Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind;
|
using Terminator = Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind;
|
||||||
@@ -718,16 +722,19 @@ void TestCompleteUMinReduction() {
|
|||||||
fixture.BlockAt().terminator.true_block = &fixture.BlockAt(1);
|
fixture.BlockAt().terminator.true_block = &fixture.BlockAt(1);
|
||||||
}
|
}
|
||||||
ValidateProgram(fixture.program, true);
|
ValidateProgram(fixture.program, true);
|
||||||
const auto stats = EliminateReadLane(fixture.program, test == Case::Wave32 ? 32u : 64u);
|
// The pipeline folds full-EXEC predicates to true before eliminating reads.
|
||||||
const bool matches = test == Case::Masked || test == Case::Subset ||
|
const uint32_t wave_size = test == Case::Wave32 || rows ? 32u : 64u;
|
||||||
test == Case::Literal || test == Case::LiteralMasked || test == Case::LiteralMove;
|
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,
|
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) {
|
if (matches) {
|
||||||
const auto *reduction = use.Instruction()->Arg(0).ResolveInstruction();
|
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(),
|
reduction->Arg(0).Resolve() == seed.Resolve(),
|
||||||
"UMin reduction changed the neutral-masked input");
|
"wave reduction changed the neutral-masked input");
|
||||||
RemoveIdentities(fixture.program);
|
RemoveIdentities(fixture.program);
|
||||||
EliminateDeadCode(fixture.program.blocks);
|
EliminateDeadCode(fixture.program.blocks);
|
||||||
uint32_t updates = 0;
|
uint32_t updates = 0;
|
||||||
@@ -735,14 +742,14 @@ void TestCompleteUMinReduction() {
|
|||||||
updates += inst.GetOpcode() == ValueOpcode::DppUpdateU32;
|
updates += inst.GetOpcode() == ValueOpcode::DppUpdateU32;
|
||||||
Check(inst.GetOpcode() != ValueOpcode::ReadLane &&
|
Check(inst.GetOpcode() != ValueOpcode::ReadLane &&
|
||||||
inst.GetOpcode() != ValueOpcode::Permlane16U32,
|
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(),
|
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);
|
ValidateProgram(fixture.program, true);
|
||||||
} else {
|
} else {
|
||||||
Check(use.Instruction()->Arg(0).Resolve() == result,
|
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();
|
TestSharedIntegerRuntimeDependencies();
|
||||||
TestConstantBufferBounds();
|
TestConstantBufferBounds();
|
||||||
TestReadLaneElimination();
|
TestReadLaneElimination();
|
||||||
TestCompleteUMinReduction();
|
TestCompleteWaveReduction();
|
||||||
TestOptimizationPipeline();
|
TestOptimizationPipeline();
|
||||||
TestControlFlowValueSurvivesReadLaneFolding();
|
TestControlFlowValueSurvivesReadLaneFolding();
|
||||||
TestDeadPhiCyclesAndPlanningRoots();
|
TestDeadPhiCyclesAndPlanningRoots();
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ struct ImageHeapTestAccess {
|
|||||||
static ImageId ImageOf(const ImageHeap &heap, uint32_t entry) {
|
static ImageId ImageOf(const ImageHeap &heap, uint32_t entry) {
|
||||||
return Element(heap, entry).binding.image_id;
|
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 {
|
struct TextureCacheTestAccess {
|
||||||
@@ -576,6 +580,10 @@ struct RenderExecutorTestAccess {
|
|||||||
executor.ResetBindings();
|
executor.ResetBindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool ResolveColorTargets(RenderExecutor &executor, CommandBuffer &buffer) {
|
||||||
|
return executor.ResolveColorTargets(buffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static bool BoundImagesInOrder(const RenderExecutor &executor, ImageId first,
|
static bool BoundImagesInOrder(const RenderExecutor &executor, ImageId first,
|
||||||
ImageId second) {
|
ImageId second) {
|
||||||
return executor.m_bound_images.size() == 2 &&
|
return executor.m_bound_images.size() == 2 &&
|
||||||
@@ -1432,6 +1440,7 @@ struct GraphicsCase {
|
|||||||
u32 pixel_custom_interpolation_mask = 0;
|
u32 pixel_custom_interpolation_mask = 0;
|
||||||
u32 width = 1;
|
u32 width = 1;
|
||||||
std::vector<u32> object_ids;
|
std::vector<u32> object_ids;
|
||||||
|
std::vector<u32> gds_initial;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CompiledShader {
|
struct CompiledShader {
|
||||||
@@ -5507,6 +5516,93 @@ public:
|
|||||||
ImageHeapTestAccess::ImageOf(heap, filtered.slots[240]),
|
ImageHeapTestAccess::ImageOf(heap, filtered.slots[240]),
|
||||||
"a reactivated view kept an element whose image slot holds a new image");
|
"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);
|
heap.UnmapMemory(table, table_size);
|
||||||
Require(name, "unmapped table",
|
Require(name, "unmapped table",
|
||||||
ImageHeapTestAccess::MemoryCount(heap) == 0u &&
|
ImageHeapTestAccess::MemoryCount(heap) == 0u &&
|
||||||
@@ -5518,6 +5614,13 @@ public:
|
|||||||
acquire(sampled, idle_table) && view.kinds.empty() &&
|
acquire(sampled, idle_table) && view.kinds.empty() &&
|
||||||
ImageHeapTestAccess::MemoryCount(heap) == 1u,
|
ImageHeapTestAccess::MemoryCount(heap) == 1u,
|
||||||
"an all-null table was not kept resident");
|
"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();
|
draw();
|
||||||
context.UnmapMemory(base, idle_table - base);
|
context.UnmapMemory(base, idle_table - base);
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
@@ -7594,6 +7697,40 @@ public:
|
|||||||
verify_array();
|
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
|
// A write through a separate buffer must not replace untouched image
|
||||||
// texels with stale backing bytes. Exact-base writes still refresh it.
|
// texels with stale backing bytes. Exact-base writes still refresh it.
|
||||||
constexpr uint64_t buffer_alias_offset = 0x1a00000;
|
constexpr uint64_t buffer_alias_offset = 0x1a00000;
|
||||||
@@ -10547,6 +10684,98 @@ public:
|
|||||||
std::printf("[gpu] %-32s ok\n", name);
|
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() {
|
void CheckRenderExecutorColorVolumeDiscovery() {
|
||||||
constexpr const char *name = "RenderExecutorColorVolumeDiscovery";
|
constexpr const char *name = "RenderExecutorColorVolumeDiscovery";
|
||||||
constexpr uintptr_t base = 0x0000000203e00000ull;
|
constexpr uintptr_t base = 0x0000000203e00000ull;
|
||||||
@@ -11317,7 +11546,7 @@ public:
|
|||||||
constexpr uintptr_t base = 0x0000000204c00000ull;
|
constexpr uintptr_t base = 0x0000000204c00000ull;
|
||||||
constexpr uint64_t size = 0x10000;
|
constexpr uint64_t size = 0x10000;
|
||||||
constexpr auto output = base + 0x2000;
|
constexpr auto output = base + 0x2000;
|
||||||
constexpr u32 value = 0x80000578u;
|
constexpr u32 value = 0x80000578u, fill = 0xa5a5a5a5u;
|
||||||
std::array<std::vector<u32>, 2> codes;
|
std::array<std::vector<u32>, 2> codes;
|
||||||
for (u32 scalar = 0; scalar < codes.size(); ++scalar) {
|
for (u32 scalar = 0; scalar < codes.size(); ++scalar) {
|
||||||
auto &code = codes[scalar];
|
auto &code = codes[scalar];
|
||||||
@@ -11356,15 +11585,18 @@ public:
|
|||||||
context.MapMemory(base, size);
|
context.MapMemory(base, size);
|
||||||
auto &cache = context.GetBufferCache();
|
auto &cache = context.GetBufferCache();
|
||||||
auto &shaders = processor.GetShCtx();
|
auto &shaders = processor.GetShCtx();
|
||||||
struct Case { u32 mode, records; bool scalar; u32 expected; };
|
// The first case stores through a NumRecords=0 output, which binds the shared null
|
||||||
constexpr Case cases[]{{2, 1, false, value}, {2, 0, false, 0},
|
// 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},
|
{3, 1, false, 0}, {3, 4, false, value},
|
||||||
{2, 1, true, 0}, {2, 4, true, value}};
|
{2, 1, true, 0}, {2, 4, true, value}};
|
||||||
for (const auto wave : {32u, 64u}) {
|
for (const auto wave : {32u, 64u}) {
|
||||||
for (const auto prefix : {0u, 8u}) {
|
for (const auto prefix : {0u, 8u}) {
|
||||||
const auto address = base + 0x1000 + prefix;
|
const auto address = base + 0x1000 + prefix;
|
||||||
for (const auto test : cases) {
|
for (const auto test : cases) {
|
||||||
cache.FillBuffer(output, wave * sizeof(u32), 0xa5a5a5a5u, false);
|
cache.FillBuffer(output, wave * sizeof(u32), fill, false);
|
||||||
shaders.SetCsShader({
|
shaders.SetCsShader({
|
||||||
.data_addr = reinterpret_cast<uint64_t>(codes[test.scalar].data()),
|
.data_addr = reinterpret_cast<uint64_t>(codes[test.scalar].data()),
|
||||||
.num_thread_x = wave, .num_thread_y = 1, .num_thread_z = 1,
|
.num_thread_x = wave, .num_thread_y = 1, .num_thread_z = 1,
|
||||||
@@ -11376,7 +11608,7 @@ public:
|
|||||||
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u) |
|
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u) |
|
||||||
(test.mode << 28u);
|
(test.mode << 28u);
|
||||||
out.UpdateAddress48(output);
|
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) |
|
out.fields[3] = DstSel(4, 5, 6, 7) |
|
||||||
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u) | (3u << 28u);
|
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u) | (3u << 28u);
|
||||||
for (u32 i = 0; i < 4; ++i) {
|
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, 0x0000ffffu, {1.0f, 0.0f}},
|
||||||
PackedCase{vk::Format::eR16G16Unorm, 0x80000001u,
|
PackedCase{vk::Format::eR16G16Unorm, 0x80000001u,
|
||||||
{1.0f / 65535.0f, 32768.0f / 65535.0f}},
|
{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::eR8Unorm, 0xffffff80u, {128.0f / 255.0f}},
|
||||||
PackedCase{vk::Format::eR8G8Unorm, 0xffff40ffu, {1.0f, 64.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}},
|
for (const auto &test : {UintCase{vk::Format::eR8Uint, 0xffffff05u, {5u}},
|
||||||
UintCase{vk::Format::eR8G8Uint, 0xffff0a05u, {5u, 10u}},
|
UintCase{vk::Format::eR8G8Uint, 0xffff0a05u, {5u, 10u}},
|
||||||
UintCase{vk::Format::eR16Uint, 0xffff1234u, {0x1234u}}}) {
|
UintCase{vk::Format::eR16Uint, 0xffff1234u, {0x1234u}},
|
||||||
Require(name, "packed unsigned color clear",
|
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) &&
|
DecodePackedColorClear(test.format, test.word, clear) &&
|
||||||
std::bit_cast<std::array<uint32_t, 4>>(clear.uint32) == test.channels,
|
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",
|
Require(name, "R8 DWORD fill",
|
||||||
!DecodeColorDwordFill(vk::Format::eR8Uint, 0x01020304u, clear) &&
|
!DecodeColorDwordFill(vk::Format::eR8Uint, 0x01020304u, clear) &&
|
||||||
@@ -12329,6 +12571,10 @@ public:
|
|||||||
DecodeColorDwordFill(vk::Format::eR32G32Sfloat, 0x3f800000u, clear) &&
|
DecodeColorDwordFill(vk::Format::eR32G32Sfloat, 0x3f800000u, clear) &&
|
||||||
clear.float32[0] == 1.0f && clear.float32[1] == 1.0f,
|
clear.float32[0] == 1.0f && clear.float32[1] == 1.0f,
|
||||||
"a repeated DWORD fill was decoded as a two-register clear");
|
"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 uintptr_t base = 0x0000000204100000ull;
|
||||||
constexpr uint64_t allocation_size = 0x1000000;
|
constexpr uint64_t allocation_size = 0x1000000;
|
||||||
constexpr uint64_t allocation_alignment = 0x10000;
|
constexpr uint64_t allocation_alignment = 0x10000;
|
||||||
@@ -17755,7 +18001,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u32> ReadBuffer(const char *shader_name, const Buffer &buffer,
|
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) {
|
if (!buffer.coherent) {
|
||||||
vk::MappedMemoryRange range{};
|
vk::MappedMemoryRange range{};
|
||||||
range.sType = vk::StructureType::eMappedMemoryRange;
|
range.sType = vk::StructureType::eMappedMemoryRange;
|
||||||
@@ -17772,7 +18018,7 @@ public:
|
|||||||
m_device.mapMemory(buffer.memory, 0, buffer.size, {}, &data),
|
m_device.mapMemory(buffer.memory, 0, buffer.size, {}, &data),
|
||||||
"vkMapMemory");
|
"vkMapMemory");
|
||||||
std::vector<u32> ret(dword_count, 0);
|
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);
|
m_device.unmapMemory(buffer.memory);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -18002,6 +18248,7 @@ public:
|
|||||||
Require(test.name, "dispatch", info.range <= buffer.size,
|
Require(test.name, "dispatch", info.range <= buffer.size,
|
||||||
"storage buffer descriptor range exceeds backing buffer");
|
"storage buffer descriptor range exceeds backing buffer");
|
||||||
}
|
}
|
||||||
|
shader_data[layout.MemorySizeDword() + index] = static_cast<u32>(info.range);
|
||||||
}
|
}
|
||||||
vk::WriteDescriptorSet write{};
|
vk::WriteDescriptorSet write{};
|
||||||
write.sType = vk::StructureType::eWriteDescriptorSet;
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||||
@@ -19672,6 +19919,40 @@ public:
|
|||||||
}
|
}
|
||||||
Require(name, "packed rounding program distinction", half_up_program != truncate_program,
|
Require(name, "packed rounding program distinction", half_up_program != truncate_program,
|
||||||
"half-up and truncate exports shared one shader specialization");
|
"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);
|
DestroyBuffer(&packed_output);
|
||||||
m_device.destroySampler(packed_sampler);
|
m_device.destroySampler(packed_sampler);
|
||||||
DestroyBuffer(&stencil_readback);
|
DestroyBuffer(&stencil_readback);
|
||||||
@@ -19749,8 +20030,40 @@ public:
|
|||||||
push_constant_range.size = ShaderRecompiler::IR::NativePushConstantSize;
|
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{};
|
vk::PipelineLayoutCreateInfo pipeline_layout_info{};
|
||||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
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 =
|
pipeline_layout_info.pushConstantRangeCount =
|
||||||
push_constant_range.size != 0 ? 1u : 0u;
|
push_constant_range.size != 0 ? 1u : 0u;
|
||||||
pipeline_layout_info.pPushConstantRanges =
|
pipeline_layout_info.pPushConstantRanges =
|
||||||
@@ -19907,6 +20220,10 @@ public:
|
|||||||
}
|
}
|
||||||
cmd.beginRendering(rendering);
|
cmd.beginRendering(rendering);
|
||||||
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
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) {
|
if (push_constant_range.size != 0) {
|
||||||
ShaderRecompiler::IR::PushData push_data;
|
ShaderRecompiler::IR::PushData push_data;
|
||||||
if (object_id) {
|
if (object_id) {
|
||||||
@@ -19934,6 +20251,8 @@ public:
|
|||||||
|
|
||||||
m_device.destroyPipeline(pipeline, nullptr);
|
m_device.destroyPipeline(pipeline, nullptr);
|
||||||
m_device.destroyPipelineLayout(pipeline_layout, 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(fragment_module, nullptr);
|
||||||
m_device.destroyShaderModule(vertex_module, nullptr);
|
m_device.destroyShaderModule(vertex_module, nullptr);
|
||||||
DestroyBuffer(&vertex_buffer);
|
DestroyBuffer(&vertex_buffer);
|
||||||
@@ -21423,8 +21742,9 @@ private:
|
|||||||
"shaderStorageImageWriteWithoutFormat is not supported");
|
"shaderStorageImageWriteWithoutFormat is not supported");
|
||||||
Require("VulkanHarness", "dispatch",
|
Require("VulkanHarness", "dispatch",
|
||||||
available_features.shaderImageGatherExtended == true &&
|
available_features.shaderImageGatherExtended == true &&
|
||||||
|
available_features.shaderResourceMinLod == true &&
|
||||||
available_derivatives.computeDerivativeGroupQuads == 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",
|
Require("VulkanHarness", "dispatch",
|
||||||
available_features11.storageBuffer16BitAccess == true &&
|
available_features11.storageBuffer16BitAccess == true &&
|
||||||
available_features12.storageBuffer8BitAccess == true,
|
available_features12.storageBuffer8BitAccess == true,
|
||||||
@@ -21468,6 +21788,7 @@ private:
|
|||||||
available_features.drawIndirectFirstInstance &&
|
available_features.drawIndirectFirstInstance &&
|
||||||
available_features.multiDrawIndirect &&
|
available_features.multiDrawIndirect &&
|
||||||
available_features.depthBounds &&
|
available_features.depthBounds &&
|
||||||
|
available_features.fragmentStoresAndAtomics &&
|
||||||
available_depth_clip.depthClipEnable &&
|
available_depth_clip.depthClipEnable &&
|
||||||
available_clip_control.depthClipControl &&
|
available_clip_control.depthClipControl &&
|
||||||
available_color_write.colorWriteEnable &&
|
available_color_write.colorWriteEnable &&
|
||||||
@@ -21558,6 +21879,7 @@ private:
|
|||||||
device_features.shaderStorageImageWriteWithoutFormat = true;
|
device_features.shaderStorageImageWriteWithoutFormat = true;
|
||||||
device_features.shaderStorageImageMultisample = available_features.shaderStorageImageMultisample;
|
device_features.shaderStorageImageMultisample = available_features.shaderStorageImageMultisample;
|
||||||
device_features.shaderImageGatherExtended = true;
|
device_features.shaderImageGatherExtended = true;
|
||||||
|
device_features.shaderResourceMinLod = true;
|
||||||
device_features.sampleRateShading = true;
|
device_features.sampleRateShading = true;
|
||||||
device_features.shaderInt64 = true;
|
device_features.shaderInt64 = true;
|
||||||
device_features.shaderFloat64 = available_features.shaderFloat64;
|
device_features.shaderFloat64 = available_features.shaderFloat64;
|
||||||
@@ -21566,6 +21888,7 @@ private:
|
|||||||
device_features.drawIndirectFirstInstance = m_rasterization_supported;
|
device_features.drawIndirectFirstInstance = m_rasterization_supported;
|
||||||
device_features.multiDrawIndirect = m_rasterization_supported;
|
device_features.multiDrawIndirect = m_rasterization_supported;
|
||||||
device_features.depthBounds = m_rasterization_supported;
|
device_features.depthBounds = m_rasterization_supported;
|
||||||
|
device_features.fragmentStoresAndAtomics = m_rasterization_supported;
|
||||||
device_info.pEnabledFeatures = &device_features;
|
device_info.pEnabledFeatures = &device_features;
|
||||||
std::vector<const char *> device_extensions{
|
std::vector<const char *> device_extensions{
|
||||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
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}};
|
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() {
|
TestCase ScalarTrapDisabledFallsThroughExactRaw() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
|
|
||||||
@@ -39088,6 +39428,48 @@ TestCase ImageGatherExplicitLod() {
|
|||||||
return test;
|
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() {
|
template <u32 mode> TestCase ImageGatherLodPerLane() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
constexpr std::array names{
|
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) {
|
void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
||||||
using namespace ShaderRecompiler::IR;
|
using namespace ShaderRecompiler::IR;
|
||||||
TestCase test;
|
TestCase test;
|
||||||
@@ -40707,23 +41133,20 @@ void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
|||||||
test.required_spirv = {"StorageBufferArrayNonUniformIndexing"};
|
test.required_spirv = {"StorageBufferArrayNonUniformIndexing"};
|
||||||
test.forbidden_spirv.push_back("OpSwitch");
|
test.forbidden_spirv.push_back("OpSwitch");
|
||||||
std::set<u32> nonuniform;
|
std::set<u32> nonuniform;
|
||||||
std::vector<u32> accessed_pointers;
|
std::vector<u32> stored_pointers;
|
||||||
u32 prefix_extracts = 0;
|
u32 prefix_extracts = 0;
|
||||||
for (size_t offset = 5; offset < compiled.spirv.size();) {
|
for (size_t offset = 5; offset < compiled.spirv.size();) {
|
||||||
const auto words = std::span(compiled.spirv).subspan(offset, compiled.spirv[offset] >> 16u);
|
const auto words = std::span(compiled.spirv).subspan(offset, compiled.spirv[offset] >> 16u);
|
||||||
const auto opcode = static_cast<spv::Op>(words[0] & 0xffffu);
|
const auto opcode = static_cast<spv::Op>(words[0] & 0xffffu);
|
||||||
if (opcode == spv::OpDecorate && words[2] == spv::DecorationNonUniform)
|
if (opcode == spv::OpDecorate && words[2] == spv::DecorationNonUniform)
|
||||||
nonuniform.insert(words[1]);
|
nonuniform.insert(words[1]);
|
||||||
if (opcode == spv::OpArrayLength) accessed_pointers.push_back(words[3]);
|
if (opcode == spv::OpStore) stored_pointers.push_back(words[1]);
|
||||||
if (opcode == spv::OpStore) accessed_pointers.push_back(words[1]);
|
|
||||||
if (opcode == spv::OpBitFieldUExtract) ++prefix_extracts;
|
if (opcode == spv::OpBitFieldUExtract) ++prefix_extracts;
|
||||||
offset += words.size();
|
offset += words.size();
|
||||||
}
|
}
|
||||||
Require(test.name, "shared store and nonuniform pointers",
|
Require(test.name, "shared store and nonuniform pointers",
|
||||||
prefix_extracts == 1u && accessed_pointers.size() == 2u &&
|
prefix_extracts == 1u && stored_pointers.size() == 1u &&
|
||||||
std::ranges::all_of(accessed_pointers, [&](u32 pointer) {
|
nonuniform.contains(stored_pointers.front()),
|
||||||
return nonuniform.contains(pointer);
|
|
||||||
}),
|
|
||||||
"indirect store retained static prefixes, was duplicated or lost a NonUniform pointer decoration");
|
"indirect store retained static prefixes, was duplicated or lost a NonUniform pointer decoration");
|
||||||
} else if (single) {
|
} else if (single) {
|
||||||
test.forbidden_spirv.push_back("OpSwitch");
|
test.forbidden_spirv.push_back("OpSwitch");
|
||||||
@@ -43826,6 +44249,92 @@ GraphicsCase GraphicsDsAddtidScratchExport() {
|
|||||||
O::DS_READ_ADDTID_B32, O::EXP, O::S_ENDPGM}};
|
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() {
|
GraphicsCase GraphicsDirectSgprPushConstantExport() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
|
|
||||||
@@ -44091,6 +44600,7 @@ std::vector<TestCase> MakeCases() {
|
|||||||
AddCase(ExactPushConstantExtent);
|
AddCase(ExactPushConstantExtent);
|
||||||
AddCase(ScalarShiftCountsMaskLowBits);
|
AddCase(ScalarShiftCountsMaskLowBits);
|
||||||
AddCase(Rdna2ScalarOpcodes);
|
AddCase(Rdna2ScalarOpcodes);
|
||||||
|
AddCase(ScalarApertureLimits);
|
||||||
AddCase(ScalarTrapDisabledFallsThroughExactRaw);
|
AddCase(ScalarTrapDisabledFallsThroughExactRaw);
|
||||||
AddCase(ScalarWaitcntDepctrCapturedVmVsrc);
|
AddCase(ScalarWaitcntDepctrCapturedVmVsrc);
|
||||||
AddCase(ScalarExtendedArithmetic);
|
AddCase(ScalarExtendedArithmetic);
|
||||||
@@ -44611,6 +45121,7 @@ std::vector<TestCase> MakeCases() {
|
|||||||
AddCase(ImageSampleAndGather);
|
AddCase(ImageSampleAndGather);
|
||||||
AddCase(ImageCubeGradientsPreserveDerivatives);
|
AddCase(ImageCubeGradientsPreserveDerivatives);
|
||||||
AddCase(ImageGatherExplicitLod);
|
AddCase(ImageGatherExplicitLod);
|
||||||
|
AddCase(ImageSampleLodClamp);
|
||||||
AddCase(ImageGatherLodPerLane<0>);
|
AddCase(ImageGatherLodPerLane<0>);
|
||||||
AddCase(ImageGatherLodPerLane<1>);
|
AddCase(ImageGatherLodPerLane<1>);
|
||||||
AddCase(ImageGatherLodPerLane<2>);
|
AddCase(ImageGatherLodPerLane<2>);
|
||||||
@@ -44696,6 +45207,9 @@ std::vector<GraphicsCase> MakeGraphicsCases() {
|
|||||||
GraphicsFrontFaceBits(true, true),
|
GraphicsFrontFaceBits(true, true),
|
||||||
GraphicsFlatInterpolatorExport(),
|
GraphicsFlatInterpolatorExport(),
|
||||||
GraphicsDsAddtidScratchExport(),
|
GraphicsDsAddtidScratchExport(),
|
||||||
|
GraphicsDsAppendSkipsHelperLanes(false),
|
||||||
|
GraphicsDsAppendSkipsHelperLanes(true),
|
||||||
|
GraphicsDsOrderedCountSkipsHelperLanes(),
|
||||||
GraphicsDirectSgprPushConstantExport(),
|
GraphicsDirectSgprPushConstantExport(),
|
||||||
GraphicsInlineSrtScalarPromotionExport(),
|
GraphicsInlineSrtScalarPromotionExport(),
|
||||||
GraphicsNullVmExportDiscardsInactiveExec(),
|
GraphicsNullVmExportDiscardsInactiveExec(),
|
||||||
@@ -49787,6 +50301,13 @@ void CheckPm4DrawIndirectMultiPacket(RenderContext &renderer) {
|
|||||||
KYTY_PM4(10, Pm4::IT_DRAW_INDIRECT_MULTI, 0), 0,
|
KYTY_PM4(10, Pm4::IT_DRAW_INDIRECT_MULTI, 0), 0,
|
||||||
auto_packet[2], auto_packet[3], Pm4::SH_NOP, 2, 0, 0, 16, 2};
|
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");
|
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};
|
arguments = {0, 1, 99, 101};
|
||||||
auto_packet[2] = Pm4::SH_NOP;
|
auto_packet[2] = Pm4::SH_NOP;
|
||||||
check_offsets(auto_packet, hs, {53, 101}, "independent destinations");
|
check_offsets(auto_packet, hs, {53, 101}, "independent destinations");
|
||||||
@@ -50686,6 +51207,13 @@ int main(int argc, char **argv) {
|
|||||||
RunCase(&vulkan, Vop3pIntegerNegationCapturedAndSelectedHalves());
|
RunCase(&vulkan, Vop3pIntegerNegationCapturedAndSelectedHalves());
|
||||||
return 0;
|
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) {
|
if (argc == 2 && std::strcmp(argv[1], "--dpp-only") == 0) {
|
||||||
VulkanHarness vulkan;
|
VulkanHarness vulkan;
|
||||||
RunGraphicsCase(&vulkan, GraphicsSparseWaveMinimum());
|
RunGraphicsCase(&vulkan, GraphicsSparseWaveMinimum());
|
||||||
@@ -51021,6 +51549,7 @@ int main(int argc, char **argv) {
|
|||||||
VulkanHarness vulkan;
|
VulkanHarness vulkan;
|
||||||
vulkan.CheckComparisonDepthTexture();
|
vulkan.CheckComparisonDepthTexture();
|
||||||
RunCase(&vulkan, ImageGatherExplicitLod());
|
RunCase(&vulkan, ImageGatherExplicitLod());
|
||||||
|
RunCase(&vulkan, ImageSampleLodClamp());
|
||||||
RunCase(&vulkan, ImageGatherLodPerLane<0>());
|
RunCase(&vulkan, ImageGatherLodPerLane<0>());
|
||||||
RunCase(&vulkan, ImageGatherLodPerLane<1>());
|
RunCase(&vulkan, ImageGatherLodPerLane<1>());
|
||||||
RunCase(&vulkan, ImageGatherLodPerLane<2>());
|
RunCase(&vulkan, ImageGatherLodPerLane<2>());
|
||||||
@@ -51758,6 +52287,7 @@ int main(int argc, char **argv) {
|
|||||||
CheckGuardedWaterfallBufferRecords(vulkan);
|
CheckGuardedWaterfallBufferRecords(vulkan);
|
||||||
CheckGpuSelectedFormattedRecordStore(vulkan);
|
CheckGpuSelectedFormattedRecordStore(vulkan);
|
||||||
CheckFiniteBufferSelection(vulkan);
|
CheckFiniteBufferSelection(vulkan);
|
||||||
|
CheckStorageBufferBindingOver2GiB(vulkan);
|
||||||
CheckRuntimeBufferRecords(vulkan);
|
CheckRuntimeBufferRecords(vulkan);
|
||||||
CheckSharedIndirectBufferLoads(vulkan);
|
CheckSharedIndirectBufferLoads(vulkan);
|
||||||
CheckComputeThreadDimensions(vulkan);
|
CheckComputeThreadDimensions(vulkan);
|
||||||
@@ -51797,6 +52327,7 @@ int main(int argc, char **argv) {
|
|||||||
vulkan.CheckGraphicsPushConstantBank();
|
vulkan.CheckGraphicsPushConstantBank();
|
||||||
vulkan.CheckRenderExecutorColorDiscovery();
|
vulkan.CheckRenderExecutorColorDiscovery();
|
||||||
vulkan.CheckRenderExecutorColor1DArrayDiscovery();
|
vulkan.CheckRenderExecutorColor1DArrayDiscovery();
|
||||||
|
vulkan.CheckColorResolvePlacedTarget();
|
||||||
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
||||||
vulkan.CheckRenderExecutorColorMetadataClear();
|
vulkan.CheckRenderExecutorColorMetadataClear();
|
||||||
vulkan.CheckSampledDccClear();
|
vulkan.CheckSampledDccClear();
|
||||||
|
|||||||
+129
-21
@@ -1847,6 +1847,31 @@ void TestShaderStageBarriers() {
|
|||||||
"SPIR-V barrier does not use workgroup acquire-release memory semantics");
|
"SPIR-V barrier does not use workgroup acquire-release memory semantics");
|
||||||
CheckSpirvBinaryValidates(result.spirv);
|
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 =
|
const auto vertex_result =
|
||||||
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
|
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
|
||||||
CheckSpirvBinaryValidates(vertex_result.spirv);
|
CheckSpirvBinaryValidates(vertex_result.spirv);
|
||||||
@@ -7464,7 +7489,7 @@ void TestNewShaderRecompilerMubufFormatTranslation() {
|
|||||||
CheckSpirvBinaryValidates(result.spirv);
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
|
void TestNewShaderRecompilerFormattedStoreUsesRuntimeBindingSizeOnly() {
|
||||||
const uint32_t shader[] = {
|
const uint32_t shader[] = {
|
||||||
EncodeMubuf0(0x04),
|
EncodeMubuf0(0x04),
|
||||||
EncodeMubuf1(0, 0, 1), // buffer_store_format_x
|
EncodeMubuf1(0, 0, 1), // buffer_store_format_x
|
||||||
@@ -7491,8 +7516,9 @@ void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
|
|||||||
CheckSpirvBinaryValidates(result.spirv);
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
|
|
||||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||||
Check((source.find("OpArrayLength") != std::string::npos),
|
Check(source.find("OpULessThan") != std::string::npos &&
|
||||||
"formatted store SPIR-V lacks runtime storage-buffer bounds check");
|
source.find("OpArrayLength") == std::string::npos,
|
||||||
|
"formatted store SPIR-V lacks a bounds check against its pushed binding size");
|
||||||
Check(
|
Check(
|
||||||
!SpirvSourceHasInstructionUsing(source, "OpULessThan", "%uint_5"),
|
!SpirvSourceHasInstructionUsing(source, "OpULessThan", "%uint_5"),
|
||||||
"formatted store SPIR-V baked descriptor NumRecords into a store guard");
|
"formatted store SPIR-V baked descriptor NumRecords into a store guard");
|
||||||
@@ -9553,6 +9579,28 @@ void TestNativeConstantSamplerSelection() {
|
|||||||
CheckSpirvBinaryValidates(result.spirv);
|
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) {
|
void TestBoundedMaterialBufferStores(bool scalar_key) {
|
||||||
// Captured batch search and descriptor waterfall, with deformation arithmetic removed.
|
// Captured batch search and descriptor waterfall, with deformation arithmetic removed.
|
||||||
const uint32_t shader[] = {
|
const uint32_t shader[] = {
|
||||||
@@ -10616,19 +10664,18 @@ void TestNewShaderRecompilerBufferLoadsGuardedByExec() {
|
|||||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||||
const auto exec_branch =
|
const auto exec_branch =
|
||||||
source.find("OpBranchConditional", 0);
|
source.find("OpBranchConditional", 0);
|
||||||
const auto array_length =
|
const auto bounds = source.find("OpULessThan", 0);
|
||||||
source.find("OpArrayLength", 0);
|
const auto bounds_branch = source.find("OpBranchConditional", bounds);
|
||||||
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
|
||||||
const auto element_access = source.find("OpAccessChain %_ptr_StorageBuffer_uint", 0);
|
const auto element_access = source.find("OpAccessChain %_ptr_StorageBuffer_uint", 0);
|
||||||
Check(exec_branch != std::string::npos,
|
Check(exec_branch != std::string::npos,
|
||||||
"buffer load SPIR-V lacks EXEC guard branch");
|
"buffer load SPIR-V lacks EXEC guard branch");
|
||||||
Check(array_length != std::string::npos,
|
Check(bounds != std::string::npos,
|
||||||
"buffer load SPIR-V lacks storage buffer array-length bounds check");
|
"buffer load SPIR-V lacks storage buffer bounds check");
|
||||||
Check(bounds_branch != std::string::npos,
|
Check(bounds_branch != std::string::npos,
|
||||||
"buffer load SPIR-V lacks storage buffer bounds branch");
|
"buffer load SPIR-V lacks storage buffer bounds branch");
|
||||||
Check(element_access != std::string::npos,
|
Check(element_access != std::string::npos,
|
||||||
"buffer load SPIR-V lacks storage element access");
|
"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");
|
"buffer load bounds check was emitted outside EXEC guard");
|
||||||
Check(bounds_branch < element_access,
|
Check(bounds_branch < element_access,
|
||||||
"buffer load storage element pointer was formed before bounds guard");
|
"buffer load storage element pointer was formed before bounds guard");
|
||||||
@@ -10661,13 +10708,12 @@ void TestNewShaderRecompilerBufferAtomicsGuardedByBounds() {
|
|||||||
CheckSpirvBinaryValidates(result.spirv);
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
|
|
||||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||||
const auto array_length =
|
const auto bounds = source.find("OpULessThan", 0);
|
||||||
source.find("OpArrayLength", 0);
|
const auto bounds_branch = source.find("OpBranchConditional", bounds);
|
||||||
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
|
||||||
const auto atomic = source.find(test.spirv, 0);
|
const auto atomic = source.find(test.spirv, 0);
|
||||||
const auto memory_barrier =
|
const auto memory_barrier =
|
||||||
source.find("OpMemoryBarrier", atomic);
|
source.find("OpMemoryBarrier", atomic);
|
||||||
Check(array_length != std::string::npos &&
|
Check(bounds != std::string::npos &&
|
||||||
bounds_branch != std::string::npos &&
|
bounds_branch != std::string::npos &&
|
||||||
atomic != std::string::npos &&
|
atomic != std::string::npos &&
|
||||||
memory_barrier != 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");
|
"64-bit buffer atomic storage view does not use eight-byte elements");
|
||||||
Check(CountSourceOccurrences(source, "Aliased") == 2u,
|
Check(CountSourceOccurrences(source, "Aliased") == 2u,
|
||||||
"both storage-buffer views must declare that they alias");
|
"both storage-buffer views must declare that they alias");
|
||||||
const auto array_length =
|
const auto bounds = source.find("OpULessThan", 0);
|
||||||
source.find("OpArrayLength", 0);
|
const auto bounds_branch = source.find("OpBranchConditional", bounds);
|
||||||
const auto bounds_branch = source.find("OpBranchConditional", array_length);
|
|
||||||
const auto atomic =
|
const auto atomic =
|
||||||
source.find(test.spirv_name, 0);
|
source.find(test.spirv_name, 0);
|
||||||
const auto memory_barrier =
|
const auto memory_barrier =
|
||||||
source.find("OpMemoryBarrier", atomic);
|
source.find("OpMemoryBarrier", atomic);
|
||||||
Check(array_length != std::string::npos &&
|
Check(bounds != std::string::npos &&
|
||||||
bounds_branch != std::string::npos &&
|
bounds_branch != std::string::npos &&
|
||||||
atomic != std::string::npos && bounds_branch < atomic,
|
atomic != std::string::npos && bounds_branch < atomic,
|
||||||
"64-bit buffer atomic was not guarded by storage-buffer bounds");
|
"64-bit buffer atomic was not guarded by storage-buffer bounds");
|
||||||
@@ -11002,6 +11047,18 @@ void TestMemoryFedScalarLeafCall() {
|
|||||||
ExpectFatal([&] { (void)PrepareShaderSource(multiple, options); },
|
ExpectFatal([&] { (void)PrepareShaderSource(multiple, options); },
|
||||||
"a native return pair was accepted as a later call target");
|
"a native return pair was accepted as a later call target");
|
||||||
#endif
|
#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() {
|
void TestNewShaderRecompilerSetpcBranch() {
|
||||||
@@ -11730,7 +11787,7 @@ void TestMeshInputAssembly() {
|
|||||||
options.user_data_count = 0;
|
options.user_data_count = 0;
|
||||||
options.input_info.vertex = &input;
|
options.input_info.vertex = &input;
|
||||||
auto program = Frontend::TranslateProgram(decoded, graph, options);
|
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};
|
test.address_low, 0x12};
|
||||||
Inst *load = nullptr;
|
Inst *load = nullptr;
|
||||||
for (auto &inst : *program.blocks.front()) {
|
for (auto &inst : *program.blocks.front()) {
|
||||||
@@ -11775,7 +11832,7 @@ void TestMeshInputAssembly() {
|
|||||||
Check(sgprs[2] == test.group_info && sgprs[3] == test.wave_info &&
|
Check(sgprs[2] == test.group_info && sgprs[3] == test.wave_info &&
|
||||||
vgprs[0] == ((test.first << 2) | (test.second << 18)) &&
|
vgprs[0] == ((test.first << 2) | (test.second << 18)) &&
|
||||||
vgprs[1] == test.third * 4 && vgprs[5] == test.vertex_id &&
|
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");
|
"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[] = {
|
const uint32_t local_shader[] = {
|
||||||
EncodeVopc(0xc1, 5 + 256, 8), // v_cmp_lt_u32 vcc, v5, v8
|
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
|
EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc
|
||||||
@@ -13716,6 +13801,7 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
|||||||
|
|
||||||
ShaderPixelInputInfo uint16_info;
|
ShaderPixelInputInfo uint16_info;
|
||||||
uint16_info.target_output_mode[0] = 7;
|
uint16_info.target_output_mode[0] = 7;
|
||||||
|
uint16_info.target_uint_mask = 1;
|
||||||
options.input_info.pixel = &uint16_info;
|
options.input_info.pixel = &uint16_info;
|
||||||
auto uint16_result = RecompileForTest(shader, options);
|
auto uint16_result = RecompileForTest(shader, options);
|
||||||
const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv);
|
const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv);
|
||||||
@@ -13730,6 +13816,18 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
|||||||
"compressed UINT16 MRT export was incorrectly decoded as FP16");
|
"compressed UINT16 MRT export was incorrectly decoded as FP16");
|
||||||
CheckSpirvBinaryValidates(uint16_result.spirv);
|
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;
|
ShaderPixelInputInfo unorm16_info;
|
||||||
unorm16_info.target_output_mode[0] = 5;
|
unorm16_info.target_output_mode[0] = 5;
|
||||||
options.input_info.pixel = &unorm16_info;
|
options.input_info.pixel = &unorm16_info;
|
||||||
@@ -13757,6 +13855,7 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
|||||||
|
|
||||||
ShaderPixelInputInfo uint_info;
|
ShaderPixelInputInfo uint_info;
|
||||||
uint_info.target_output_mode[0] = 7;
|
uint_info.target_output_mode[0] = 7;
|
||||||
|
uint_info.target_uint_mask = 1;
|
||||||
options.input_info.pixel = &uint_info;
|
options.input_info.pixel = &uint_info;
|
||||||
auto partial_uint_result =
|
auto partial_uint_result =
|
||||||
RecompileForTest(partial_shader, options);
|
RecompileForTest(partial_shader, options);
|
||||||
@@ -13783,6 +13882,15 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
|||||||
"compressed UINT16 BA-only export did not read and unpack VSRC1");
|
"compressed UINT16 BA-only export did not read and unpack VSRC1");
|
||||||
CheckSpirvBinaryValidates(compressed_ba_result.spirv);
|
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;
|
unorm16_info.target_output_mode[0] = 5;
|
||||||
options.input_info.pixel = &unorm16_info;
|
options.input_info.pixel = &unorm16_info;
|
||||||
auto unorm16_ba_result =
|
auto unorm16_ba_result =
|
||||||
@@ -16543,7 +16651,6 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
|
|||||||
.variables = 2,
|
.variables = 2,
|
||||||
.loads = 9,
|
.loads = 9,
|
||||||
.stores = 4,
|
.stores = 4,
|
||||||
.array_lengths = 2,
|
|
||||||
.phis = 5,
|
.phis = 5,
|
||||||
.labels = 34,
|
.labels = 34,
|
||||||
.selection_merges = 10,
|
.selection_merges = 10,
|
||||||
@@ -16744,7 +16851,7 @@ int main() {
|
|||||||
TestNewShaderRecompilerNativeWideBufferIr();
|
TestNewShaderRecompilerNativeWideBufferIr();
|
||||||
TestNewShaderRecompilerScalarB64LaneTranslation();
|
TestNewShaderRecompilerScalarB64LaneTranslation();
|
||||||
TestNewShaderRecompilerMubufFormatTranslation();
|
TestNewShaderRecompilerMubufFormatTranslation();
|
||||||
TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly();
|
TestNewShaderRecompilerFormattedStoreUsesRuntimeBindingSizeOnly();
|
||||||
TestNewShaderRecompilerTypedBufferTranslation();
|
TestNewShaderRecompilerTypedBufferTranslation();
|
||||||
TestNewShaderRecompilerDsReadWrite2Translation();
|
TestNewShaderRecompilerDsReadWrite2Translation();
|
||||||
TestNewShaderRecompilerDsWideAndAtomicTranslation();
|
TestNewShaderRecompilerDsWideAndAtomicTranslation();
|
||||||
@@ -16815,6 +16922,7 @@ int main() {
|
|||||||
TestNativeGuardedSamplerSource(false);
|
TestNativeGuardedSamplerSource(false);
|
||||||
TestNativeGuardedSamplerSource(true);
|
TestNativeGuardedSamplerSource(true);
|
||||||
TestNativeConstantSamplerSelection();
|
TestNativeConstantSamplerSelection();
|
||||||
|
TestPackedZeroHighSamplerTableKey();
|
||||||
for (const bool scalar_key : {false, true}) TestBoundedMaterialBufferStores(scalar_key);
|
for (const bool scalar_key : {false, true}) TestBoundedMaterialBufferStores(scalar_key);
|
||||||
TestNativeDescriptorProvenanceKeepsGpuSelection();
|
TestNativeDescriptorProvenanceKeepsGpuSelection();
|
||||||
TestCfgSiblingSharedExit();
|
TestCfgSiblingSharedExit();
|
||||||
|
|||||||
Reference in new issue
Block a user