mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 03:46:53 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 038d2257de | |||
| 190f838ea4 | |||
| 470669c677 | |||
| dd754065d8 | |||
| 743fdc6990 | |||
| 9f83fd5f0c | |||
| 5297abb7ac | |||
| a8beac190f | |||
| b96871e2bf | |||
| 21a1346e27 | |||
| 3ba6ebae81 | |||
| fbf6ae400c | |||
| d2413fc2eb | |||
| 96eb681b90 | |||
| 26cce9909a | |||
| 5af370c257 | |||
| 67d7db685a | |||
| bb98f26433 | |||
| cc44a43ebc | |||
| 2a18e66a4c |
+6
-9
@@ -674,15 +674,12 @@ if(BUILD_TESTING)
|
||||
add_test(NAME color_render_target_1d
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --rt1d-only)
|
||||
|
||||
if(WIN32)
|
||||
# These tests still depend on the Windows multisample-depth path.
|
||||
add_test(NAME texture_cache_image_overlap
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||
add_test(NAME texture_cache_htile_clear
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
endif()
|
||||
add_test(NAME texture_cache_image_overlap
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
|
||||
add_test(NAME texture_cache_htile_clear
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
ime_dialog_tests
|
||||
|
||||
@@ -152,6 +152,10 @@ bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
bool SyncRawImageBuffersEnabled() {
|
||||
return g_config->sync_raw_image_buffers;
|
||||
}
|
||||
|
||||
bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
@@ -164,6 +168,10 @@ bool PlayGoHackEnabled() {
|
||||
return g_config->playgo_hack_enabled;
|
||||
}
|
||||
|
||||
bool SkipNoticeScreen() {
|
||||
return g_config->skip_notice_screen;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled() {
|
||||
return g_config->red_zone_protection_enabled;
|
||||
|
||||
@@ -73,9 +73,11 @@ struct ConfigOptions {
|
||||
bool gpu_assisted_validation_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
bool playgo_hack_enabled = false;
|
||||
bool skip_notice_screen = false;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool red_zone_protection_enabled = false;
|
||||
#endif
|
||||
@@ -123,9 +125,11 @@ bool GpuAssistedValidationEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
bool SyncRawImageBuffersEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool TrophyEnabled();
|
||||
bool PlayGoHackEnabled();
|
||||
bool SkipNoticeScreen();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled();
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,12 @@ enum class ContextStateOperation : uint32_t {
|
||||
PushClear = 3,
|
||||
};
|
||||
|
||||
struct IndirectDrawRegisters {
|
||||
uint32_t vertex_offset;
|
||||
uint32_t instance_offset;
|
||||
uint32_t index_offset;
|
||||
};
|
||||
|
||||
class Pm4Execution {
|
||||
public:
|
||||
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
|
||||
@@ -79,10 +85,11 @@ public:
|
||||
void DrawIndex(DrawIndexArgs args);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
|
||||
void DrawIndexAuto(DrawAutoArgs args);
|
||||
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
uint32_t draw_initiator, bool indexed);
|
||||
IndirectDrawRegisters registers, uint32_t draw_initiator, bool indexed);
|
||||
void WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index,
|
||||
uint32_t event_write_source, void* dst_gpu_addr, uint32_t value,
|
||||
|
||||
@@ -184,7 +184,6 @@ static HW::RenderControl DecodeRenderControl(uint32_t value) {
|
||||
KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_STENCIL_TO_COLOR) != 0;
|
||||
r.copy_centroid = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_CENTROID) != 0;
|
||||
r.copy_sample = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_SAMPLE);
|
||||
EXIT_NOT_IMPLEMENTED(r.copy_depth_to_color || r.copy_stencil_to_color);
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -1642,8 +1641,12 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirect) {
|
||||
const auto data_offset = buffer[0];
|
||||
const auto draw_initiator = buffer[3];
|
||||
const bool indexed = (cmd_id == 0xc0032500);
|
||||
// The native indexed packet enables the high-half first-index destination in word 3.
|
||||
const IndirectDrawRegisters registers {
|
||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
||||
indexed && (buffer[2] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP};
|
||||
|
||||
cp.DrawIndirect(data_offset, draw_initiator, indexed);
|
||||
cp.DrawIndirect(data_offset, registers, draw_initiator, indexed);
|
||||
|
||||
return 4;
|
||||
}
|
||||
@@ -1661,13 +1664,17 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirectMulti) {
|
||||
const auto stride_in_bytes = buffer[7];
|
||||
const auto draw_initiator = buffer[8];
|
||||
const bool indexed = (cmd_id == 0xc0083800);
|
||||
// Multi-draw moves the first-index enable to word 4; the destinations stay in 2/3.
|
||||
const IndirectDrawRegisters registers {
|
||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
||||
indexed && (buffer[3] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP};
|
||||
|
||||
if (count_indirect == 0) {
|
||||
count_addr = nullptr;
|
||||
}
|
||||
|
||||
cp.DrawIndirectMulti(data_offset, max_count_or_count, count_addr, stride_in_bytes,
|
||||
draw_initiator, indexed);
|
||||
registers, draw_initiator, indexed);
|
||||
|
||||
return 9;
|
||||
}
|
||||
|
||||
@@ -875,15 +875,32 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
|
||||
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||
uint32_t vertex_offset, uint32_t instance_offset,
|
||||
uint32_t first_index) {
|
||||
const auto write = [&](uint32_t location, uint32_t value) {
|
||||
if (location == Pm4::SH_NOP) {
|
||||
return;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(location >= Pm4::SH_NUM ||
|
||||
g_hw_sh_indirect_func[location] == nullptr);
|
||||
g_hw_sh_indirect_func[location](cp, location, value);
|
||||
};
|
||||
write(registers.vertex_offset, vertex_offset);
|
||||
write(registers.instance_offset, instance_offset);
|
||||
write(registers.index_offset, first_index);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
DrawIndirectMulti(data_offset, 1, nullptr, args_size, draw_initiator, indexed);
|
||||
DrawIndirectMulti(data_offset, 1, nullptr, args_size, registers, draw_initiator, indexed);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr,
|
||||
uint32_t stride_in_bytes, uint32_t draw_initiator,
|
||||
bool indexed) {
|
||||
uint32_t stride_in_bytes, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u));
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
|
||||
@@ -918,6 +935,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
|
||||
if (!indexed) {
|
||||
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location,
|
||||
args->start_instance_location, 0);
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
@@ -928,6 +947,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location,
|
||||
args->start_instance_location, args->start_index_location);
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size);
|
||||
|
||||
+9
-10
@@ -694,7 +694,9 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
requested.type == cached.info.type && requested.pitch == cached.info.pitch &&
|
||||
!requested.HasStencil() && !cached.info.HasStencil() && !requested.HasMetadata() &&
|
||||
!cached.info.HasMetadata();
|
||||
// PPSA04264
|
||||
// PPSA04264, PPSA04288
|
||||
// A partial view retains the entire matching array layout. HTile belongs to
|
||||
// the depth source, independently of the data slices copied into color storage.
|
||||
const bool retain_cached_layout =
|
||||
requested.samples == 1 && cached.info.samples == 1 && cached.backing.samples == 1 &&
|
||||
requested.bytes_per_block == cached.info.bytes_per_block &&
|
||||
@@ -713,7 +715,8 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
requested.data.size / requested.resources.layers ==
|
||||
cached.info.data.size / cached.info.resources.layers &&
|
||||
!requested.HasStencil() && !cached.info.HasStencil() && !requested.HasMetadata() &&
|
||||
!cached.info.HasMetadata();
|
||||
(cached.info.metadata.kind == ImageMetadataKind::None ||
|
||||
cached.info.metadata.kind == ImageMetadataKind::Htile);
|
||||
bool recreate = cached.info.resources < requested.resources ||
|
||||
requested.IsVolume() != cached.info.IsVolume();
|
||||
switch (binding) {
|
||||
@@ -738,8 +741,6 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
info.data = cached.info.data;
|
||||
info.resources = cached.info.resources;
|
||||
info.mip_layout = cached.info.mip_layout;
|
||||
} else {
|
||||
info.resources = std::max(requested.resources, cached.info.resources);
|
||||
}
|
||||
info.htile_clear_mask = 0;
|
||||
const auto replacement_id = InsertImage(info);
|
||||
@@ -1791,14 +1792,12 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
const auto available = buffer.Size() - buf_offset;
|
||||
uint32_t levels = 0;
|
||||
uint64_t copy_size = 0;
|
||||
if (image.info.IsVolume()) {
|
||||
// Volume mips contain strided block slices, so a mip's linear span cannot prove that
|
||||
// every retained slice fits. Keep volume synchronization whole-image only.
|
||||
if (!buffer.IsInBounds(image.info.data.address, image.info.data.size)) {
|
||||
return false;
|
||||
}
|
||||
if (buffer.IsInBounds(image.info.data.address, image.info.data.size)) {
|
||||
levels = image.info.resources.levels;
|
||||
copy_size = image.info.data.size;
|
||||
} else if (image.info.IsVolume() || image.info.resources.layers > 1) {
|
||||
// Array and volume slices are strided across the full mip chain.
|
||||
return false;
|
||||
} else {
|
||||
for (; levels < image.info.resources.levels; ++levels) {
|
||||
const auto& mip = image.info.mip_layout[levels];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
@@ -126,8 +127,9 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
|
||||
if (size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
|
||||
EXIT("storage buffer range is unsupported\n");
|
||||
}
|
||||
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(address, size, resource.written,
|
||||
resource.formatted, id);
|
||||
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(
|
||||
address, size, resource.written,
|
||||
resource.formatted || (resource.read && Config::SyncRawImageBuffersEnabled()), id);
|
||||
const auto aligned_offset = Common::AlignDown(offset, alignment);
|
||||
const auto adjustment = offset - aligned_offset;
|
||||
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
|
||||
|
||||
@@ -589,8 +589,12 @@ static uint32_t DrawColorOutputMask(const HW::Context& ctx) {
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask;
|
||||
uint32_t output_mask = 0;
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
if (sh_regs.target_output_mode[slot] != 0 &&
|
||||
for (uint32_t index = 0; index < RENDER_COLOR_ATTACHMENTS_MAX; index++) {
|
||||
const auto slot = ShaderPixelExportTarget(sh_regs.m_cbShaderMask, index);
|
||||
if (slot >= RENDER_COLOR_ATTACHMENTS_MAX) {
|
||||
break;
|
||||
}
|
||||
if (sh_regs.target_output_mode[index] != 0 &&
|
||||
render_target_mask_slot(write_mask, slot) != 0) {
|
||||
output_mask |= 1u << slot;
|
||||
}
|
||||
@@ -902,7 +906,7 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal
|
||||
if (state.ps_active) {
|
||||
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt) {
|
||||
mrt_mask |= 1u << output.index;
|
||||
mrt_mask |= 1u << output.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1280,8 +1284,10 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
emit.vertex_offset = vertex_offset + args.base_vertex;
|
||||
emit.first_instance = instance_offset;
|
||||
// Native mesh prologs consume the offsets written to the packet's user SGPRs.
|
||||
const bool mesh_indirect = indirect && state.vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
emit.vertex_offset = mesh_indirect ? 0 : vertex_offset + args.base_vertex;
|
||||
emit.first_instance = mesh_indirect ? 0 : instance_offset;
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
|
||||
primitive_restart);
|
||||
@@ -1369,8 +1375,10 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
DrawEmitInfo emit {};
|
||||
emit.first_vertex = static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = instance_offset;
|
||||
const bool mesh_indirect = indirect && state.vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
emit.first_vertex = mesh_indirect ? 0 :
|
||||
static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = mesh_indirect ? 0 : instance_offset;
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false);
|
||||
|
||||
@@ -195,6 +195,37 @@ std::mutex g_trophy_notification_mutex;
|
||||
std::deque<TrophyNotification> g_trophy_notifications;
|
||||
std::atomic<bool> g_trophy_notification_active {false};
|
||||
|
||||
std::vector<std::string_view> WrapTrophyTitle(std::string_view text, float font_size, float width) {
|
||||
std::vector<std::string_view> lines;
|
||||
auto* const font = ImGui::GetFont();
|
||||
for (;;) {
|
||||
const auto newline = text.find('\n');
|
||||
const auto paragraph = text.substr(0, newline);
|
||||
auto line = paragraph.substr(0, 0);
|
||||
for (auto start = paragraph.find_first_not_of(" \t\r"); start != std::string_view::npos;) {
|
||||
const auto end = std::min(paragraph.find_first_of(" \t\r", start), paragraph.size());
|
||||
const auto word = paragraph.substr(start, end - start);
|
||||
const std::string_view candidate(line.empty() ? word.begin() : line.begin(),
|
||||
word.end());
|
||||
// Keep an oversized word intact; the toast's clip rectangle handles overflow.
|
||||
if (!line.empty() && font->CalcTextSizeA(font_size, std::numeric_limits<float>::max(),
|
||||
0.0f, candidate.data(),
|
||||
candidate.data() + candidate.size()).x > width) {
|
||||
lines.push_back(line);
|
||||
line = word;
|
||||
} else {
|
||||
line = candidate;
|
||||
}
|
||||
start = paragraph.find_first_not_of(" \t\r", end);
|
||||
}
|
||||
lines.push_back(line);
|
||||
if (newline == std::string_view::npos) {
|
||||
return lines;
|
||||
}
|
||||
text.remove_prefix(newline + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearInputEvents() {
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
g_input_events.clear();
|
||||
@@ -1101,7 +1132,26 @@ struct SystemOverlay::Impl {
|
||||
0.65f, 1.25f);
|
||||
const float width = std::max(
|
||||
0.0f, std::min(520.0f * screen_scale, static_cast<float>(frame_extent.width) - 32.0f));
|
||||
const float height = 104.0f * screen_scale;
|
||||
constexpr std::array GRADE_COLORS {
|
||||
IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255), IM_COL32(211, 220, 232, 255),
|
||||
IM_COL32(211, 142, 91, 255)};
|
||||
const int grade = trophy_notification->grade;
|
||||
const bool has_grade = grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size());
|
||||
const float pad = 10.0f * screen_scale;
|
||||
const float base_height = 104.0f * screen_scale;
|
||||
const float icon_size = base_height - 2.0f * pad;
|
||||
const float text_offset = trophy_notification_image != nullptr
|
||||
? pad + icon_size + 18.0f * screen_scale
|
||||
: 20.0f * screen_scale;
|
||||
const float title_offset = text_offset + (has_grade ? 30.0f * screen_scale : 0.0f);
|
||||
const float title_size = 26.0f * screen_scale;
|
||||
const float line_height = 30.0f * screen_scale;
|
||||
const float text_padding = 16.0f * screen_scale;
|
||||
const auto title_lines = WrapTrophyTitle(trophy_notification->name, title_size,
|
||||
width - title_offset - text_padding);
|
||||
const float block_height =
|
||||
static_cast<float>(title_lines.size()) * line_height + 28.0f * screen_scale;
|
||||
const float height = std::max(base_height, block_height + 2.0f * text_padding);
|
||||
const float rounding = 14.0f * screen_scale;
|
||||
const float fade_in = std::clamp(age / 0.22f, 0.0f, 1.0f);
|
||||
const float fade_out = std::clamp((TOAST_LIFETIME - age) / 0.4f, 0.0f, 1.0f);
|
||||
@@ -1121,26 +1171,19 @@ struct SystemOverlay::Impl {
|
||||
draw->AddRect(top_left, bottom_right, fade(255, 255, 255, 28), rounding,
|
||||
ImDrawFlags_RoundCornersAll, 1.0f);
|
||||
|
||||
const float pad = 10.0f * screen_scale;
|
||||
const float icon_size = height - 2.0f * pad;
|
||||
float text_left = top_left.x + 20.0f * screen_scale;
|
||||
const float text_left = top_left.x + text_offset;
|
||||
if (trophy_notification_image != nullptr) {
|
||||
const ImVec2 icon_min {top_left.x + pad, top_left.y + pad};
|
||||
const ImVec2 icon_min {top_left.x + pad, top_left.y + (height - icon_size) * 0.5f};
|
||||
const ImVec2 icon_max {icon_min.x + icon_size, icon_min.y + icon_size};
|
||||
draw->AddImageRounded(trophy_notification_image->GetTexRef(), icon_min, icon_max,
|
||||
{0, 0}, {1, 1}, fade(255, 255, 255, 255), 8.0f * screen_scale);
|
||||
draw->AddRect(icon_min, icon_max, fade(255, 255, 255, 40), 8.0f * screen_scale,
|
||||
ImDrawFlags_RoundCornersAll, 1.0f);
|
||||
text_left = icon_max.x + 18.0f * screen_scale;
|
||||
}
|
||||
|
||||
constexpr std::array GRADE_COLORS {IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255),
|
||||
IM_COL32(211, 220, 232, 255),
|
||||
IM_COL32(211, 142, 91, 255)};
|
||||
const int grade = trophy_notification->grade;
|
||||
const float title_y = top_left.y + height * 0.5f - 25.0f * screen_scale;
|
||||
float title_x = text_left;
|
||||
if (grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size())) {
|
||||
const float title_y = top_left.y + (height - block_height) * 0.5f;
|
||||
const float title_x = top_left.x + title_offset;
|
||||
if (has_grade) {
|
||||
// Small cup glyph tinted with the trophy grade.
|
||||
const ImU32 cup = ImGui::GetColorU32(GRADE_COLORS[grade - 1], alpha);
|
||||
const float u = screen_scale;
|
||||
@@ -1155,12 +1198,15 @@ struct SystemOverlay::Impl {
|
||||
draw->AddRectFilled({cx - 1.5f * u, cy + 2.0f * u}, {cx + 1.5f * u, cy + 7.0f * u}, cup);
|
||||
draw->AddRectFilled({cx - 5.0f * u, cy + 7.0f * u}, {cx + 5.0f * u, cy + 10.0f * u}, cup,
|
||||
1.5f * u);
|
||||
title_x += 30.0f * u;
|
||||
}
|
||||
draw->PushClipRect(top_left, {bottom_right.x - 16.0f * screen_scale, bottom_right.y}, true);
|
||||
draw->AddText(nullptr, 26.0f * screen_scale, {title_x, title_y},
|
||||
fade(255, 255, 255, 255), trophy_notification->name.c_str());
|
||||
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, title_y + 38.0f * screen_scale},
|
||||
draw->PushClipRect(top_left, {bottom_right.x - text_padding, bottom_right.y}, true);
|
||||
float line_y = title_y;
|
||||
for (const auto line : title_lines) {
|
||||
draw->AddText(nullptr, title_size, {title_x, line_y}, fade(255, 255, 255, 255),
|
||||
line.data(), line.data() + line.size());
|
||||
line_y += line_height;
|
||||
}
|
||||
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, line_y + 8.0f * screen_scale},
|
||||
fade(176, 180, 192, 255), "Trophy earned!");
|
||||
draw->PopClipRect();
|
||||
}
|
||||
|
||||
@@ -280,6 +280,10 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::StoreCompletion: requirements.subgroup_barrier = true; break;
|
||||
case IR::ValueOpcode::BvhIntersect: requirements.bvh = true; break;
|
||||
case IR::ValueOpcode::ConditionRef:
|
||||
requirements.subgroup_ballot |=
|
||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||
break;
|
||||
case IR::ValueOpcode::Ballot: requirements.subgroup_ballot = true; break;
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
|
||||
@@ -42,8 +42,9 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
EmitBinaryU32(state, spv::OpIMul, group, ConstantU32(state, size)));
|
||||
}
|
||||
const bool centroid = kind == IR::StageInputKind::BaryCoordSmoothCentroid;
|
||||
const bool sample = kind == IR::StageInputKind::BaryCoordSmoothSample;
|
||||
const auto variable = InputVariableForKind(
|
||||
state, centroid ? IR::StageInputKind::BaryCoordSmooth : kind);
|
||||
state, centroid || sample ? IR::StageInputKind::BaryCoordSmooth : kind);
|
||||
if (variable == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
@@ -76,15 +77,24 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
if (centroid || kind == IR::StageInputKind::BaryCoordSmooth ||
|
||||
if (centroid || sample || kind == IR::StageInputKind::BaryCoordSmooth ||
|
||||
kind == IR::StageInputKind::BaryCoordNoPerspective) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
if (centroid) {
|
||||
if (centroid || sample) {
|
||||
const auto coordinates = state.builder.AllocateId();
|
||||
state.builder.RequireCapability(spv::CapabilityInterpolationFunction);
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
|
||||
GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable);
|
||||
if (sample) {
|
||||
const auto sample_id = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), sample_id,
|
||||
InputVariableForKind(state, IR::StageInputKind::SampleId));
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
|
||||
GlslStd450(state), GLSLstd450InterpolateAtSample,
|
||||
variable, sample_id);
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
|
||||
GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), value,
|
||||
coordinates, component + 1u);
|
||||
} else {
|
||||
@@ -252,8 +262,12 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
bool uint_output) {
|
||||
auto& state = ctx.state;
|
||||
if (exp.compr && !uint_output) {
|
||||
const auto unpack =
|
||||
MrtOutputMode(state, exp) == 5u ? GLSLstd450UnpackUnorm2x16 : GLSLstd450UnpackHalf2x16;
|
||||
auto unpack = GLSLstd450UnpackHalf2x16;
|
||||
switch (MrtOutputMode(state, exp)) {
|
||||
case 5: unpack = GLSLstd450UnpackUnorm2x16; break;
|
||||
case 6: unpack = GLSLstd450UnpackSnorm2x16; break;
|
||||
default: break;
|
||||
}
|
||||
uint32_t f32[4] = {ConstantF32(state, 0), ConstantF32(state, 0), ConstantF32(state, 0),
|
||||
ConstantF32(state, 0x3f800000u)};
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
@@ -699,26 +713,18 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (ctx.other_half == nullptr) return ctx.Arg(inst, 0);
|
||||
// A native scalar branch makes one decision for both emulated wave halves.
|
||||
if (ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
|
||||
// Scalar branches include lanes disabled by EXEC, which the branch body may restore.
|
||||
if (ctx.other_half != nullptr && ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
|
||||
const auto kind = inst.Flags<CFG::BranchCondition>();
|
||||
if (kind == CFG::BranchCondition::ScalarInstruction) return ctx.Arg(inst, 0);
|
||||
const auto ballot = ctx.Ballot(inst.Arg(0));
|
||||
const auto low = ctx.state.builder.AllocateId();
|
||||
const auto high = ctx.state.builder.AllocateId();
|
||||
const auto combined = ctx.state.builder.AllocateId();
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1);
|
||||
const bool zero = kind == CFG::BranchCondition::ExecZero ||
|
||||
kind == CFG::BranchCondition::VccZero || kind == CFG::BranchCondition::SccZero;
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpBitwiseAnd : spv::OpBitwiseOr,
|
||||
TypeU32(ctx.state), combined, low, high);
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpIEqual : spv::OpINotEqual,
|
||||
TypeBool(ctx.state), result, combined,
|
||||
ConstantU32(ctx.state, zero ? ~0u : 0u));
|
||||
return result;
|
||||
const auto ballot = ctx.Ballot(inst.Arg(0));
|
||||
const auto reference = zero ? ctx.Ballot(IR::Value(true))
|
||||
: ConstantU32CompositeZero(ctx.state, 4);
|
||||
const auto compare = Binary(ctx.state, zero ? spv::OpIEqual : spv::OpINotEqual,
|
||||
TypeBoolVector(ctx.state, 4), ballot, reference);
|
||||
return Unary(ctx.state, zero ? spv::OpAll : spv::OpAny, TypeBool(ctx.state), compare);
|
||||
}
|
||||
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
|
||||
|
||||
@@ -716,18 +716,38 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
|
||||
return LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource);
|
||||
}
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
const auto value = EmitValueOrDefaultIfCondition(
|
||||
ctx.state, plan.in_bounds, TypeU32(ctx.state),
|
||||
FormattedOutOfBoundsValue(ctx, mem, plan, 1u),
|
||||
[&]() { return LoadFormattedInBounds(ctx, mem, plan, 0u); });
|
||||
if (mem.data_bits != 16u) {
|
||||
return value;
|
||||
}
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
return EmitUMin32(ctx.state, value, ConstantU32(ctx.state, 0xffffu));
|
||||
}
|
||||
if (info.type == Format::ComponentType::Sint) {
|
||||
const auto lower = EmitSMax32(ctx.state, value, ConstantU32(ctx.state, 0xffff8000u));
|
||||
return EmitSMin32(ctx.state, lower, ConstantU32(ctx.state, 0x7fffu));
|
||||
}
|
||||
return EmitConvertF16F32(ctx.state, EmitBitCastF32U32(ctx.state, value));
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
|
||||
const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data) {
|
||||
uint32_t component, uint32_t data, uint32_t data_bits) {
|
||||
auto& state = ctx.state;
|
||||
const auto bits = info.component_bits[component];
|
||||
if (data_bits == 16u) {
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
} else if (info.type == Format::ComponentType::Sint) {
|
||||
data = EmitBitFieldSExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
} else {
|
||||
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
|
||||
}
|
||||
}
|
||||
const bool normalized = info.type == Format::ComponentType::Unorm ||
|
||||
info.type == Format::ComponentType::Snorm;
|
||||
const bool scaled = info.type == Format::ComponentType::Uscaled ||
|
||||
@@ -779,7 +799,7 @@ void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
value, data, component);
|
||||
}
|
||||
}
|
||||
value = EncodeFormattedStoreComponent(ctx, info, component, value);
|
||||
value = EncodeFormattedStoreComponent(ctx, info, component, value, mem.data_bits);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.packed_bitfield) {
|
||||
packed = EmitBitFieldInsert(
|
||||
|
||||
@@ -63,6 +63,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32},
|
||||
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
|
||||
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
|
||||
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
||||
@@ -296,6 +298,9 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X) {
|
||||
inst.dst.sdwa_sel = 4u;
|
||||
}
|
||||
DecodeVectorGpr(vaddr, inst.src0);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
||||
DecodeScalarSource(soffset, pc, inst.src2);
|
||||
|
||||
@@ -618,10 +618,12 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XY:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_LOAD_UBYTE:
|
||||
case Opcode::BUFFER_LOAD_USHORT:
|
||||
case Opcode::BUFFER_LOAD_DWORD:
|
||||
|
||||
@@ -269,6 +269,7 @@ enum class Opcode {
|
||||
V_MIN3_I32,
|
||||
V_MIN3_U32,
|
||||
V_MIN3_F16,
|
||||
V_MIN3_U16,
|
||||
V_MAX3_F32,
|
||||
V_MAX3_I32,
|
||||
V_MAX3_U32,
|
||||
@@ -455,19 +456,34 @@ enum class Opcode {
|
||||
V_CMP_NE_U32,
|
||||
V_CMP_GE_U32,
|
||||
V_CMP_T_U32,
|
||||
V_CMP_F_I64,
|
||||
V_CMP_EQ_I64,
|
||||
V_CMP_LT_I64,
|
||||
V_CMP_LE_I64,
|
||||
V_CMP_NE_I64,
|
||||
V_CMP_T_I64,
|
||||
V_CMP_F_U64,
|
||||
V_CMP_LT_U64,
|
||||
V_CMP_EQ_U64,
|
||||
V_CMP_LE_U64,
|
||||
V_CMP_GT_U64,
|
||||
V_CMP_NE_U64,
|
||||
V_CMP_GE_U64,
|
||||
V_CMP_T_U64,
|
||||
V_CMPX_F_I64,
|
||||
V_CMPX_LT_I64,
|
||||
V_CMPX_EQ_I64,
|
||||
V_CMPX_LE_I64,
|
||||
V_CMPX_NE_I64,
|
||||
V_CMPX_T_I64,
|
||||
V_CMPX_F_U64,
|
||||
V_CMPX_LT_U64,
|
||||
V_CMPX_EQ_U64,
|
||||
V_CMPX_LE_U64,
|
||||
V_CMPX_GT_U64,
|
||||
V_CMPX_NE_U64,
|
||||
V_CMPX_GE_U64,
|
||||
V_CMPX_T_U64,
|
||||
V_CMPX_LT_U32,
|
||||
V_CMPX_EQ_U32,
|
||||
V_CMPX_LE_U32,
|
||||
@@ -490,10 +506,12 @@ enum class Opcode {
|
||||
BUFFER_LOAD_FORMAT_XY,
|
||||
BUFFER_LOAD_FORMAT_XYZ,
|
||||
BUFFER_LOAD_FORMAT_XYZW,
|
||||
BUFFER_LOAD_FORMAT_D16_X,
|
||||
BUFFER_STORE_FORMAT_X,
|
||||
BUFFER_STORE_FORMAT_XY,
|
||||
BUFFER_STORE_FORMAT_XYZ,
|
||||
BUFFER_STORE_FORMAT_XYZW,
|
||||
BUFFER_STORE_FORMAT_D16_X,
|
||||
BUFFER_LOAD_UBYTE,
|
||||
BUFFER_LOAD_SBYTE,
|
||||
BUFFER_LOAD_USHORT,
|
||||
|
||||
@@ -265,10 +265,18 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false},
|
||||
{0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa3u, Opcode::V_CMP_LE_I64, false},
|
||||
{0xa5u, Opcode::V_CMP_NE_I64, false},
|
||||
{0xa0u, Opcode::V_CMP_F_I64, false}, {0xa7u, Opcode::V_CMP_T_I64, false},
|
||||
{0xb0u, Opcode::V_CMPX_F_I64, false}, {0xb1u, Opcode::V_CMPX_LT_I64, false},
|
||||
{0xb2u, Opcode::V_CMPX_EQ_I64, false}, {0xb3u, Opcode::V_CMPX_LE_I64, false},
|
||||
{0xb7u, Opcode::V_CMPX_T_I64, false},
|
||||
{0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xd1u, Opcode::V_CMPX_LT_U32},
|
||||
{0xd2u, Opcode::V_CMPX_EQ_U32}, {0xd3u, Opcode::V_CMPX_LE_U32},
|
||||
{0xd4u, Opcode::V_CMPX_GT_U32}, {0xd5u, Opcode::V_CMPX_NE_U32},
|
||||
{0xd6u, Opcode::V_CMPX_GE_U32}, {0xe1u, Opcode::V_CMP_LT_U64, false},
|
||||
{0xe0u, Opcode::V_CMP_F_U64, false}, {0xe7u, Opcode::V_CMP_T_U64, false},
|
||||
{0xf0u, Opcode::V_CMPX_F_U64, false}, {0xf1u, Opcode::V_CMPX_LT_U64, false},
|
||||
{0xf2u, Opcode::V_CMPX_EQ_U64, false}, {0xf4u, Opcode::V_CMPX_GT_U64, false},
|
||||
{0xf6u, Opcode::V_CMPX_GE_U64, false}, {0xf7u, Opcode::V_CMPX_T_U64, false},
|
||||
{0xe2u, Opcode::V_CMP_EQ_U64, false}, {0xe3u, Opcode::V_CMP_LE_U64, false},
|
||||
{0xe4u, Opcode::V_CMP_GT_U64, false}, {0xe5u, Opcode::V_CMP_NE_U64, false},
|
||||
{0xe6u, Opcode::V_CMP_GE_U64, false}, {0xf3u, Opcode::V_CMPX_LE_U64, false},
|
||||
@@ -310,6 +318,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x152u, Opcode::V_MIN3_I32},
|
||||
{0x153u, Opcode::V_MIN3_U32},
|
||||
{0x351u, Opcode::V_MIN3_F16},
|
||||
{0x353u, Opcode::V_MIN3_U16},
|
||||
{0x154u, Opcode::V_MAX3_F32},
|
||||
{0x155u, Opcode::V_MAX3_I32},
|
||||
{0x156u, Opcode::V_MAX3_U32},
|
||||
@@ -460,8 +469,9 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
|
||||
opcode == Opcode::V_MED3_F16 || opcode == Opcode::V_FMA_F16;
|
||||
}
|
||||
|
||||
bool IsNativeVop3I16TernaryOpcode(Opcode opcode) {
|
||||
return opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MAD_I16;
|
||||
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
|
||||
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 ||
|
||||
opcode == Opcode::V_MAD_I16;
|
||||
}
|
||||
|
||||
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
|
||||
@@ -1367,7 +1377,7 @@ void ApplyNativeVop3TernaryModifiers(Instruction& inst, uint32_t op_sel, uint32_
|
||||
inst.dst.sdwa_sel = ((op_sel & 0x8u) != 0) ? 5u : 4u;
|
||||
}
|
||||
|
||||
void ApplyNativeVop3I16TernarySelectors(Instruction& inst, uint32_t op_sel) {
|
||||
void ApplyNativeVop3B16TernarySelectors(Instruction& inst, uint32_t op_sel) {
|
||||
Operand* sources[] = {&inst.src0, &inst.src1, &inst.src2};
|
||||
for (uint32_t i = 0; i < 3u; i++) {
|
||||
sources[i]->op_sel = ((op_sel >> i) & 1u) != 0;
|
||||
@@ -1486,7 +1496,7 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
|
||||
// Clamp is applied by WriteF16 for every f16 ternary; omod only for V_FMA_F16.
|
||||
return opcode != Opcode::V_FMA_F16 && omod != 0u;
|
||||
}
|
||||
if (IsNativeVop3I16TernaryOpcode(opcode)) {
|
||||
if (IsNativeVop3B16TernaryOpcode(opcode)) {
|
||||
return abs != 0u || (clamp != 0u && !clamp_modifier) || omod != 0u || neg != 0u;
|
||||
}
|
||||
if (IsNativeVop3B16BinaryOpcode(opcode)) {
|
||||
@@ -1555,9 +1565,20 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GT_U32:
|
||||
case Opcode::V_CMPX_NE_U32:
|
||||
case Opcode::V_CMPX_GE_U32:
|
||||
case Opcode::V_CMPX_F_I64:
|
||||
case Opcode::V_CMPX_LT_I64:
|
||||
case Opcode::V_CMPX_EQ_I64:
|
||||
case Opcode::V_CMPX_LE_I64:
|
||||
case Opcode::V_CMPX_NE_I64:
|
||||
case Opcode::V_CMPX_T_I64:
|
||||
case Opcode::V_CMPX_F_U64:
|
||||
case Opcode::V_CMPX_LT_U64:
|
||||
case Opcode::V_CMPX_EQ_U64:
|
||||
case Opcode::V_CMPX_LE_U64:
|
||||
case Opcode::V_CMPX_GT_U64:
|
||||
case Opcode::V_CMPX_NE_U64:
|
||||
case Opcode::V_CMPX_GE_U64:
|
||||
case Opcode::V_CMPX_T_U64:
|
||||
case Opcode::V_CMPX_LT_I16:
|
||||
case Opcode::V_CMPX_EQ_I16:
|
||||
case Opcode::V_CMPX_LE_I16:
|
||||
@@ -1712,7 +1733,7 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
const bool vop3b_uses_sdst = carry_in_out || vop3b_carry_out || vop3b_mad_u64;
|
||||
const bool mad_mix = false;
|
||||
const bool f16_ternary = IsNativeVop3F16TernaryOpcode(inst.opcode);
|
||||
const bool i16_ternary = IsNativeVop3I16TernaryOpcode(inst.opcode);
|
||||
const bool b16_ternary = IsNativeVop3B16TernaryOpcode(inst.opcode);
|
||||
const bool b16_binary = IsNativeVop3B16BinaryOpcode(inst.opcode);
|
||||
const bool pack_b32_f16 = inst.opcode == Opcode::V_PACK_B32_F16;
|
||||
const bool permlane = IsPermlaneOpcode(inst.opcode);
|
||||
@@ -1823,8 +1844,8 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
ApplyNativeVop3TernaryModifiers(inst, op_sel, abs, neg);
|
||||
} else if (f16_ternary) {
|
||||
ApplyNativeVop3TernaryModifiers(inst, op_sel, abs, neg);
|
||||
} else if (i16_ternary) {
|
||||
ApplyNativeVop3I16TernarySelectors(inst, op_sel);
|
||||
} else if (b16_ternary) {
|
||||
ApplyNativeVop3B16TernarySelectors(inst, op_sel);
|
||||
} else if (b16_binary) {
|
||||
ApplyNativeVop3B16BinaryModifiers(inst, op_sel);
|
||||
} else if (pack_b32_f16) {
|
||||
|
||||
@@ -31,6 +31,7 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
|
||||
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
||||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMP_EQ_I64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMPX_EQ_I64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMP_NE_I64 ||
|
||||
inst.opcode == Decoder::Opcode::V_CMPX_NE_I64;
|
||||
const auto read = [&](const Decoder::Operand& operand) {
|
||||
|
||||
@@ -31,11 +31,12 @@ void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
}
|
||||
|
||||
void Translator::V_MED3_I16(const Decoder::Instruction& inst) {
|
||||
void Translator::Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool sign) {
|
||||
const auto result = IR::U32(ir.Emit(
|
||||
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
|
||||
ReadU16AsU32(inst.src2, true)}));
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
opcode, {ReadU16AsU32(inst.src0, sign), ReadU16AsU32(inst.src1, sign),
|
||||
ReadU16AsU32(inst.src2, sign)}));
|
||||
Write16Bits(DestinationOperand(inst), result);
|
||||
}
|
||||
|
||||
void Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
|
||||
@@ -187,7 +187,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
|
||||
if (decoded.family == Decoder::Family::MUBUF || decoded.family == Decoder::Family::MTBUF) {
|
||||
const bool store_or_atomic =
|
||||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_FORMAT_X &&
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW) ||
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X) ||
|
||||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_BYTE &&
|
||||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_DWORDX4) ||
|
||||
(decoded.opcode >= Decoder::Opcode::TBUFFER_STORE_FORMAT_X &&
|
||||
@@ -427,7 +427,7 @@ void Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
|
||||
void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
IR::ValueOpcode opcode;
|
||||
const auto bits = memory.data_bits;
|
||||
const auto bits = memory.formatted ? 32u : memory.data_bits;
|
||||
const auto sign = memory.data_signed;
|
||||
switch (bits) {
|
||||
case 8u: opcode = IR::ValueOpcode::LoadBufferU8; break;
|
||||
@@ -473,7 +473,8 @@ void Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
|
||||
const auto data = ReadU32(data_src);
|
||||
IR::ValueOpcode opcode;
|
||||
IR::Value value;
|
||||
switch (memory.data_bits) {
|
||||
// Formatted stores carry packed VGPR bits; the format determines their conversion.
|
||||
switch (memory.formatted ? 32u : memory.data_bits) {
|
||||
case 8u:
|
||||
opcode = IR::ValueOpcode::StoreBufferU8;
|
||||
value = NarrowSubdword(data, 8u);
|
||||
@@ -980,6 +981,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XY:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XYZ:
|
||||
@@ -995,6 +997,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XYZ:
|
||||
|
||||
@@ -889,9 +889,16 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_
|
||||
case Decoder::Opcode::V_CMP_GT_U64:
|
||||
case Decoder::Opcode::V_CMP_NE_U64:
|
||||
case Decoder::Opcode::V_CMP_GE_U64:
|
||||
case Decoder::Opcode::V_CMPX_LT_I64:
|
||||
case Decoder::Opcode::V_CMPX_EQ_I64:
|
||||
case Decoder::Opcode::V_CMPX_LE_I64:
|
||||
case Decoder::Opcode::V_CMPX_NE_I64:
|
||||
case Decoder::Opcode::V_CMPX_LT_U64:
|
||||
case Decoder::Opcode::V_CMPX_EQ_U64:
|
||||
case Decoder::Opcode::V_CMPX_LE_U64:
|
||||
case Decoder::Opcode::V_CMPX_GT_U64:
|
||||
case Decoder::Opcode::V_CMPX_NE_U64:
|
||||
case Decoder::Opcode::V_CMPX_GE_U64:
|
||||
include_vector(inst.src0, 2u);
|
||||
include_vector(inst.src1, 2u);
|
||||
break;
|
||||
@@ -1130,7 +1137,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
} else if (options.stage == ShaderType::Mesh) {
|
||||
const auto& mesh = options.input_info.vertex->mesh;
|
||||
EXIT_NOT_IMPLEMENTED(mesh.primitives_per_group == 0u || mesh.vertices_per_group > 64u ||
|
||||
EXIT_NOT_IMPLEMENTED(mesh.primitives_per_group == 0u || mesh.vertices_per_group > 0x1ffu ||
|
||||
mesh.primitives_per_group > 0x1ffu ||
|
||||
total_threads > 15u * options.wave_size);
|
||||
const auto u32 = [](uint32_t value) { return IR::U32(IR::Value(value)); };
|
||||
const auto draw = [&](uint32_t index) {
|
||||
@@ -1166,6 +1174,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
const auto wave_info = entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(wave, u32(24)),
|
||||
u32(((total_threads + options.wave_size - 1u) /
|
||||
options.wave_size) << 28u));
|
||||
entry_ir.SetScalarReg(
|
||||
static_cast<IR::ScalarReg>(2),
|
||||
entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(vertices, u32(12)),
|
||||
entry_ir.ShiftLeftLogical(primitives, u32(22))));
|
||||
entry_ir.SetScalarReg(
|
||||
static_cast<IR::ScalarReg>(3),
|
||||
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
||||
@@ -1274,6 +1286,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
};
|
||||
barycentric_pair(ps->ps_perspective_center_vgpr, IR::StageInputKind::BaryCoordSmooth);
|
||||
barycentric_pair(ps->ps_perspective_sample_vgpr,
|
||||
IR::StageInputKind::BaryCoordSmoothSample);
|
||||
barycentric_pair(ps->ps_perspective_centroid_vgpr,
|
||||
IR::StageInputKind::BaryCoordSmoothCentroid);
|
||||
uint32_t reg = ps->ps_system_input_base;
|
||||
|
||||
@@ -166,8 +166,8 @@ private:
|
||||
void FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
|
||||
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void V_MAD_I16(const Decoder::Instruction& inst);
|
||||
void V_MED3_I16(const Decoder::Instruction& inst);
|
||||
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic);
|
||||
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
|
||||
@@ -96,16 +96,34 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMPX_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
|
||||
return;
|
||||
case O::V_CMP_F_I64:
|
||||
case O::V_CMP_F_U64: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_T_I64:
|
||||
case O::V_CMP_T_U64: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMPX_F_I64:
|
||||
case O::V_CMPX_F_U64: EmitCompareConstant(inst, false, false, true); return;
|
||||
case O::V_CMPX_T_I64:
|
||||
case O::V_CMPX_T_U64: EmitCompareConstant(inst, true, false, true); return;
|
||||
case O::V_CMP_EQ_I64:
|
||||
case O::V_CMP_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_I64:
|
||||
case O::V_CMPX_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_GT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_NE_I64:
|
||||
case O::V_CMP_NE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false);
|
||||
@@ -118,9 +136,15 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMP_LT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
@@ -130,6 +154,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMP_GE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, true);
|
||||
return;
|
||||
|
||||
case O::V_CMP_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, false);
|
||||
@@ -395,7 +422,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
|
||||
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
|
||||
case O::V_MAD_I16: return V_MAD_I16(inst);
|
||||
case O::V_MED3_I16: return V_MED3_I16(inst);
|
||||
case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
|
||||
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
|
||||
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
|
||||
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
|
||||
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
|
||||
|
||||
@@ -196,6 +196,7 @@ enum class StageInputKind {
|
||||
Layer,
|
||||
SampleId,
|
||||
BaryCoordSmooth,
|
||||
BaryCoordSmoothSample,
|
||||
BaryCoordSmoothCentroid,
|
||||
BaryCoordNoPerspective,
|
||||
WorkgroupId,
|
||||
|
||||
@@ -1731,7 +1731,7 @@ private:
|
||||
indirect.table_immediate = table_immediate;
|
||||
indirect.table_stride = table_stride;
|
||||
indirect.workgroup_axis = WorkgroupAxis(key);
|
||||
if (indirect.workgroup_axis != UINT32_MAX && table_source.dword_count == 2u) {
|
||||
if (indirect.workgroup_axis != UINT32_MAX && !plan.table_indexed) {
|
||||
// The descriptor also supplies dimensions to shader arithmetic. Keep its reads;
|
||||
// only the image handle is projected onto the bounded workgroup key.
|
||||
plan.retain_reads = true;
|
||||
|
||||
@@ -143,6 +143,7 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
|
||||
}
|
||||
break;
|
||||
case StageInputKind::BaryCoordSmooth:
|
||||
case StageInputKind::BaryCoordSmoothSample:
|
||||
case StageInputKind::BaryCoordSmoothCentroid:
|
||||
case StageInputKind::BaryCoordNoPerspective:
|
||||
if (component >= 2u) {
|
||||
@@ -299,6 +300,9 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
|
||||
break;
|
||||
case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break;
|
||||
case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break;
|
||||
case StageInputKind::BaryCoordSmoothSample:
|
||||
AddInput(info, StageInputKind::SampleId, 0, 1, "gl_SampleID");
|
||||
[[fallthrough]];
|
||||
case StageInputKind::BaryCoordSmooth:
|
||||
case StageInputKind::BaryCoordSmoothCentroid:
|
||||
AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR");
|
||||
@@ -386,16 +390,23 @@ void CollectOutputs(const Program& program, ShaderStageInputInfo input_info, Sha
|
||||
AddOutput(info, StageOutputKind::Parameter, export_info.index,
|
||||
export_info.index, fmt::format("out_param_{}", export_info.index));
|
||||
break;
|
||||
case ExportTargetKind::Mrt:
|
||||
case ExportTargetKind::Mrt: {
|
||||
if (alpha_remap && export_info.index != 0) {
|
||||
break;
|
||||
}
|
||||
AddOutput(info, StageOutputKind::Mrt, export_info.index, export_info.index,
|
||||
const auto slot = input_info.pixel->dual_source_blending
|
||||
? 0u : ShaderPixelExportTarget(input_info.pixel->target_shader_mask,
|
||||
export_info.index);
|
||||
if (slot >= 8) {
|
||||
break;
|
||||
}
|
||||
AddOutput(info, StageOutputKind::Mrt, export_info.index, slot,
|
||||
fmt::format("out_mrt_{}", export_info.index));
|
||||
if (alpha_remap) {
|
||||
AddOutput(info, StageOutputKind::Mrt, 1, 1, "out_mrt_1");
|
||||
AddOutput(info, StageOutputKind::Mrt, 1, 0, "out_mrt_1");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,6 +525,9 @@ static void ShaderGetStaticInputInfoPS(
|
||||
EXIT_NOT_IMPLEMENTED(ps_info.input_num > std::size(ps_info.interpolator_settings));
|
||||
ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh);
|
||||
const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr;
|
||||
if ((active_inputs & 0x00000001u) != 0) {
|
||||
ps_info.ps_perspective_sample_vgpr = 0;
|
||||
}
|
||||
if ((active_inputs & 0x00000002u) != 0) {
|
||||
ps_info.ps_perspective_center_vgpr = (active_inputs & 0x00000001u) != 0 ? 2u : 0u;
|
||||
}
|
||||
@@ -558,10 +561,12 @@ static void ShaderGetStaticInputInfoPS(
|
||||
ps_info.interpolator_settings[i] = sh.ps_interpolator_settings[i];
|
||||
}
|
||||
|
||||
ps_info.target_shader_mask = sh.m_cbShaderMask;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
const auto slot = ShaderPixelExportTarget(ps_info.target_shader_mask, i);
|
||||
ps_info.target_output_mode[i] = sh.target_output_mode[i];
|
||||
ps_info.target_export_mapping[i] = sh.target_output_mode[i] != 0
|
||||
? target_export_mapping[i]
|
||||
ps_info.target_export_mapping[i] = sh.target_output_mode[i] != 0 && slot < 8
|
||||
? target_export_mapping[slot]
|
||||
: Prospero::ColorComponentMapping {};
|
||||
}
|
||||
}
|
||||
@@ -649,6 +654,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(info.ps_system_input_base);
|
||||
key.push_back(info.custom_interpolation_mask);
|
||||
key.push_back(info.ps_perspective_center_vgpr);
|
||||
key.push_back(info.ps_perspective_sample_vgpr);
|
||||
key.push_back(info.ps_perspective_centroid_vgpr);
|
||||
key.push_back(static_cast<uint32_t>(info.ps_pos_x));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_pos_y));
|
||||
@@ -664,6 +670,8 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(static_cast<uint32_t>(info.dual_source_blending));
|
||||
key.push_back(static_cast<uint32_t>(info.alpha_blend_source));
|
||||
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
|
||||
const auto mask = info.target_shader_mask;
|
||||
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
|
||||
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
|
||||
uint32_t packed = 0;
|
||||
for (uint32_t i = 0; i < 4u; i++) {
|
||||
@@ -697,11 +705,14 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
const auto& sh = context.GetShaderRegisters();
|
||||
const auto [data, hash] = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoVS():");
|
||||
const bool merged = (context.GetShaderStages() & 0x20u) != 0;
|
||||
// GS_EN controls amplification, not whether an NGG shader uses a workgroup.
|
||||
const bool native_ngg = !merged && data.type == Prospero::ShaderBinaryType::kGs &&
|
||||
regs.gs_regs.rsrc2.lds_size != 0;
|
||||
auto params = GetShaderParams(
|
||||
regs.es_regs.data_addr, hash,
|
||||
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data,
|
||||
merged ? 8u : 0u);
|
||||
if (!merged) {
|
||||
merged || native_ngg ? 8u : 0u);
|
||||
if (!merged && !native_ngg) {
|
||||
if (!ShaderGetStaticVertexInputInfo(regs.es_regs.data_addr, regs.gs_user_sgpr,
|
||||
regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) {
|
||||
EXIT("failed to prepare vertex shader program\n");
|
||||
@@ -746,7 +757,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
group.primitive_group_size != 1u || group.vertex_group_size != 1u ||
|
||||
user_vgpr.vgpr1 || user_vgpr.vgpr2 || user_vgpr.vgpr3 ||
|
||||
mesh.max_vertices != sh.m_vgtGsMaxVertOut);
|
||||
} else {
|
||||
} else if (merged) {
|
||||
EXIT_NOT_IMPLEMENTED(regs.gs_regs.rsrc1.gs_vgpr_component_count != 3u ||
|
||||
regs.gs_regs.rsrc2.es_vgpr_component_count != 3u);
|
||||
}
|
||||
@@ -755,7 +766,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriFan &&
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip &&
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriList) ||
|
||||
sh.m_vgtGsOutPrimType != 2u || sh.m_vgtGsMaxVertOut < 3u ||
|
||||
sh.m_vgtGsOutPrimType != 2u || (!native_ngg && sh.m_vgtGsMaxVertOut < 3u) ||
|
||||
group.vertex_group_size < mesh.InputPrimitiveSize() ||
|
||||
mesh.max_vertices == 0u) {
|
||||
EXIT("unsupported GS assembly: input=%u output=%u vertices=%u GE=%u/%u max_output=%u\n",
|
||||
@@ -763,14 +774,16 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
group.primitive_group_size, group.vertex_group_size, mesh.max_vertices);
|
||||
}
|
||||
mesh.max_primitives = mesh.fast_launch ? mesh.max_vertices :
|
||||
native_ngg ? mesh.InputPrimitiveCount(mesh.max_vertices) :
|
||||
group.primitive_group_size * (sh.m_vgtGsMaxVertOut - 2u);
|
||||
mesh.primitives_per_group = std::min({static_cast<uint32_t>(group.primitive_group_size),
|
||||
mesh.InputPrimitiveCount(group.vertex_group_size),
|
||||
mesh.max_vertices / sh.m_vgtGsMaxVertOut});
|
||||
native_ngg ? mesh.max_primitives :
|
||||
mesh.max_vertices / sh.m_vgtGsMaxVertOut});
|
||||
EXIT_IF(mesh.primitives_per_group == 0u);
|
||||
mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group);
|
||||
mesh.threads_num[0] =
|
||||
((mesh.max_vertices + mesh.wave_size - 1u) / mesh.wave_size) * mesh.wave_size;
|
||||
const auto threads = native_ngg && !mesh.fast_launch ? mesh.vertices_per_group : mesh.max_vertices;
|
||||
mesh.threads_num[0] = ((threads + mesh.wave_size - 1u) / mesh.wave_size) * mesh.wave_size;
|
||||
mesh.threads_num[1] = mesh.threads_num[2] = 1u;
|
||||
return params;
|
||||
}
|
||||
@@ -910,6 +923,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) {
|
||||
"\t ps_system_input_base = %u\n"
|
||||
"\t custom_interpolation_mask = 0x%08" PRIx32 "\n"
|
||||
"\t ps_perspective_center_vgpr = %" PRIu32 "\n"
|
||||
"\t ps_perspective_sample_vgpr = %" PRIu32 "\n"
|
||||
"\t ps_perspective_centroid_vgpr = %" PRIu32 "\n"
|
||||
"\t ps_pos_x = %s\n"
|
||||
"\t ps_pos_y = %s\n"
|
||||
@@ -923,7 +937,8 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) {
|
||||
"\t ps_early_z = %s\n"
|
||||
"\t ps_execute_on_noop = %s\n",
|
||||
info.input_num, info.ps_system_input_base, info.custom_interpolation_mask,
|
||||
info.ps_perspective_center_vgpr, info.ps_perspective_centroid_vgpr,
|
||||
info.ps_perspective_center_vgpr, info.ps_perspective_sample_vgpr,
|
||||
info.ps_perspective_centroid_vgpr,
|
||||
info.ps_pos_x ? "true" : "false",
|
||||
info.ps_pos_y ? "true" : "false", info.ps_pos_z ? "true" : "false",
|
||||
info.ps_pos_w ? "true" : "false", info.ps_front_face ? "true" : "false",
|
||||
|
||||
@@ -167,8 +167,10 @@ struct ShaderPixelInputInfo {
|
||||
uint32_t ps_system_input_base = 0;
|
||||
uint32_t custom_interpolation_mask = 0;
|
||||
uint32_t ps_perspective_center_vgpr = UINT32_MAX;
|
||||
uint32_t ps_perspective_sample_vgpr = UINT32_MAX;
|
||||
uint32_t ps_perspective_centroid_vgpr = UINT32_MAX;
|
||||
uint8_t target_output_mode[8] = {};
|
||||
uint32_t target_shader_mask = UINT32_MAX;
|
||||
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
||||
uint32_t scratch_size_dwords = 0;
|
||||
bool ps_pos_x = false;
|
||||
@@ -309,6 +311,7 @@ struct ShaderMappedData {
|
||||
|
||||
void ShaderInit();
|
||||
void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data);
|
||||
uint32_t ShaderPixelExportTarget(uint32_t shader_mask, uint32_t export_index);
|
||||
|
||||
void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info);
|
||||
void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info);
|
||||
|
||||
@@ -11,6 +11,19 @@ constexpr uint32_t PsInputFlatShade = 0x00000400u;
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t ShaderPixelExportTarget(uint32_t shader_mask, uint32_t export_index) {
|
||||
for (uint32_t target = 0; target < 8u; target++) {
|
||||
if (((shader_mask >> (target * 4u)) & 0xfu) == 0u) {
|
||||
continue;
|
||||
}
|
||||
if (export_index == 0u) {
|
||||
return target;
|
||||
}
|
||||
export_index--;
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, uint32_t input) {
|
||||
return input < info.input_num ? info.interpolator_settings[input] & PsInputOffsetMask : input;
|
||||
}
|
||||
|
||||
@@ -179,6 +179,18 @@
|
||||
<property name="text"><string>Auto-hide cursor</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_skip_notice_screen">
|
||||
<property name="toolTip"><string>Skip startup logos and notices in supported games.</string></property>
|
||||
<property name="text"><string>Skip boot logos</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_sync_raw_image_buffers">
|
||||
<property name="toolTip"><string>Can fix missing textures and incorrect transparency in some games. May reduce performance.</string></property>
|
||||
<property name="text"><string>Accurate texture synchronization</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -119,8 +119,10 @@ public:
|
||||
bool fullscreen_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
bool skip_notice_screen = false;
|
||||
int vblank_frequency = 60;
|
||||
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -152,8 +154,10 @@ public:
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||
readback_linear_images = other.readback_linear_images;
|
||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||
tessellation_enabled = other.tessellation_enabled;
|
||||
trophy_enabled = other.trophy_enabled;
|
||||
skip_notice_screen = other.skip_notice_screen;
|
||||
vblank_frequency = other.vblank_frequency;
|
||||
console_language = other.console_language;
|
||||
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
||||
|
||||
@@ -12,8 +12,10 @@ QVariantMap Configuration::GameSettings() const {
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
KYTY_CFG_SET(hide_cursor_enabled);
|
||||
KYTY_CFG_SET(readback_linear_images);
|
||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||
KYTY_CFG_SET(tessellation_enabled);
|
||||
KYTY_CFG_SET(trophy_enabled);
|
||||
KYTY_CFG_SET(skip_notice_screen);
|
||||
KYTY_CFG_SET(vblank_frequency);
|
||||
KYTY_CFG_SET(console_language);
|
||||
KYTY_CFG_SET(vulkan_validation_enabled);
|
||||
@@ -55,8 +57,10 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
KYTY_CFG_GET(fullscreen_enabled);
|
||||
KYTY_CFG_GET(hide_cursor_enabled);
|
||||
KYTY_CFG_GET(readback_linear_images);
|
||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||
KYTY_CFG_GET(tessellation_enabled);
|
||||
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
|
||||
KYTY_CFG_GET(skip_notice_screen);
|
||||
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
|
||||
console_language = s.value("console_language", console_language).toInt();
|
||||
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
|
||||
|
||||
@@ -275,7 +275,9 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
|
||||
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
||||
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
|
||||
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
|
||||
m_ui->comboBox_console_language->clear();
|
||||
@@ -434,7 +436,9 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
|
||||
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
||||
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
|
||||
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
|
||||
info.console_language = ui.comboBox_console_language->currentIndex();
|
||||
|
||||
@@ -245,7 +245,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
args << "--hide-cursor";
|
||||
}
|
||||
args << "--readback-linear-images" << BoolArg(info.readback_linear_images);
|
||||
args << "--sync-raw-image-buffers" << BoolArg(info.sync_raw_image_buffers);
|
||||
args << "--trophy-notifications" << BoolArg(info.trophy_enabled);
|
||||
args << "--skip-notice-screen" << BoolArg(info.skip_notice_screen);
|
||||
if (info.tessellation_enabled) {
|
||||
args << "--tessellation";
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileInfo>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QImage>
|
||||
#include <QLabel>
|
||||
@@ -18,6 +19,7 @@
|
||||
#include <QPainterPath>
|
||||
#include <QPixmap>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSize>
|
||||
#include <QStringList>
|
||||
@@ -25,6 +27,8 @@
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
static const QRegularExpression TrophyFilePattern(QStringLiteral("^trophy(\\d+)\\.ucp$"));
|
||||
@@ -54,17 +58,115 @@ static QString GradeToText(int grade) {
|
||||
}
|
||||
}
|
||||
|
||||
static QString TrophyTooltip(const Common::Trophies::Trophy& trophy) {
|
||||
QStringList lines {QString::fromStdString(trophy.name),
|
||||
QString::fromStdString(trophy.description),
|
||||
QObject::tr("Grade: %1").arg(GradeToText(trophy.grade))};
|
||||
if (trophy.hidden) {
|
||||
lines.append(QObject::tr("Hidden trophy"));
|
||||
QString TrophyName(int id, const Common::Trophies::Trophy& trophy, bool masked) {
|
||||
return masked ? QObject::tr("Hidden trophy")
|
||||
: trophy.name.empty() ? QObject::tr("Trophy %1").arg(id)
|
||||
: QString::fromStdString(trophy.name);
|
||||
}
|
||||
|
||||
QPixmap TrophyIcon(const Common::Trophies::Trophy& trophy, bool unlocked, bool masked, int size) {
|
||||
QPixmap pixmap;
|
||||
if (masked) {
|
||||
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
|
||||
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
|
||||
static_cast<uint>(trophy.icon_png.size())) &&
|
||||
!unlocked) {
|
||||
auto image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
|
||||
for (int y = 0; y < image.height(); ++y) {
|
||||
auto* line = reinterpret_cast<QRgb*>(image.scanLine(y));
|
||||
for (int x = 0; x < image.width(); ++x) {
|
||||
const int gray = qGray(line[x]);
|
||||
line[x] = qRgba(gray, gray, gray, qAlpha(line[x]));
|
||||
}
|
||||
}
|
||||
pixmap = QPixmap::fromImage(image);
|
||||
}
|
||||
if (trophy.has_reward && !trophy.reward.empty()) {
|
||||
lines.append(QObject::tr("Reward: %1").arg(QString::fromStdString(trophy.reward)));
|
||||
return pixmap.isNull()
|
||||
? pixmap
|
||||
: pixmap.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
QString TrophyEarnedDate(int id, const Common::Trophies::UnlockData& unlocks) {
|
||||
const auto found = unlocks.timestamps.find(id);
|
||||
if (found == unlocks.timestamps.end()) {
|
||||
return {};
|
||||
}
|
||||
return lines.join(QLatin1Char('\n'));
|
||||
return QDateTime::fromMSecsSinceEpoch(
|
||||
static_cast<qint64>((found->second - Common::Trophies::UnixEpochTick) / 1000))
|
||||
.toLocalTime()
|
||||
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
void ShowTrophyInspector(QWidget* parent, int id, const Common::Trophies::Trophy& trophy,
|
||||
bool unlocked, const QString& earned_date) {
|
||||
QDialog dialog(parent);
|
||||
dialog.setWindowTitle(QObject::tr("Trophy Details"));
|
||||
dialog.resize(640, 380);
|
||||
auto* layout = new QVBoxLayout(&dialog);
|
||||
auto* top = new QHBoxLayout;
|
||||
auto* icon = new QLabel(&dialog);
|
||||
icon->setFixedSize(148, 148);
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
auto* text_box = new QVBoxLayout;
|
||||
auto* name = new QLabel(&dialog);
|
||||
name->setTextFormat(Qt::PlainText);
|
||||
name->setStyleSheet(QStringLiteral("font-size: 26px;"));
|
||||
name->setWordWrap(true);
|
||||
auto* subtitle = new QLabel(QObject::tr("Hidden trophy"), &dialog);
|
||||
subtitle->setStyleSheet(QStringLiteral("font-size: 16px;"));
|
||||
auto* reveal = new QPushButton(QObject::tr("Show Hidden Trophy"), &dialog);
|
||||
text_box->addWidget(name);
|
||||
text_box->addWidget(subtitle);
|
||||
text_box->addWidget(reveal, 0, Qt::AlignLeft);
|
||||
text_box->addStretch(1);
|
||||
top->addWidget(icon);
|
||||
top->addSpacing(16);
|
||||
top->addLayout(text_box, 1);
|
||||
layout->addLayout(top);
|
||||
|
||||
auto* grid = new QGridLayout;
|
||||
grid->setColumnStretch(1, 1);
|
||||
const auto make_row = [&](const QString& label, const QString& text = {}) {
|
||||
auto* key = new QLabel(label, &dialog);
|
||||
auto* value = new QLabel(text, &dialog);
|
||||
key->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||
value->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||
value->setTextFormat(Qt::PlainText);
|
||||
value->setWordWrap(true);
|
||||
const int row = grid->rowCount();
|
||||
grid->addWidget(key, row, 0);
|
||||
grid->addWidget(value, row, 1);
|
||||
return value;
|
||||
};
|
||||
auto* grade = make_row(QObject::tr("Grade"));
|
||||
make_row(QObject::tr("Status"), unlocked ? QObject::tr("Earned") : QObject::tr("Not earned"));
|
||||
make_row(QObject::tr("Earned date"),
|
||||
unlocked && !earned_date.isEmpty() ? earned_date : QStringLiteral("-"));
|
||||
auto* details = make_row(QObject::tr("Details"));
|
||||
auto* reward =
|
||||
trophy.has_reward && !trophy.reward.empty() ? make_row(QObject::tr("Reward")) : nullptr;
|
||||
layout->addLayout(grid);
|
||||
layout->addStretch(1);
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
|
||||
QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
const auto refresh = [&](bool masked) {
|
||||
icon->setPixmap(TrophyIcon(trophy, unlocked, masked, 148));
|
||||
name->setText(TrophyName(id, trophy, masked));
|
||||
subtitle->setVisible(trophy.hidden && !masked);
|
||||
reveal->setVisible(masked);
|
||||
grade->setText(masked ? QObject::tr("Hidden trophy") : GradeToText(trophy.grade));
|
||||
details->setText(masked || trophy.description.empty()
|
||||
? QStringLiteral("-")
|
||||
: QString::fromStdString(trophy.description));
|
||||
if (reward != nullptr) {
|
||||
reward->setText(masked ? QStringLiteral("-") : QString::fromStdString(trophy.reward));
|
||||
}
|
||||
};
|
||||
QObject::connect(reveal, &QPushButton::clicked, &dialog, [&] { refresh(false); });
|
||||
refresh(trophy.hidden && !unlocked);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -337,13 +439,13 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
}
|
||||
QStringList errors;
|
||||
for (const auto& file: trophy_files) {
|
||||
const auto package =
|
||||
auto package =
|
||||
Common::Trophies::LoadPackage(GameContent::ToPath(file), info.console_language);
|
||||
if (package.trophies.empty()) {
|
||||
errors.append(tr("Could not read trophy package %1.").arg(QFileInfo(file).fileName()));
|
||||
continue;
|
||||
}
|
||||
const auto unlocks = LoadUnlocks(info, runtime_directory, file);
|
||||
auto unlocks = LoadUnlocks(info, runtime_directory, file);
|
||||
const auto counts = Common::Trophies::GetProgress(package, unlocks);
|
||||
auto* page = new QWidget(m_tabs);
|
||||
auto* page_layout = new QVBoxLayout(page);
|
||||
@@ -395,8 +497,10 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
const auto hidden_locked = trophy.hidden && !trophy_unlocked;
|
||||
auto* item = new QListWidgetItem(list);
|
||||
item->setSizeHint(QSize(900, 96));
|
||||
item->setData(Qt::UserRole, id);
|
||||
|
||||
auto* card = new QWidget(list);
|
||||
card->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
card->setStyleSheet(
|
||||
QStringLiteral("QWidget#card { background: palette(base); border-radius: 12px; }"));
|
||||
card->setObjectName(QStringLiteral("card"));
|
||||
@@ -404,26 +508,12 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
auto* icon = new QLabel(card);
|
||||
icon->setFixedSize(76, 76);
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
QPixmap pixmap;
|
||||
if (hidden_locked) {
|
||||
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
|
||||
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
|
||||
static_cast<uint>(trophy.icon_png.size())) &&
|
||||
!trophy_unlocked) {
|
||||
pixmap =
|
||||
QPixmap::fromImage(pixmap.toImage().convertToFormat(QImage::Format_Grayscale8));
|
||||
}
|
||||
if (!pixmap.isNull()) {
|
||||
icon->setPixmap(
|
||||
pixmap.scaled(76, 76, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
icon->setPixmap(TrophyIcon(trophy, trophy_unlocked, hidden_locked, 76));
|
||||
row->addWidget(icon);
|
||||
|
||||
auto* left = new QVBoxLayout;
|
||||
const auto name_text = hidden_locked ? tr("Hidden trophy")
|
||||
: trophy.name.empty() ? tr("Trophy %1").arg(id)
|
||||
: QString::fromStdString(trophy.name);
|
||||
auto* name = new QLabel(name_text, card);
|
||||
auto* left = new QVBoxLayout;
|
||||
auto* name = new QLabel(TrophyName(id, trophy, hidden_locked), card);
|
||||
name->setTextFormat(Qt::PlainText);
|
||||
name->setStyleSheet(QStringLiteral("font-size: 20px; font-weight: bold;"));
|
||||
left->addWidget(name);
|
||||
if (!hidden_locked) {
|
||||
@@ -440,15 +530,9 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
|
||||
row->addLayout(left, 2);
|
||||
|
||||
auto* right = new QVBoxLayout;
|
||||
QString date_text;
|
||||
if (const auto found = unlocks.timestamps.find(id); found != unlocks.timestamps.end()) {
|
||||
date_text = QDateTime::fromMSecsSinceEpoch(
|
||||
static_cast<qint64>(
|
||||
(found->second - Common::Trophies::UnixEpochTick) / 1000))
|
||||
.toLocalTime()
|
||||
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
|
||||
} else if (trophy_unlocked) {
|
||||
auto* right = new QVBoxLayout;
|
||||
auto date_text = TrophyEarnedDate(id, unlocks);
|
||||
if (date_text.isEmpty() && trophy_unlocked) {
|
||||
date_text = tr("Earned");
|
||||
}
|
||||
auto* date = new QLabel(date_text, card);
|
||||
@@ -456,15 +540,22 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
||||
date->setStyleSheet(QStringLiteral("font-size: 15px;"));
|
||||
auto* detail = new QLabel(
|
||||
hidden_locked ? QString {} : QString::fromStdString(trophy.description), card);
|
||||
detail->setTextFormat(Qt::PlainText);
|
||||
detail->setWordWrap(true);
|
||||
detail->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
detail->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||
right->addWidget(date);
|
||||
right->addWidget(detail, 1);
|
||||
row->addLayout(right, 3);
|
||||
card->setToolTip(hidden_locked ? name_text : TrophyTooltip(trophy));
|
||||
list->setItemWidget(item, card);
|
||||
}
|
||||
connect(list, &QListWidget::itemClicked, this,
|
||||
[this, trophies = std::move(package.trophies),
|
||||
unlocks = std::move(unlocks)](QListWidgetItem* item) {
|
||||
const int id = item->data(Qt::UserRole).toInt();
|
||||
ShowTrophyInspector(this, id, trophies.at(id), unlocks.unlocked.contains(id),
|
||||
TrophyEarnedDate(id, unlocks));
|
||||
});
|
||||
m_tabs->addTab(page, QString::fromStdString(package.title));
|
||||
}
|
||||
m_tabs->tabBar()->setVisible(m_tabs->count() > 1);
|
||||
|
||||
@@ -2388,6 +2388,12 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegistersIndirect(CommandBuffer*
|
||||
return cmd;
|
||||
}
|
||||
|
||||
uint64_t KYTY_SYSV_ABI AgcDcbSetShRegistersIndirectGetSize() {
|
||||
PRINT_NAME();
|
||||
|
||||
return 5u * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegistersIndirect(CommandBuffer* buf,
|
||||
const volatile ShaderRegister* regs,
|
||||
uint32_t num_regs) {
|
||||
|
||||
@@ -137,6 +137,7 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetCxRegistersIndirect(CommandBuffer*
|
||||
uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegistersIndirect(CommandBuffer* buf,
|
||||
const volatile ShaderRegister* regs,
|
||||
uint32_t num_regs);
|
||||
uint64_t KYTY_SYSV_ABI AgcDcbSetShRegistersIndirectGetSize();
|
||||
uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegistersIndirect(CommandBuffer* buf,
|
||||
const volatile ShaderRegister* regs,
|
||||
uint32_t num_regs);
|
||||
|
||||
@@ -95,6 +95,7 @@ LIB_DEFINE(InitAgcDriver_1) {
|
||||
LIB_FUNC("w4-d0n60hdo", Gen5::AgcDcbSetUcRegisterDirect);
|
||||
LIB_FUNC("ZvwO9euwYzc", Gen5::AgcDcbSetCxRegistersIndirect);
|
||||
LIB_FUNC("-HOOCn0JY48", Gen5::AgcDcbSetShRegistersIndirect);
|
||||
LIB_FUNC("nNlUtdDDvZ0", Gen5::AgcDcbSetShRegistersIndirectGetSize);
|
||||
LIB_FUNC("hvUfkUIQcOE", Gen5::AgcDcbSetUcRegistersIndirect);
|
||||
LIB_FUNC("GIIW2J37e70", Gen5::AgcDcbSetIndexSize);
|
||||
LIB_FUNC("l4fM9K-Lyks", Gen5::AgcDcbSetIndexBuffer);
|
||||
|
||||
@@ -206,7 +206,7 @@ static int KYTY_SYSV_ABI SystemServiceGetNoticeScreenSkipFlag(bool* value) {
|
||||
return SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
}
|
||||
|
||||
*value = false;
|
||||
*value = Config::SkipNoticeScreen();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -84,9 +84,14 @@ static void PrintUsage() {
|
||||
::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n");
|
||||
::printf(
|
||||
" --readback-linear-images <true|false> Read back writable linear images on submit.\n");
|
||||
::printf(
|
||||
" --sync-raw-image-buffers <true|false> Synchronize raw reads of GPU images. Default: false.\n");
|
||||
::printf(
|
||||
" --trophy-notifications <true|false> Show trophy unlock toasts and play their sound.\n");
|
||||
::printf(" --playgo-hack Use the supplied PlayGo stub fallback.\n");
|
||||
::printf(
|
||||
" --skip-notice-screen <true|false> Skip startup logos and notices in supported games.\n"
|
||||
" Default: false.\n");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
::printf(" --redzone Protect the guest SysV red zone.\n");
|
||||
#endif
|
||||
@@ -423,11 +428,21 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--sync-raw-image-buffers") {
|
||||
if (!ParseBool(value, options.config.sync_raw_image_buffers)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--trophy-notifications") {
|
||||
if (!ParseBool(value, options.config.trophy_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--skip-notice-screen") {
|
||||
if (!ParseBool(value, options.config.skip_notice_screen)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--keymap") {
|
||||
const auto split = value.find('=');
|
||||
if (split == std::string::npos || split == 0 || split + 1 == value.size()) {
|
||||
|
||||
@@ -712,7 +712,7 @@ constexpr u32 EncodeVopcSdwa(u32 src0, u32 sdst = 0, u32 sd = 0,
|
||||
|
||||
constexpr u32 EncodeMubuf0(u32 opcode, u32 offset = 0, bool idxen = false,
|
||||
bool offen = true, bool glc = false) {
|
||||
return (0x38u << 26u) | ((opcode & 0x7fu) << 18u) |
|
||||
return (0x38u << 26u) | ((opcode & 0xffu) << 18u) |
|
||||
(offen ? (1u << 12u) : 0u) | (idxen ? (1u << 13u) : 0u) |
|
||||
(glc ? (1u << 14u) : 0u) | (offset & 0xfffu);
|
||||
}
|
||||
@@ -1344,6 +1344,7 @@ struct GraphicsCase {
|
||||
bool pixel_position_w = false;
|
||||
float vertex_clip_w = 1.0f;
|
||||
bool pixel_depth_export = false;
|
||||
u32 pixel_perspective_sample_vgpr = UINT32_MAX;
|
||||
u32 pixel_perspective_centroid_vgpr = UINT32_MAX;
|
||||
u32 pixel_custom_interpolation_mask = 0;
|
||||
};
|
||||
@@ -1761,6 +1762,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) {
|
||||
pixel_info.ps_pos_w = test.pixel_position_w;
|
||||
pixel_info.ps_depth_export_enable = test.pixel_depth_export;
|
||||
pixel_info.ps_system_input_base = 2;
|
||||
pixel_info.ps_perspective_sample_vgpr = test.pixel_perspective_sample_vgpr;
|
||||
pixel_info.ps_perspective_centroid_vgpr = test.pixel_perspective_centroid_vgpr;
|
||||
pixel_info.custom_interpolation_mask = test.pixel_custom_interpolation_mask;
|
||||
for (u32 i = 0; i < std::size(pixel_info.interpolator_settings); i++) {
|
||||
@@ -5110,7 +5112,7 @@ public:
|
||||
void CheckUnifiedTextureCacheFlow() {
|
||||
constexpr const char *name = "UnifiedTextureCacheFlow";
|
||||
constexpr uintptr_t base = 0x0000000200600000ull;
|
||||
constexpr uint64_t allocation_size = 0x2800000;
|
||||
constexpr uint64_t allocation_size = 0x3000000;
|
||||
constexpr uint64_t allocation_alignment = 0x200000;
|
||||
EnsureRuntimeContext();
|
||||
RenderContext context(m_runtime_context);
|
||||
@@ -5668,6 +5670,81 @@ public:
|
||||
"raw render-target copying overwrote the BC5 guest mip layout");
|
||||
DestroyBuffer(&bc5_readback);
|
||||
|
||||
{
|
||||
constexpr uint64_t tiled_array_offset = 0x2c00000;
|
||||
constexpr uint32_t tiled_levels = 5, tiled_layers = 6;
|
||||
TileSurfaceLayout surface{};
|
||||
Require(name, "tiled array alias layout",
|
||||
TileGetTiledTextureLayout(
|
||||
{Prospero::BufferFormat::k32UInt, Prospero::TileMode::kStandard64KB,
|
||||
TileSurfaceDimension::Dim2D, 256, 256, 1, tiled_levels, tiled_layers},
|
||||
surface) && surface.total_size == 0x240000 &&
|
||||
surface.block_slice_size == 0x60000 &&
|
||||
surface.mips[0].offset + surface.mips[0].size * tiled_layers == 0x1a0000,
|
||||
"fixture lost the gap between an array mip span and its full backing");
|
||||
auto tiled_array = MakeLinearDesc(
|
||||
base + tiled_array_offset, surface.total_size, vk::Format::eR32Uint,
|
||||
Prospero::BufferFormat::k32UInt, Prospero::ImageType::kColor2D,
|
||||
{256, 256, 1}, tiled_layers, sizeof(uint32_t), 1);
|
||||
tiled_array.info.tile_mode = Prospero::TileMode::kStandard64KB;
|
||||
tiled_array.info.resources.levels = tiled_levels;
|
||||
tiled_array.view_info.level_count = tiled_levels;
|
||||
for (uint32_t level = 0; level < tiled_levels; ++level) {
|
||||
const auto &mip = surface.mips[level];
|
||||
tiled_array.info.mip_layout[level] = {
|
||||
mip.offset, mip.size * tiled_layers, mip.padded_width, mip.padded_height};
|
||||
}
|
||||
std::memset(memory + tiled_array_offset, 0, surface.total_size);
|
||||
const auto tiled_array_id = texture_cache.FindImage(tiled_array);
|
||||
(void)texture_cache.FindTexture(tiled_array_id, tiled_array);
|
||||
constexpr uint32_t all_mips = 0x12345678u, last_mip = 0x89abcdefu;
|
||||
vk::ClearValue clear{};
|
||||
clear.color.uint32[0] = all_mips;
|
||||
TextureCacheTestAccess::ClearImage(
|
||||
texture_cache, scheduler.Current(), tiled_array_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, tiled_levels, 0, tiled_layers}, clear);
|
||||
clear.color.uint32[0] = last_mip;
|
||||
TextureCacheTestAccess::ClearImage(
|
||||
texture_cache, scheduler.Current(), tiled_array_id,
|
||||
{vk::ImageAspectFlagBits::eColor, tiled_levels - 1, 1, tiled_layers - 1, 1}, clear);
|
||||
Libs::Graphics::Buffer full_array(
|
||||
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
||||
tiled_array.info.data.address, AllFlags, surface.total_size);
|
||||
Libs::Graphics::Buffer partial_array(
|
||||
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
||||
tiled_array.info.data.address, AllFlags, 0x1a0000);
|
||||
Require(name, "tiled array alias containment",
|
||||
BufferCacheTestAccess::SynchronizeBufferFromImage(
|
||||
resources.GetBufferCache(), full_array,
|
||||
tiled_array.info.data.address, surface.total_size) &&
|
||||
!BufferCacheTestAccess::SynchronizeBufferFromImage(
|
||||
resources.GetBufferCache(), partial_array,
|
||||
tiled_array.info.data.address, 0x1a0000),
|
||||
"full array synchronization failed or accepted an incomplete slice chain");
|
||||
auto readback = CreateHostBuffer(name, 2 * sizeof(uint32_t),
|
||||
vk::BufferUsageFlagBits::eTransferDst, {0, 0});
|
||||
TransferReadBarrier(full_array.Handle(), surface.total_size);
|
||||
for (uint32_t probe = 0; probe < 2; ++probe) {
|
||||
const auto &mip = surface.mips[probe == 0 ? 0 : tiled_levels - 1];
|
||||
uint32_t within_block = 0;
|
||||
Require(name, "tiled array probe address",
|
||||
TileGetBlockOffset(surface.texture.block, mip.tail_x, mip.tail_y,
|
||||
0, within_block),
|
||||
"last-layer mip origin has no guest address");
|
||||
const vk::BufferCopy copy{
|
||||
(tiled_layers - 1) * surface.block_slice_size + mip.offset + within_block,
|
||||
probe * sizeof(uint32_t), sizeof(uint32_t)};
|
||||
command.Handle().copyBuffer(full_array.Handle(), readback.buffer, 1, ©);
|
||||
}
|
||||
HostReadBarrier(readback.buffer, readback.size, vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferWrite);
|
||||
scheduler.Finish();
|
||||
Require(name, "tiled array last-layer contents",
|
||||
ReadBuffer(name, readback, 2) == std::vector<u32>{all_mips, last_mip},
|
||||
"array synchronization lost GPU contents beyond the first mip span");
|
||||
DestroyBuffer(&readback);
|
||||
}
|
||||
|
||||
// A formatted Buffer read must use the private
|
||||
// Exercise the cache-native image-copy path. Use a request larger
|
||||
// than the stream shortcut and poison guest backing
|
||||
@@ -6042,6 +6119,74 @@ public:
|
||||
"sampling");
|
||||
DestroyBuffer(&layered_raw_d16_readback);
|
||||
|
||||
// RE2 converts a prefix of a 32-layer HTile depth array to R32F storage.
|
||||
constexpr uint32_t htile_layers = 32;
|
||||
constexpr uint64_t htile_slice_size = 0x10000;
|
||||
auto htile_depth = MakeLinearDesc(
|
||||
base + 0x2800000, htile_layers * htile_slice_size,
|
||||
vk::Format::eD32Sfloat, Prospero::BufferFormat::k32Float,
|
||||
Prospero::ImageType::kColor2D, {128, 128, 1}, htile_layers, 4, 1);
|
||||
htile_depth.type = BindingType::DepthTarget;
|
||||
htile_depth.info.tile_mode = Prospero::TileMode::kDepth;
|
||||
htile_depth.info.metadata.kind = ImageMetadataKind::Htile;
|
||||
htile_depth.info.metadata.range = {base + 0x2a00000, htile_layers * 0x8000};
|
||||
htile_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
htile_depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
const auto htile_depth_image = texture_cache.FindImage(htile_depth);
|
||||
vk::ClearValue htile_clear{};
|
||||
htile_clear.depthStencil.depth = 0.25f;
|
||||
TextureCacheTestAccess::ClearImage(
|
||||
texture_cache, scheduler.Current(), htile_depth_image,
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 1, 0, htile_layers}, htile_clear);
|
||||
|
||||
auto htile_storage = htile_depth;
|
||||
htile_storage.type = BindingType::Storage;
|
||||
htile_storage.info.pixel_format = vk::Format::eR32Sfloat;
|
||||
htile_storage.info.metadata = {};
|
||||
htile_storage.view_info.format = vk::Format::eR32Sfloat;
|
||||
htile_storage.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
htile_storage.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
||||
ImageId htile_storage_image{};
|
||||
for (uint32_t layers : {1u, 2u}) {
|
||||
htile_storage.info.data.size = layers * htile_slice_size;
|
||||
htile_storage.info.resources.layers = layers;
|
||||
htile_storage.info.mip_layout[0].size = htile_storage.info.data.size;
|
||||
htile_storage.view_info.layer_count = layers;
|
||||
const auto id = texture_cache.FindImage(htile_storage);
|
||||
const auto &owner = texture_cache.GetImage(id);
|
||||
Require(name, "HTile depth storage prefix layout",
|
||||
id != htile_depth_image &&
|
||||
(!htile_storage_image || id == htile_storage_image) &&
|
||||
owner.info.data == htile_depth.info.data &&
|
||||
owner.info.resources == htile_depth.info.resources &&
|
||||
owner.info.mip_layout == htile_depth.info.mip_layout &&
|
||||
owner.backing.layers == htile_layers &&
|
||||
!owner.info.HasMetadata(),
|
||||
"partial storage conversion separated array layers from their guest layout");
|
||||
Require(name, "HTile depth storage prefix view",
|
||||
texture_cache.FindTexture(id, htile_storage) != nullptr,
|
||||
"converted depth prefix has no storage view");
|
||||
htile_storage_image = id;
|
||||
}
|
||||
auto htile_readback = CreateHostBuffer(
|
||||
name, htile_layers * sizeof(float), vk::BufferUsageFlagBits::eTransferDst,
|
||||
std::vector<u32>(htile_layers));
|
||||
vk::BufferImageCopy htile_copy{};
|
||||
htile_copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, htile_layers};
|
||||
htile_copy.imageExtent = {1, 1, 1};
|
||||
texture_cache.GetImage(htile_storage_image).Download(
|
||||
std::span<const vk::BufferImageCopy>(&htile_copy, 1), htile_readback.buffer,
|
||||
0, htile_readback.size);
|
||||
HostReadBarrier(htile_readback.buffer, htile_readback.size,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferWrite);
|
||||
scheduler.Finish();
|
||||
Require(name, "HTile depth storage array contents",
|
||||
ReadBuffer(name, htile_readback, htile_layers) ==
|
||||
std::vector<u32>(htile_layers, std::bit_cast<u32>(0.25f)),
|
||||
"partial storage conversion lost native depth data outside its view");
|
||||
DestroyBuffer(&htile_readback);
|
||||
|
||||
auto layered_stencil_desc = MakeLinearDesc(
|
||||
base + 0x27d4000, 6 * 4 * sizeof(float),
|
||||
vk::Format::eD32SfloatS8Uint, Prospero::BufferFormat::k32Float,
|
||||
@@ -7250,7 +7395,10 @@ public:
|
||||
auto layered_depth = layered_color;
|
||||
layered_depth.type = BindingType::DepthTarget;
|
||||
layered_depth.info.resources = {1, 4};
|
||||
layered_depth.info.mip_layout = {};
|
||||
layered_depth.info.mip_layout[0] = {0, layered_guest_size, 2, 2};
|
||||
layered_depth.view_info.level_count = 1;
|
||||
layered_depth.view_info.layer_count = 4;
|
||||
layered_depth.info.pixel_format = vk::Format::eD32Sfloat;
|
||||
layered_depth.view_info.format = vk::Format::eD32Sfloat;
|
||||
layered_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
@@ -7260,13 +7408,13 @@ public:
|
||||
const auto &layered_native = texture_cache.GetImage(layered_depth_image);
|
||||
Require(name, "layered mipped depth alias",
|
||||
layered_depth_image != layered_color_image &&
|
||||
layered_native.backing.layers == 2 &&
|
||||
layered_native.backing.mip_levels == 2 &&
|
||||
layered_native.backing.layers == 4 &&
|
||||
layered_native.backing.mip_levels == 1 &&
|
||||
layered_native.info.resources ==
|
||||
layered_color.info.resources &&
|
||||
layered_depth.info.resources &&
|
||||
layered_native.info.mip_layout == layered_depth.info.mip_layout &&
|
||||
layered_native.IsGpuModified(),
|
||||
"depth/color conversion did not use the lexicographic "
|
||||
"resource maximum");
|
||||
"depth/color conversion mixed the source mip count with the requested layout");
|
||||
const volatile auto layered_guest_byte =
|
||||
*reinterpret_cast<const volatile uint8_t *>(memory + layered_offset);
|
||||
(void)layered_guest_byte;
|
||||
@@ -7557,29 +7705,19 @@ public:
|
||||
vk::AccessFlagBits::eTransferWrite);
|
||||
|
||||
auto layered_readback = CreateHostBuffer(
|
||||
name, sizeof(layered_values), vk::BufferUsageFlagBits::eTransferDst,
|
||||
std::vector<u32>(sizeof(layered_values) / sizeof(u32), 0));
|
||||
name, 8 * sizeof(float), vk::BufferUsageFlagBits::eTransferDst,
|
||||
std::vector<u32>(8, 0));
|
||||
auto &layered_depth_native = texture_cache.GetImage(layered_depth_image);
|
||||
layered_depth_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {},
|
||||
command.Handle());
|
||||
std::array<vk::BufferImageCopy, 2> layered_copies{};
|
||||
layered_copies[0].bufferOffset = 0;
|
||||
layered_copies[0].imageSubresource.aspectMask =
|
||||
vk::ImageAspectFlagBits::eDepth;
|
||||
layered_copies[0].imageSubresource.mipLevel = 0;
|
||||
layered_copies[0].imageSubresource.layerCount = 2;
|
||||
layered_copies[0].imageExtent = {2, 2, 1};
|
||||
layered_copies[1].bufferOffset = 32;
|
||||
layered_copies[1].imageSubresource.aspectMask =
|
||||
vk::ImageAspectFlagBits::eDepth;
|
||||
layered_copies[1].imageSubresource.mipLevel = 1;
|
||||
layered_copies[1].imageSubresource.layerCount = 2;
|
||||
layered_copies[1].imageExtent = {1, 1, 1};
|
||||
vk::BufferImageCopy layered_copy{};
|
||||
layered_copy.imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, 0, 2};
|
||||
layered_copy.imageExtent = {2, 2, 1};
|
||||
command.Handle().copyImageToBuffer(
|
||||
layered_depth_native.backing.image,
|
||||
vk::ImageLayout::eTransferSrcOptimal, layered_readback.buffer,
|
||||
static_cast<uint32_t>(layered_copies.size()), layered_copies.data());
|
||||
1, &layered_copy);
|
||||
HostReadBarrier(layered_readback.buffer, layered_readback.size,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferWrite);
|
||||
@@ -7792,21 +7930,19 @@ public:
|
||||
std::vector<u32>{0x40004200u, 0x44003c00u},
|
||||
"GPU BGRA16 swap did not publish RGBA half-word order");
|
||||
scheduler.Finish();
|
||||
const auto layered_words =
|
||||
ReadBuffer(name, layered_readback, layered_values.size());
|
||||
const std::array<float, 10> layered_expected{
|
||||
const auto layered_words = ReadBuffer(name, layered_readback, 8);
|
||||
const std::array<float, 8> layered_expected{
|
||||
layered_values[0], layered_values[1], layered_values[2],
|
||||
layered_values[3], layered_values[4], layered_values[5],
|
||||
layered_values[6], layered_values[7], layered_values[8],
|
||||
layered_values[9]};
|
||||
layered_values[6], layered_values[7]};
|
||||
bool layered_content = layered_words.size() == layered_expected.size();
|
||||
for (uint32_t index = 0;
|
||||
layered_content && index < layered_expected.size(); index++) {
|
||||
layered_content &=
|
||||
layered_words[index] == std::bit_cast<u32>(layered_expected[index]);
|
||||
}
|
||||
Require(name, "layered mipped depth content", layered_content,
|
||||
"depth/color conversion changed a mip or array-layer value");
|
||||
Require(name, "layered depth common-mip content", layered_content,
|
||||
"depth/color conversion changed a shared array-layer value");
|
||||
const std::array<std::array<float, 4>, 2> expected_ms_depth{{
|
||||
{0x2000 / 65535.0f, 0xe000 / 65535.0f, 0.0f, 0.0f},
|
||||
{0.0f, 0x4000 / 65535.0f, 0x8000 / 65535.0f, 1.0f},
|
||||
@@ -13886,8 +14022,14 @@ public:
|
||||
"backing");
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
|
||||
{
|
||||
constexpr uint32_t before = 0x13579bdfu;
|
||||
Require(name, "raw image synchronization default",
|
||||
!Config::SyncRawImageBuffersEnabled(),
|
||||
"raw image synchronization should be disabled by default");
|
||||
for (const auto [sync_raw, formatted] :
|
||||
{std::pair{false, false}, {true, false}, {false, true}, {true, true}}) {
|
||||
Config::Load({.sync_raw_image_buffers = sync_raw});
|
||||
const bool sync_image = sync_raw || formatted;
|
||||
const uint32_t before = 0x13579bd0u | (uint32_t(sync_raw) << 1u) | uint32_t(formatted);
|
||||
constexpr uint32_t guard = 0x2468ace0u;
|
||||
constexpr uint32_t after = 0xa1b2c3d4u;
|
||||
vk::ClearValue clear{};
|
||||
@@ -13901,7 +14043,7 @@ public:
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear);
|
||||
|
||||
const auto buffer_program = make_buffer_program(
|
||||
ShaderType::Vertex, {.read = true, .formatted = true});
|
||||
ShaderType::Vertex, {.read = true, .formatted = formatted});
|
||||
|
||||
constexpr uint64_t buffer_size = 2 * target_mip_size;
|
||||
static_assert(buffer_size > BufferCache::CACHING_PAGESIZE);
|
||||
@@ -13924,13 +14066,14 @@ public:
|
||||
RenderExecutorTestAccess::PrepareGraphicsBindings(
|
||||
executor, stages, std::span{&target, 1u});
|
||||
const auto &buffer = buffer_bindings.buffers[0];
|
||||
Require(name, "rediscovered target formatted-buffer read",
|
||||
Require(name, "rediscovered target buffer read",
|
||||
target.image_id == expanded_array_id &&
|
||||
buffer.range == buffer_size &&
|
||||
buffer_bindings.images.empty() &&
|
||||
texture_cache.GetImage(target.image_id).backing.state.layout ==
|
||||
vk::ImageLayout::eTransferSrcOptimal,
|
||||
"target discovery did not precede the formatted image-to-buffer copy");
|
||||
(sync_image ? vk::ImageLayout::eTransferSrcOptimal
|
||||
: vk::ImageLayout::eTransferDstOptimal),
|
||||
"image-to-buffer synchronization ignored its setting or formatted reads");
|
||||
auto readback = CreateHostBuffer(name, 2 * sizeof(uint32_t),
|
||||
vk::BufferUsageFlagBits::eTransferDst, {0, 0});
|
||||
const std::array copies{
|
||||
@@ -13952,12 +14095,12 @@ public:
|
||||
|
||||
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
||||
executor, scheduler.Current(), &target, 1, no_array_depth);
|
||||
Require(name, "attachment layout after formatted-buffer read",
|
||||
Require(name, "attachment layout after buffer read",
|
||||
rendering.color_attachments[0].image_layout ==
|
||||
vk::ImageLayout::eColorAttachmentOptimal &&
|
||||
texture_cache.GetImage(target.image_id).backing.state.layout ==
|
||||
vk::ImageLayout::eColorAttachmentOptimal,
|
||||
"the formatted buffer copy left the render attachment in its transfer layout");
|
||||
"the buffer copy left the render attachment in its transfer layout");
|
||||
vk::ClearAttachment clear_attachment{};
|
||||
clear_attachment.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
clear_attachment.colorAttachment = 0;
|
||||
@@ -13967,17 +14110,19 @@ public:
|
||||
scheduler.Current().Handle().clearAttachments(1, &clear_attachment, 1, &clear_rect);
|
||||
scheduler.EndRendering();
|
||||
scheduler.Finish();
|
||||
Require(name, "formatted alias observes prior GPU contents",
|
||||
ReadBuffer(name, readback, 2) == std::vector<u32>{before, guard},
|
||||
"formatted buffer acquisition copied stale guest bytes instead of the image");
|
||||
Require(name, "buffer alias observes prior GPU contents",
|
||||
ReadBuffer(name, readback, 2) ==
|
||||
(sync_image ? std::vector<u32>{before, guard} : std::vector<u32>{0, 0}),
|
||||
"buffer contents did not match the selected image synchronization mode");
|
||||
DestroyBuffer(&readback);
|
||||
Require(name, "rendering after formatted-buffer acquisition",
|
||||
Require(name, "rendering after buffer acquisition",
|
||||
ReadCachedTexel(name, context, target.image_id) == std::vector<u32>{after} &&
|
||||
ReadCachedTexel(name, context, target.image_id, {}, {1, 1, 1}, 1) ==
|
||||
std::vector<u32>{guard},
|
||||
"attachment rendering lost its new color or changed the untouched array layer");
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
}
|
||||
Config::Load({});
|
||||
|
||||
auto colliding_msaa_texture = array_texture;
|
||||
colliding_msaa_texture.fields[3] =
|
||||
@@ -15549,6 +15694,7 @@ public:
|
||||
HW::UserConfig user_config{};
|
||||
HW::Shader shaders{};
|
||||
registers.SetRenderTargetMask(0xf);
|
||||
registers.SetShaderMask(0xf);
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
auto &resources = context;
|
||||
auto &cache = context.GetTextureCache();
|
||||
@@ -16292,8 +16438,9 @@ public:
|
||||
registers.SetColorAttrib2(slot, {.height = side - 1, .width = side - 1});
|
||||
registers.SetColorAttrib3(slot,
|
||||
{.tile_mode = Prospero::TileMode::kLinear, .dimension = 1});
|
||||
registers.SetTargetOutputMode(slot, 4);
|
||||
}
|
||||
registers.SetTargetOutputMode(0, 4);
|
||||
registers.SetTargetOutputMode(1, 4);
|
||||
stencil_target.z_info.htile_acceleration = true;
|
||||
stencil_target.htile_data_base_addr = depth_address + 0x30000;
|
||||
registers.SetDepthRenderTarget(stencil_target);
|
||||
@@ -16355,7 +16502,7 @@ public:
|
||||
registers.SetDepthShaderControl({});
|
||||
registers.SetDepthControl({.z_enable = true, .z_write_enable = true,
|
||||
.zfunc = static_cast<uint8_t>(vk::CompareOp::eAlways)});
|
||||
for (const auto slot : {0u, 3u}) {
|
||||
for (const auto slot : {0u, 1u}) {
|
||||
registers.SetTargetOutputMode(slot, 0);
|
||||
}
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
@@ -16366,37 +16513,77 @@ public:
|
||||
std::ranges::all_of(depth_only_pixels, [](u32 v) { return v == 0; }),
|
||||
"disabled color exports invoked the stale PS or clipped fixed-function depth");
|
||||
registers.SetPsInControl(0x8000);
|
||||
for (const auto slot : {0u, 3u}) {
|
||||
registers.SetTargetOutputMode(slot, 4);
|
||||
}
|
||||
registers.SetTargetOutputMode(0, 4);
|
||||
|
||||
// With MRT0 still bound, an MRT3-only export must retain location3 in
|
||||
// rendering attachments, pipeline formats, blend masks and dynamic write enables.
|
||||
static const auto sparse_pixel = [] {
|
||||
auto code = native_pixel;
|
||||
*std::ranges::find(code, EncodeExp0(0x00, 0xf)) = EncodeExp0(0x03, 0xf);
|
||||
return code;
|
||||
// Export ordinals follow CB_SHADER_MASK. A disabled CB_TARGET_MASK slot
|
||||
// still consumes an ordinal, and stale MRT0 must not clip physical MRT3.
|
||||
static const auto sparse_pixels_code = [] {
|
||||
std::array<std::vector<u32>, 2> result{native_pixel, native_pixel};
|
||||
auto &code = result[1];
|
||||
const auto exp = std::ranges::find(code, EncodeExp0(0, 0xf));
|
||||
*exp = EncodeExp0(0, 0xf, false);
|
||||
*(exp + 1) = EncodeExp1(0, 0, 0, 0);
|
||||
code.insert(exp + 2, {EncodeExp0(1, 0xf), EncodeExp1(1, 1, 1, 2)});
|
||||
return result;
|
||||
}();
|
||||
const auto sparse_address = reinterpret_cast<uint64_t>(sparse_pixel.data());
|
||||
ShaderMapUserData(sparse_address,
|
||||
{.type = Prospero::ShaderBinaryType::kPs,
|
||||
.code_size_bytes = static_cast<uint32_t>(sparse_pixel.size() * sizeof(u32))});
|
||||
shaders.SetPsShaderBase(sparse_address);
|
||||
registers.SetRenderTargetMask(0xf000);
|
||||
registers.SetDepthControl({});
|
||||
registers.SetDepthShaderControl({});
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
|
||||
RenderColorInfo sparse_color{};
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
||||
executor, scheduler.Current(), sparse_color, 3);
|
||||
const auto sparse_pixels = ReadCachedTexel(name, context, sparse_color.image_id,
|
||||
{}, {extent, extent, 1});
|
||||
for (size_t component = 0; component < sparse_pixels.size(); component++) {
|
||||
const auto expected = component % 4 == 3 ? 0x3f800000u : 0x3e800000u;
|
||||
Require(name, "sparse MRT3 output", sparse_pixels[component] == expected,
|
||||
"MRT3 was compacted to a different slot or clipped by unused MRT0");
|
||||
for (uint32_t index = 0; index < sparse_pixels_code.size(); index++) {
|
||||
const auto &code = sparse_pixels_code[index];
|
||||
const auto address = reinterpret_cast<uint64_t>(code.data());
|
||||
ShaderMapUserData(address,
|
||||
{.type = Prospero::ShaderBinaryType::kPs,
|
||||
.code_size_bytes = static_cast<uint32_t>(code.size() * sizeof(u32))});
|
||||
shaders.SetPsShaderBase(address);
|
||||
registers.SetShaderMask(index == 0 ? 0xf000 : 0xf00f);
|
||||
registers.SetTargetOutputMode(1, index == 0 ? 0 : 4);
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
||||
executor, scheduler.Current(), sparse_color, 3);
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {});
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
|
||||
const auto pixels = ReadCachedTexel(name, context, sparse_color.image_id,
|
||||
{}, {extent, extent, 1});
|
||||
for (size_t component = 0; component < pixels.size(); component++) {
|
||||
const auto expected = component % 4 == 3 ? 0x3f800000u : 0x3e800000u;
|
||||
Require(name, "compact export to sparse MRT3", pixels[component] == expected,
|
||||
"shader output routing used the write mask or clipped MRT3 with stale MRT0");
|
||||
}
|
||||
}
|
||||
|
||||
static const auto snorm_pixel = [] {
|
||||
std::vector<u32> code;
|
||||
AppendVMovLiteral(&code, 0, 0xc0008000u);
|
||||
AppendVMovLiteral(&code, 1, 0x7fff4000u);
|
||||
code.insert(code.end(), {EncodeExp0(0, 0xf, true, true), EncodeExp1(0, 1, 0, 0)});
|
||||
AppendEnd(&code);
|
||||
return code;
|
||||
}();
|
||||
const auto snorm_address = reinterpret_cast<uint64_t>(snorm_pixel.data());
|
||||
ShaderMapUserData(snorm_address,
|
||||
{.type = Prospero::ShaderBinaryType::kPs,
|
||||
.code_size_bytes = static_cast<uint32_t>(snorm_pixel.size() * sizeof(u32))});
|
||||
shaders.SetPsShaderBase(snorm_address);
|
||||
registers.SetShaderMask(0xf000);
|
||||
registers.SetTargetOutputMode(0, 6);
|
||||
registers.SetTargetOutputMode(1, 0);
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
|
||||
const auto snorm_pixels = ReadCachedTexel(name, context, sparse_color.image_id,
|
||||
{}, {extent, extent, 1});
|
||||
constexpr std::array<float, 4> snorm_expected{-1, -16384.0f / 32767,
|
||||
16384.0f / 32767, 1};
|
||||
for (size_t component = 0; component < snorm_pixels.size(); component++) {
|
||||
Require(name, "compressed SNORM16 color export",
|
||||
std::abs(std::bit_cast<float>(snorm_pixels[component]) -
|
||||
snorm_expected[component % 4]) < 0.00001f,
|
||||
"SNORM16 export lost its sign, normalization or negative endpoint clamp");
|
||||
}
|
||||
registers.SetTargetOutputMode(0, 4);
|
||||
|
||||
// A fourth VS invocation reads beyond the descriptor instead of reconstructing the corner.
|
||||
const ShaderBufferResource rect_buffer{{
|
||||
static_cast<u32>(rect_address),
|
||||
@@ -16422,7 +16609,7 @@ public:
|
||||
}
|
||||
AppendVMovU32(&code, 22, 0);
|
||||
AppendVMovLiteral(&code, 23, 0x3f800000u);
|
||||
code.insert(code.end(), {EncodeExp0(0x03, 0xf), EncodeExp1(20, 21, 22, 23)});
|
||||
code.insert(code.end(), {EncodeExp0(0, 0xf), EncodeExp1(20, 21, 22, 23)});
|
||||
AppendEnd(&code);
|
||||
return code;
|
||||
}();
|
||||
@@ -22148,6 +22335,65 @@ TestCase Vop3MadI16CapturedSelectorsAndSaturation() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Vop3Min3U16CapturedAndSelectors() {
|
||||
using O = ShaderOpcode;
|
||||
struct MinCase {
|
||||
u32 lhs, rhs, third, expected;
|
||||
u32 selectors = 0;
|
||||
u32 dst = 17;
|
||||
bool active = true;
|
||||
};
|
||||
constexpr u32 a = 0x0009ea60u, b = 0x0007c350u, c = 0x00059c40u;
|
||||
const std::array<MinCase, 10> cases{{
|
||||
{a, b, c, 0x0005beefu, 14}, // Captured: min(60000, 7, 5) into v17.hi.
|
||||
{a, b, c, 0xa5a59c40u}, // Unsigned values above INT16_MAX.
|
||||
{a, b, c, 0xa5a50009u, 1},
|
||||
{a, b, c, 0xa5a50007u, 2},
|
||||
{a, b, c, 0xa5a50005u, 4},
|
||||
{a, b, c, 0x0005beefu, 15},
|
||||
{a, b, c, 0x9c40ea60u, 8, 4}, // Destination aliases source 0.
|
||||
{0xabcdffffu, 0xffff0000u, 0x98768000u, 0xa5a50000u},
|
||||
{0x0000ffffu, 0x7fffffffu, 0x8000ffffu, 0xa5a5ffffu},
|
||||
{a, b, c, 0xa5a5beefu, 14, 17, false},
|
||||
}};
|
||||
TestCase test;
|
||||
test.name = "Vop3Min3U16CapturedAndSelectors";
|
||||
for (const auto &entry : cases) {
|
||||
test.initial.insert(test.initial.end(), {entry.lhs, entry.rhs, entry.third});
|
||||
}
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
for (u32 i = 0; i < cases.size(); ++i) {
|
||||
const auto &entry = cases[i];
|
||||
u32 offset = i * 12u;
|
||||
for (u32 reg : {4u, 0u, 7u}) {
|
||||
AppendVMovU32(&code, 30, offset);
|
||||
AppendBufferLoadDword(&code, reg, 30); // Runtime inputs prevent folding.
|
||||
offset += 4u;
|
||||
}
|
||||
AppendVMovLiteral(&code, 17, 0xa5a5beefu);
|
||||
if (!entry.active) {
|
||||
code.push_back(EncodeSop1(0x04, 24, 126)); // Preserve the initial active lanes.
|
||||
code.push_back(EncodeSop1(0x04, 126, 128));
|
||||
}
|
||||
if (i == 0u) {
|
||||
code.insert(code.end(), {0xd7537011u, 0x041e0104u});
|
||||
} else {
|
||||
AppendVop3(&code, 0x353, entry.dst, Vgpr(4), Vgpr(0), Vgpr(7), 0,
|
||||
entry.selectors);
|
||||
}
|
||||
if (!entry.active) code.push_back(EncodeSop1(0x04, 126, 24));
|
||||
AppendStoreVgpr(&code, entry.dst, static_cast<u32>(test.initial.size()) + i);
|
||||
test.expected.push_back(entry.expected);
|
||||
}
|
||||
AppendEnd(&code);
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MIN3_U16,
|
||||
O::S_MOV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{"V_MIN3_U16", cases.size()}};
|
||||
test.required_spirv = {"OpULessThan"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Vop3Med3I16Captured() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -24729,10 +24975,10 @@ TestCase VectorCompareInteger64Edges() {
|
||||
const auto lhs = pairs[i][0], rhs = pairs[i][1];
|
||||
const auto signed_lhs = std::bit_cast<int64_t>(lhs);
|
||||
const auto signed_rhs = std::bit_cast<int64_t>(rhs);
|
||||
for (const auto [opcode, value] : std::array<std::pair<u32, bool>, 5>{{
|
||||
{0xa1, signed_lhs < signed_rhs}, {0xa3, signed_lhs <= signed_rhs},
|
||||
{0xa5, lhs != rhs},
|
||||
{0xe3, lhs <= rhs}, {0xe6, lhs >= rhs}}}) {
|
||||
for (const auto [opcode, value] : std::array<std::pair<u32, bool>, 9>{{
|
||||
{0xa0, false}, {0xa1, signed_lhs < signed_rhs},
|
||||
{0xa3, signed_lhs <= signed_rhs}, {0xa5, lhs != rhs}, {0xa7, true},
|
||||
{0xe0, false}, {0xe3, lhs <= rhs}, {0xe6, lhs >= rhs}, {0xe7, true}}}) {
|
||||
test.code.push_back(EncodeVopc(opcode, Vgpr(1), 3));
|
||||
store_mask(106, value);
|
||||
AppendVop3(&test.code, opcode, 20, Vgpr(1), 8);
|
||||
@@ -24750,6 +24996,12 @@ TestCase VectorCompareInteger64Edges() {
|
||||
AppendVop3(&test.code, 0xa3, 20, Vgpr(1), 255);
|
||||
test.code.push_back(0xffffffffu);
|
||||
store_mask(20, signed_lhs <= -1);
|
||||
test.code.push_back(EncodeSop1(0x04, 10, 126));
|
||||
AppendVop3(&test.code, 0xb2, 126, 255, Vgpr(3)); // CMPX_EQ_I64 sign-extends -1.
|
||||
test.code.push_back(0xffffffffu);
|
||||
test.code.push_back(EncodeSop1(0x04, 20, 126));
|
||||
test.code.push_back(EncodeSop1(0x04, 126, 10));
|
||||
store_mask(20, -1 == signed_rhs);
|
||||
// Captured 8395e43f382309df PC 0x20cc: s[2:3] = (s[0:1] != 0) & EXEC.
|
||||
// Save the test buffer descriptor while its SGPRs hold the captured operands.
|
||||
test.code.push_back(EncodeSop1(0x04, 32, 0));
|
||||
@@ -24765,8 +25017,9 @@ TestCase VectorCompareInteger64Edges() {
|
||||
AppendEnd(&test.code);
|
||||
test.initial.resize(test.expected.size());
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32,
|
||||
O::V_CMP_LT_I64, O::V_CMP_LE_I64, O::V_CMP_EQ_I64, O::V_CMP_NE_I64,
|
||||
O::V_CMP_LE_U64, O::V_CMP_GE_U64,
|
||||
O::V_CMP_F_I64, O::V_CMP_LT_I64, O::V_CMP_LE_I64, O::V_CMP_EQ_I64,
|
||||
O::V_CMP_NE_I64, O::V_CMP_T_I64, O::V_CMPX_EQ_I64,
|
||||
O::V_CMP_F_U64, O::V_CMP_LE_U64, O::V_CMP_GE_U64, O::V_CMP_T_U64,
|
||||
O::S_MOV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpSLessThan", "OpULessThanEqual", "OpUGreaterThanEqual"};
|
||||
return test;
|
||||
@@ -24774,7 +25027,7 @@ TestCase VectorCompareInteger64Edges() {
|
||||
|
||||
TestCase VectorCompareExecWaveMasks(u32 wave_size) {
|
||||
using O = ShaderOpcode;
|
||||
// Rows exercise signed 16-bit truncation, full-width unsigned ordering, and
|
||||
// Rows exercise signed 16-bit truncation, full-width integer ordering, and
|
||||
// ordered FP64 comparisons (including NaN, signed zero, and subnormals).
|
||||
constexpr std::array<std::array<std::array<uint64_t, 2>, 8>, 3> inputs{{
|
||||
{{{0x1111ffff, 0x22220000}, {0x33338000, 0x44447fff},
|
||||
@@ -24795,8 +25048,13 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) {
|
||||
Compare{0x99, O::V_CMPX_LT_I16, 0}, Compare{0x9a, O::V_CMPX_EQ_I16, 0},
|
||||
Compare{0x9b, O::V_CMPX_LE_I16, 0}, Compare{0x9c, O::V_CMPX_GT_I16, 0},
|
||||
Compare{0x9d, O::V_CMPX_NE_I16, 0}, Compare{0x9e, O::V_CMPX_GE_I16, 0},
|
||||
Compare{0xf3, O::V_CMPX_LE_U64, 1}, Compare{0x33, O::V_CMPX_LE_F64, 2},
|
||||
Compare{0x36, O::V_CMPX_GE_F64, 2}};
|
||||
Compare{0xb0, O::V_CMPX_F_I64, 1}, Compare{0xb1, O::V_CMPX_LT_I64, 1},
|
||||
Compare{0xb2, O::V_CMPX_EQ_I64, 1}, Compare{0xb3, O::V_CMPX_LE_I64, 1},
|
||||
Compare{0xb7, O::V_CMPX_T_I64, 1}, Compare{0xf0, O::V_CMPX_F_U64, 1},
|
||||
Compare{0xf1, O::V_CMPX_LT_U64, 1}, Compare{0xf2, O::V_CMPX_EQ_U64, 1},
|
||||
Compare{0xf3, O::V_CMPX_LE_U64, 1}, Compare{0xf4, O::V_CMPX_GT_U64, 1},
|
||||
Compare{0xf6, O::V_CMPX_GE_U64, 1}, Compare{0xf7, O::V_CMPX_T_U64, 1},
|
||||
Compare{0x33, O::V_CMPX_LE_F64, 2}, Compare{0x36, O::V_CMPX_GE_F64, 2}};
|
||||
constexpr u32 low_exec = 0xeeeeeeeeu, high_exec = 0x77777777u;
|
||||
constexpr u32 vcc_lo = 0x12345678u, vcc_hi = 0x89abcdefu;
|
||||
TestCase test;
|
||||
@@ -24832,7 +25090,15 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) {
|
||||
case 0x9c: result = a > b; break;
|
||||
case 0x9d: result = a != b; break;
|
||||
case 0x9e: result = a >= b; break;
|
||||
case 0xb0: case 0xf0: result = false; break;
|
||||
case 0xb1: result = std::bit_cast<int64_t>(pair[0]) < std::bit_cast<int64_t>(pair[1]); break;
|
||||
case 0xb2: case 0xf2: result = pair[0] == pair[1]; break;
|
||||
case 0xb3: result = std::bit_cast<int64_t>(pair[0]) <= std::bit_cast<int64_t>(pair[1]); break;
|
||||
case 0xb7: case 0xf7: result = true; break;
|
||||
case 0xf1: result = pair[0] < pair[1]; break;
|
||||
case 0xf3: result = pair[0] <= pair[1]; break;
|
||||
case 0xf4: result = pair[0] > pair[1]; break;
|
||||
case 0xf6: result = pair[0] >= pair[1]; break;
|
||||
case 0x33: result = std::bit_cast<double>(pair[0]) <= std::bit_cast<double>(pair[1]); break;
|
||||
case 0x36: result = std::bit_cast<double>(pair[0]) >= std::bit_cast<double>(pair[1]); break;
|
||||
}
|
||||
@@ -27353,6 +27619,42 @@ TestCase BranchVccnzUsesWaveMask() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ScalarBranchRestoresInactiveLanes(u32 wave_size, u32 threads) {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
if (threads < wave_size) {
|
||||
test.name = wave_size == 32 ? "ScalarBranchRestoresPartialWave32"
|
||||
: "ScalarBranchRestoresPartialWave64";
|
||||
} else {
|
||||
test.name = wave_size == 32 ? "ScalarBranchRestoresInactiveWave32"
|
||||
: "ScalarBranchRestoresInactiveWave64";
|
||||
}
|
||||
auto &code = test.code;
|
||||
code = {EncodeVop1(0x01, 1, InlineU32(32)),
|
||||
EncodeVop2(0x25, 1, Vgpr(0), 1),
|
||||
EncodeSop1(0x04, 8, 126), // Save all participating lanes.
|
||||
EncodeSMovB32(20, InlineU32(0)),
|
||||
EncodeSMovB32(126, InlineU32(1)), EncodeSMovB32(127, InlineU32(0)),
|
||||
EncodeSopp(0x08, 3), // EXEC is nonzero for the whole wave.
|
||||
EncodeSop1(0x04, 126, 8)};
|
||||
// The branch restores inactive lanes before reading the last lane's value.
|
||||
AppendVop3(&code, 0x360, 20, Vgpr(1), InlineU32(threads - 1));
|
||||
code.push_back(EncodeSop1(0x04, 126, 8));
|
||||
AppendStoreSgprAtLaneDwordOffset(&code, 20, 0, 0);
|
||||
AppendEnd(&code);
|
||||
test.initial.assign(threads, 0);
|
||||
test.expected.assign(threads, 32 + threads - 1);
|
||||
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::S_MOV_B32, O::S_MOV_B64,
|
||||
O::S_CBRANCH_EXECZ, O::V_READLANE_B32, O::V_LSHLREV_B32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpGroupNonUniformBallot", "OpAll"};
|
||||
test.compute_info.wave_size = wave_size;
|
||||
test.compute_info.threads_num[0] = threads;
|
||||
test.compute_info.thread_ids_num = 1;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ScalarMemRealtimeCapturedPlaceholder() {
|
||||
using O = ShaderOpcode;
|
||||
namespace D = ShaderRecompiler::Decoder;
|
||||
@@ -28696,6 +28998,108 @@ TestCase BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords() {
|
||||
return test;
|
||||
}
|
||||
|
||||
std::vector<TestCase> BufferLoadFormatD16XCases() {
|
||||
using F = Prospero::BufferFormat;
|
||||
using O = ShaderOpcode;
|
||||
struct LoadCase {
|
||||
const char *name;
|
||||
F format;
|
||||
u32 input;
|
||||
u32 expected;
|
||||
u32 selector = 4;
|
||||
u32 index = 0;
|
||||
bool active = true;
|
||||
bool overlap = false;
|
||||
};
|
||||
constexpr LoadCase inputs[] = {
|
||||
{"BufferLoadFormatD16XFloat16", F::k16Float, 0x7e63c000u, 0xabcdc000u},
|
||||
{"BufferLoadFormatD16XFloat32", F::k32Float, 0x3f000000u, 0xabcd3800u},
|
||||
{"BufferLoadFormatD16XUint32", F::k32UInt, 0x1234fffeu, 0xabcdffffu},
|
||||
{"BufferLoadFormatD16XUintAtMax", F::k32UInt, 0x0000ffffu, 0xabcdffffu},
|
||||
{"BufferLoadFormatD16XUintMax32", F::k32UInt, 0xffffffffu, 0xabcdffffu},
|
||||
{"BufferLoadFormatD16XSint32", F::k32SInt, 0xffff8001u, 0xabcd8001u},
|
||||
{"BufferLoadFormatD16XSintAboveMax", F::k32SInt, 0x00008000u, 0xabcd7fffu},
|
||||
{"BufferLoadFormatD16XSintBelowMin", F::k32SInt, 0xffff7fffu, 0xabcd8000u},
|
||||
{"BufferLoadFormatD16XSintAtMax", F::k32SInt, 0x00007fffu, 0xabcd7fffu},
|
||||
{"BufferLoadFormatD16XSintAtMin", F::k32SInt, 0xffff8000u, 0xabcd8000u},
|
||||
{"BufferLoadFormatD16XSintMax32", F::k32SInt, 0x7fffffffu, 0xabcd7fffu},
|
||||
{"BufferLoadFormatD16XSintMin32", F::k32SInt, 0x80000000u, 0xabcd8000u},
|
||||
{"BufferLoadFormatD16XUnorm8", F::k8UNorm, 0xffu, 0xabcd3c00u},
|
||||
{"BufferLoadFormatD16XSnorm16", F::k16SNorm, 0x8001u, 0xabcdbc00u},
|
||||
{"BufferLoadFormatD16XFloatOne", F::k32Float, 0u, 0xabcd3c00u, 1},
|
||||
{"BufferLoadFormatD16XOobFloatOne", F::k32Float, 0u, 0xabcd3c00u, 1, 4},
|
||||
{"BufferLoadFormatD16XOobUintOne", F::k32UInt, 0u, 0xabcd0001u, 1, 4},
|
||||
{"BufferLoadFormatD16XOobZero", F::k32Float, 0u, 0xabcd0000u, 4, 4},
|
||||
{"BufferLoadFormatD16XInactive", F::k32Float, 0u, 0xabcd1234u, 4, 0, false},
|
||||
{"BufferLoadFormatD16XOverlappingAddress", F::k16UInt, 7u, 7u, 4, 0, true, true},
|
||||
};
|
||||
std::vector<TestCase> cases;
|
||||
for (const auto &input : inputs) {
|
||||
TestCase test;
|
||||
test.name = input.name;
|
||||
AppendVMovLiteral(&test.code, 0, input.overlap ? input.index : 0xabcd1234u);
|
||||
AppendVMovU32(&test.code, 1, input.index);
|
||||
if (!input.active) {
|
||||
test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
||||
}
|
||||
test.code.push_back(EncodeMubuf0(0x80u, 0, true, false));
|
||||
test.code.push_back(EncodeMubuf1(0, 0, input.overlap ? 0 : 1));
|
||||
if (!input.active) {
|
||||
test.code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
||||
test.code.push_back(EncodeSMovB32(127, InlineU32(0)));
|
||||
}
|
||||
AppendStoreVgpr(&test.code, 0, 2);
|
||||
AppendEnd(&test.code);
|
||||
test.initial = {input.input, 0xdeadbeefu, 0u, 0xcafef00du};
|
||||
test.expected = {input.input, 0xdeadbeefu, input.expected, 0xcafef00du};
|
||||
test.user_data = MakeStructuredStorageBufferData(4, 4, false, BufferFormat(input.format));
|
||||
test.user_data[3] = (test.user_data[3] & ~7u) | input.selector;
|
||||
test.has_user_data = true;
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_D16_X,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
cases.push_back(std::move(test));
|
||||
}
|
||||
return cases;
|
||||
}
|
||||
|
||||
std::vector<TestCase> BufferStoreFormatD16XCases() {
|
||||
using F = Prospero::BufferFormat;
|
||||
using O = ShaderOpcode;
|
||||
struct StoreCase {
|
||||
const char *name;
|
||||
F format;
|
||||
u32 input;
|
||||
u32 expected;
|
||||
};
|
||||
constexpr StoreCase inputs[] = {
|
||||
{"BufferStoreFormatD16XFloat16", F::k16Float, 0x7e63c000u, 0xffffc000u},
|
||||
{"BufferStoreFormatD16XFloat32", F::k32Float, 0x7e633800u, 0x3f000000u},
|
||||
{"BufferStoreFormatD16XUint32", F::k32UInt, 0x7e63fffeu, 0x0000fffeu},
|
||||
{"BufferStoreFormatD16XSint32", F::k32SInt, 0x7e63fffeu, 0xfffffffeu},
|
||||
{"BufferStoreFormatD16XUnorm8", F::k8UNorm, 0x7e633800u, 0xffffff80u},
|
||||
{"BufferStoreFormatD16XSnorm16", F::k16SNorm, 0x7e63b800u, 0xffffc000u},
|
||||
{"BufferStoreFormatD16XZeroFill", F::k16_16Float, 0x7e633800u, 0x00003800u},
|
||||
};
|
||||
std::vector<TestCase> cases;
|
||||
for (const auto &input : inputs) {
|
||||
TestCase test;
|
||||
test.name = input.name;
|
||||
AppendVMovLiteral(&test.code, 66, input.input);
|
||||
AppendVMovU32(&test.code, 65, 1);
|
||||
// Match the captured CS824586f35db5c80d instruction at pc 0x484.
|
||||
test.code.push_back(EncodeMubuf0(0x84u, 0, true, false));
|
||||
test.code.push_back(EncodeMubuf1(66, 0, 65));
|
||||
AppendEnd(&test.code);
|
||||
test.initial = {0xdeadbeefu, 0xffffffffu, 0xcafef00du};
|
||||
test.expected = {0xdeadbeefu, input.expected, 0xcafef00du};
|
||||
test.user_data = MakeStructuredStorageBufferData(4, 3, false, BufferFormat(input.format));
|
||||
test.has_user_data = true;
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_D16_X, O::S_ENDPGM};
|
||||
cases.push_back(std::move(test));
|
||||
}
|
||||
return cases;
|
||||
}
|
||||
|
||||
std::vector<TestCase> FormattedStoreConversionCases() {
|
||||
using F = Prospero::BufferFormat;
|
||||
using O = ShaderOpcode;
|
||||
@@ -34867,6 +35271,26 @@ GraphicsCase GraphicsPackedHalfCentroid() {
|
||||
return test;
|
||||
}
|
||||
|
||||
GraphicsCase GraphicsPerspectiveSample(bool multisample) {
|
||||
GraphicsCase test;
|
||||
test.name = multisample ? "GraphicsPerspectiveSample4x"
|
||||
: "GraphicsPerspectiveSample1x";
|
||||
test.pixel_perspective_sample_vgpr = 0;
|
||||
test.samples = multisample ? vk::SampleCountFlagBits::e4
|
||||
: vk::SampleCountFlagBits::e1;
|
||||
test.fragment_code = {
|
||||
EncodeVop2(0x08, 2, Vgpr(0), 0), // I squared
|
||||
EncodeVop2(0x08, 3, Vgpr(1), 1), // J squared
|
||||
EncodeExp0(0x00, 0xf), EncodeExp1(2, 3, 0, 1)};
|
||||
AppendEnd(&test.fragment_code);
|
||||
// The triangle gives I=x/2, J=y/2. Squaring before the 4x resolve
|
||||
// distinguishes sample interpolation (21/256) from pixel-center (1/16).
|
||||
const auto square = std::bit_cast<u32>(multisample ? 21.0f / 256.0f : 1.0f / 16.0f);
|
||||
test.expected_pixel = {square, square, 0x3e800000u, 0x3e800000u};
|
||||
test.opcodes = {ShaderOpcode::V_MUL_F32, ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
GraphicsCase GraphicsPackedHalfInputAlias(bool second_weights) {
|
||||
auto test = GraphicsPackedHalfCentroid();
|
||||
test.name = second_weights ? "GraphicsPackedHalfAliasSecondWeights"
|
||||
@@ -35337,6 +35761,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(Vop3CvtPkI16I32Captured);
|
||||
AddCase(Vop3MulLoU16CapturedAndSelectors);
|
||||
AddCase(Vop3MadI16CapturedSelectorsAndSaturation);
|
||||
AddCase(Vop3Min3U16CapturedAndSelectors);
|
||||
AddCase(Vop3Med3I16Captured);
|
||||
AddCase(Vop2SdwaMinU32PreservesWordDestination);
|
||||
AddCase(VectorShiftCountsMaskLowBits);
|
||||
@@ -35470,6 +35895,10 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(SharedReturnKeepsSelectedValues);
|
||||
AddCase(SiblingSharedExitKeepsCapturedConditions);
|
||||
AddCase(BranchVccnzUsesWaveMask);
|
||||
AddCase([] { return ScalarBranchRestoresInactiveLanes(32, 32); });
|
||||
AddCase([] { return ScalarBranchRestoresInactiveLanes(64, 64); });
|
||||
AddCase([] { return ScalarBranchRestoresInactiveLanes(32, 4); });
|
||||
AddCase([] { return ScalarBranchRestoresInactiveLanes(64, 4); });
|
||||
AddCase(BranchVccnzUsesCarryProducedWaveMask);
|
||||
AddCase(ScalarMemRealtimeCapturedPlaceholder);
|
||||
AddCase(ScalarMemoryLoadVariants);
|
||||
@@ -35527,6 +35956,12 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(BufferStoreFormatXyzwFloat16ConvertsComponents);
|
||||
AddCase(BufferStoreFormatXyzwSnorm16CapturedSkinningVectors);
|
||||
AddCase(BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords);
|
||||
for (auto &test : BufferLoadFormatD16XCases()) {
|
||||
cases.push_back(std::move(test));
|
||||
}
|
||||
for (auto &test : BufferStoreFormatD16XCases()) {
|
||||
cases.push_back(std::move(test));
|
||||
}
|
||||
for (auto &test : FormattedStoreConversionCases()) {
|
||||
cases.push_back(std::move(test));
|
||||
}
|
||||
@@ -35735,6 +36170,8 @@ std::vector<GraphicsCase> MakeGraphicsCases() {
|
||||
GraphicsInterpolationExport(),
|
||||
GraphicsPositionWExport(),
|
||||
GraphicsPackedHalfCentroid(),
|
||||
GraphicsPerspectiveSample(false),
|
||||
GraphicsPerspectiveSample(true),
|
||||
GraphicsPackedHalfInputAlias(false),
|
||||
GraphicsPackedHalfInputAlias(true),
|
||||
GraphicsSmoothRawInputAlias(),
|
||||
@@ -40260,6 +40697,77 @@ void CheckPm4DrawIndirectMultiPacket(RenderContext &renderer) {
|
||||
}
|
||||
}
|
||||
renderer.GetCommandScheduler().Finish();
|
||||
processor.BufferInit();
|
||||
auto &shaders = processor.GetShCtx();
|
||||
const auto &gs = shaders.GetVs().gs_user_sgpr.value;
|
||||
const auto &hs = shaders.GetVs().hs_user_sgpr.value;
|
||||
const auto check_offsets = [&](auto &commands, const auto &sgprs,
|
||||
std::initializer_list<uint32_t> expected_offsets,
|
||||
const char *label) {
|
||||
Require("Pm4DrawIndirectMulti", label,
|
||||
execute(commands.data(), commands.size()) &&
|
||||
std::equal(expected_offsets.begin(), expected_offsets.end(), sgprs + 9),
|
||||
"indirect draw did not write the expected user SGPR offsets");
|
||||
};
|
||||
// These locations match the captured NGG prolog: user SGPR9/10 become s17/s18.
|
||||
shaders.SetGsUserSgpr(9, 17821, HW::UserSgprType::Unknown);
|
||||
shaders.SetGsUserSgpr(10, 0x898f3f24u, HW::UserSgprType::Unknown);
|
||||
arguments = {0, 1};
|
||||
std::array<uint32_t, 5> indexed_packet{
|
||||
KYTY_PM4(5, Pm4::IT_DRAW_INDEX_INDIRECT, 0), 0,
|
||||
Pm4::SPI_SHADER_USER_DATA_GS_0 + 9,
|
||||
Pm4::SPI_SHADER_USER_DATA_GS_0 + 10, 0};
|
||||
check_offsets(indexed_packet, gs, {0, 0}, "clear stale offsets");
|
||||
arguments = {0, 1, 5, static_cast<uint32_t>(-17), 3};
|
||||
indexed_packet[2] |= (Pm4::SPI_SHADER_USER_DATA_GS_0 + 11) << 16u;
|
||||
// Native sceAgcDcbDrawIndexIndirect (0x5a00) emits first-index enable in word3 bit28.
|
||||
indexed_packet[3] |= 1u << 28u;
|
||||
check_offsets(indexed_packet, gs, {static_cast<uint32_t>(-17), 3, 5}, "indexed offsets");
|
||||
indexed_packet[3] ^= (1u << 28u) | (1u << 27u);
|
||||
arguments[2] = 6;
|
||||
check_offsets(indexed_packet, gs, {static_cast<uint32_t>(-17), 3, 5},
|
||||
"index destination disabled");
|
||||
|
||||
arguments = {0, 1, 31, 5};
|
||||
std::array<uint32_t, 5> auto_packet{
|
||||
KYTY_PM4(5, Pm4::IT_DRAW_INDIRECT, 0), 0,
|
||||
Pm4::SPI_SHADER_USER_DATA_HS_0 + 9,
|
||||
Pm4::SPI_SHADER_USER_DATA_HS_0 + 10, 2};
|
||||
check_offsets(auto_packet, hs, {31, 5}, "non-indexed offsets");
|
||||
Require("Pm4DrawIndirectMulti", "shader stage", gs[9] == static_cast<uint32_t>(-17),
|
||||
"non-indexed draw patched the wrong shader stage");
|
||||
|
||||
arguments = {0, 1, 6, 41, 7, 0, 1, 8, 43, 9, 0, 1, 10, 45, 11};
|
||||
uint32_t count = 2;
|
||||
const auto multi_count_address = reinterpret_cast<uint64_t>(&count);
|
||||
// Native sceAgcDcbDrawIndexIndirectMulti (0x5800) puts this enable in word4 bit28.
|
||||
std::array<uint32_t, 10> multi_packet{
|
||||
KYTY_PM4(10, Pm4::IT_DRAW_INDEX_INDIRECT_MULTI, 0), 0,
|
||||
indexed_packet[2], Pm4::SPI_SHADER_USER_DATA_GS_0 + 10,
|
||||
(1u << 30u) | (1u << 28u) | Pm4::SH_NOP, 3,
|
||||
static_cast<uint32_t>(multi_count_address),
|
||||
static_cast<uint32_t>(multi_count_address >> 32u), 20, 0};
|
||||
check_offsets(multi_packet, gs, {43, 9, 8}, "counted multi-draw offsets");
|
||||
count = 0;
|
||||
check_offsets(multi_packet, gs, {43, 9, 8}, "empty multi-draw");
|
||||
count = 3;
|
||||
multi_packet[5] = 1;
|
||||
multi_packet[3] |= 1u << 28u;
|
||||
multi_packet[4] &= ~(1u << 28u);
|
||||
check_offsets(multi_packet, gs, {41, 7, 8}, "multi-draw enable word and count clamp");
|
||||
|
||||
arguments = {0, 1, 51, 13, 0, 1, 53, 15};
|
||||
std::array<uint32_t, 10> auto_multi_packet{
|
||||
KYTY_PM4(10, Pm4::IT_DRAW_INDIRECT_MULTI, 0), 0,
|
||||
auto_packet[2], auto_packet[3], Pm4::SH_NOP, 2, 0, 0, 16, 2};
|
||||
check_offsets(auto_multi_packet, hs, {53, 15}, "non-indexed multi-draw offsets");
|
||||
arguments = {0, 1, 99, 101};
|
||||
auto_packet[2] = Pm4::SH_NOP;
|
||||
check_offsets(auto_packet, hs, {53, 101}, "independent destinations");
|
||||
auto_packet[3] = Pm4::SH_NOP;
|
||||
arguments[3] = 103;
|
||||
check_offsets(auto_packet, hs, {53, 101}, "disabled destinations");
|
||||
processor.BufferFlush();
|
||||
std::printf("[host] %-32s ok\n", "Pm4DrawIndirectMulti");
|
||||
}
|
||||
|
||||
@@ -40715,6 +41223,15 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckNativeIndirectDispatch();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--integer64-compare-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorCompareInteger64Edges());
|
||||
RunCase(&vulkan, VectorCompareExecWaveMasks(32));
|
||||
RunCase(&vulkan, VectorCompareExecWaveMasks(64));
|
||||
RunCase(&vulkan, VectorVopcCmpxNeU64CapturedExecMask());
|
||||
RunCase(&vulkan, VectorVop3CmpxNeI64CapturedExecMask());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--new-opcodes-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorCompareInteger64Edges());
|
||||
@@ -40780,6 +41297,12 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, BufferStoreFormatXyzwFloat16ConvertsComponents());
|
||||
RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors());
|
||||
RunCase(&vulkan, BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords());
|
||||
for (const auto &test : BufferLoadFormatD16XCases()) {
|
||||
RunCase(&vulkan, test);
|
||||
}
|
||||
for (const auto &test : BufferStoreFormatD16XCases()) {
|
||||
RunCase(&vulkan, test);
|
||||
}
|
||||
for (const auto &test : FormattedStoreConversionCases()) {
|
||||
RunCase(&vulkan, test);
|
||||
}
|
||||
@@ -41022,6 +41545,10 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, ScratchIsPrivatePerInvocation());
|
||||
RunCase(&vulkan, Vop1MoveRelDestination());
|
||||
RunCase(&vulkan, BranchVccnzUsesWaveMask());
|
||||
RunCase(&vulkan, ScalarBranchRestoresInactiveLanes(32, 32));
|
||||
RunCase(&vulkan, ScalarBranchRestoresInactiveLanes(64, 64));
|
||||
RunCase(&vulkan, ScalarBranchRestoresInactiveLanes(32, 4));
|
||||
RunCase(&vulkan, ScalarBranchRestoresInactiveLanes(64, 4));
|
||||
RunCase(&vulkan, BranchVccnzUsesCarryProducedWaveMask());
|
||||
RunCase(&vulkan, ImageSampleAndGather());
|
||||
RunCase(&vulkan, ImageGatherExplicitLod());
|
||||
@@ -41037,6 +41564,8 @@ int main(int argc, char **argv) {
|
||||
if (argc == 2 && std::strcmp(argv[1], "--centroid-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunGraphicsCase(&vulkan, GraphicsPackedHalfCentroid());
|
||||
RunGraphicsCase(&vulkan, GraphicsPerspectiveSample(false));
|
||||
RunGraphicsCase(&vulkan, GraphicsPerspectiveSample(true));
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--pixel-alias-only") == 0) {
|
||||
@@ -41260,6 +41789,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--med3-i16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, Vop3Min3U16CapturedAndSelectors());
|
||||
RunCase(&vulkan, Vop3Med3I16Captured());
|
||||
return 0;
|
||||
}
|
||||
@@ -41599,7 +42129,6 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckCubeFaceStorageExpansion();
|
||||
if (rasterization) {
|
||||
vulkan.CheckGraphicsPushConstantBank();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
vulkan.CheckRenderExecutorColorDiscovery();
|
||||
vulkan.CheckRenderExecutorColor1DArrayDiscovery();
|
||||
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
||||
@@ -41613,7 +42142,6 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckRasterization(false, Prospero::BufferFormat::k11_11_10Float);
|
||||
vulkan.CheckRasterization(false, Prospero::BufferFormat::k10_10_10_2UScaled);
|
||||
vulkan.CheckBufferCacheDirtyGarbageCollection();
|
||||
#endif
|
||||
} else {
|
||||
skipped_device_checks = true;
|
||||
}
|
||||
|
||||
+136
-37
@@ -6539,6 +6539,7 @@ void TestPerspectiveCentroidInputs() {
|
||||
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
||||
HW::ShaderRegisters sh{};
|
||||
sh.ps_input_ena = sh.ps_input_addr = inputs;
|
||||
sh.m_cbShaderMask = 0xf;
|
||||
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
|
||||
ShaderPixelInputInfo pixel{};
|
||||
(void)PrepareProgram(regs, sh, mappings, pixel);
|
||||
@@ -6570,6 +6571,47 @@ void TestPerspectiveCentroidInputs() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestPerspectiveSampleInputs() {
|
||||
for (const uint32_t inputs : {1u, 3u, 5u, 7u}) {
|
||||
const std::vector<uint32_t> shader = {
|
||||
EncodeExp0(0x00, 0xf),
|
||||
EncodeExp1(0, 1, inputs == 1 ? 0 : 2, inputs == 1 ? 1 : 3),
|
||||
EncodeSopp(0x01)};
|
||||
HW::PixelShaderInfo regs{};
|
||||
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader.data());
|
||||
ShaderMappedData mapped{};
|
||||
mapped.code_size_bytes = shader.size() * sizeof(uint32_t);
|
||||
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
||||
HW::ShaderRegisters sh{};
|
||||
sh.ps_input_ena = sh.ps_input_addr = inputs;
|
||||
sh.m_cbShaderMask = 0xf;
|
||||
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
|
||||
ShaderPixelInputInfo pixel{};
|
||||
(void)PrepareProgram(regs, sh, mappings, pixel);
|
||||
Check(pixel.ps_perspective_sample_vgpr == 0 &&
|
||||
pixel.ps_system_input_base == 2u * std::popcount(inputs),
|
||||
"perspective sample pair did not occupy the first enabled VGPRs");
|
||||
const auto key = MakeStageStaticKey(pixel);
|
||||
pixel.ps_perspective_sample_vgpr = UINT32_MAX;
|
||||
Check(key != MakeStageStaticKey(pixel), "sample input is missing from shader key");
|
||||
pixel.ps_perspective_sample_vgpr = 0;
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.input_info.pixel = &pixel;
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
Check(SpirvContainsCapability(result.spirv, 52u) &&
|
||||
SpirvHasDecorationValue(result.spirv, 11u, 18u) &&
|
||||
source.find("InterpolateAtSample %gl_BaryCoordKHR") != std::string::npos &&
|
||||
SpirvSourceHasInstructionUsing(source, "OpLoad", "%int %gl_SampleID") &&
|
||||
SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 1") &&
|
||||
SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 2"),
|
||||
"sample I/J did not evaluate BaryCoordKHR Y/Z at SampleId");
|
||||
Check(!SpirvHasDecorationValueWithDecoration(result.spirv, 11u, 5286u, 17u),
|
||||
"sample interpolation changed the shared barycentric input location");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
}
|
||||
|
||||
void TestPsInputCountRegisterDecode() {
|
||||
HW::Context context;
|
||||
// NUM_INTERP is 3 while bit 14 is an independent control flag that must be
|
||||
@@ -10573,6 +10615,32 @@ void TestMergedShaderUserDataSnapshot() {
|
||||
regs.gs_user_sgpr.value[HW::UserSgprInfo::SGPRS_MAX - 1]++;
|
||||
Check(full.user_data.back() == 0x10001000u + HW::UserSgprInfo::SGPRS_MAX - 1,
|
||||
"merged shader parameters borrowed mutable register storage");
|
||||
regs.gs_regs.rsrc2.lds_size = 64;
|
||||
regs.gs_regs.rsrc1.gs_vgpr_component_count = 1;
|
||||
context.SetGsMaxVertOut(0);
|
||||
for (const uint32_t wave_size : {32u, 64u}) {
|
||||
context.SetShaderStages(wave_size == 32 ? 0x00400000u : 0u);
|
||||
for (const uint16_t capacity : {64u, 128u, 256u}) {
|
||||
context.SetMaxOutputPerSubgroup(capacity);
|
||||
user_config.SetGeControl({capacity, capacity});
|
||||
ShaderVertexInputInfo input{};
|
||||
const auto params = PrepareProgram(regs, context, user_config, input);
|
||||
Check(input.logical_stage == ShaderType::Mesh && input.mesh.wave_size == wave_size &&
|
||||
input.mesh.lds_size_dwords == 8192 && input.mesh.max_vertices == capacity &&
|
||||
input.mesh.max_primitives == capacity / 3 &&
|
||||
input.mesh.primitives_per_group == capacity / 3 &&
|
||||
input.mesh.vertices_per_group == capacity / 3 * 3 &&
|
||||
input.mesh.threads_num[0] == capacity && params.back_code.empty() &&
|
||||
params.user_data_count == 8u + HW::UserSgprInfo::SGPRS_MAX &&
|
||||
params.user_data[0] == 0 && params.user_data[1] == 0 &&
|
||||
std::equal(std::begin(regs.gs_user_sgpr.value), std::end(regs.gs_user_sgpr.value),
|
||||
params.user_data.begin() + 8),
|
||||
"non-amplifying NGG shader lost its workgroup, GE bounds, or native user SGPRs");
|
||||
}
|
||||
}
|
||||
regs.gs_regs.rsrc2.lds_size = 0;
|
||||
regs.gs_regs.rsrc1.gs_vgpr_component_count = 3;
|
||||
context.SetShaderStages(0x20);
|
||||
context.SetMaxOutputPerSubgroup(256);
|
||||
context.SetGsMaxVertOut(8);
|
||||
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriFan);
|
||||
@@ -10830,7 +10898,7 @@ void TestMeshInputAssembly() {
|
||||
struct Case {
|
||||
Prospero::PrimitiveType topology;
|
||||
uint32_t capacity, count, group, lane, width, address_low, base_vertex;
|
||||
uint32_t wave_info, first, second, third, byte_offset, vertex_id;
|
||||
uint32_t wave_info, group_info, first, second, third, byte_offset, vertex_id;
|
||||
bool fetch;
|
||||
uint32_t wave_size = 64;
|
||||
bool fast_launch = false;
|
||||
@@ -10838,51 +10906,63 @@ void TestMeshInputAssembly() {
|
||||
};
|
||||
std::vector<Case> cases = {
|
||||
{Prospero::PrimitiveType::kTriList, 14, 177, 14, 2, 2, 0x1002, UINT32_MAX,
|
||||
0x40000309, 6, 7, 8, 340, 0xabcc, true},
|
||||
0x40000309, 0x00c09000, 6, 7, 8, 340, 0xabcc, true},
|
||||
{Prospero::PrimitiveType::kTriList, 14, 177, 14, 9, 2, 0x1002, 0,
|
||||
0x40000309, 27, 28, 29, 356, 0, false},
|
||||
0x40000309, 0x00c09000, 27, 28, 29, 356, 0, false},
|
||||
{Prospero::PrimitiveType::kTriList, 8, 180, 0, 64, 2, 0x1002, 0,
|
||||
0x41000000, 192, 193, 194, 128, 0, false},
|
||||
0x41000000, 0x00806000, 192, 193, 194, 128, 0, false},
|
||||
{Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 1, 0x1000, 5,
|
||||
0x40000206, 3, 4, 5, 4, 0xb0, true},
|
||||
0x40000206, 0x00806000, 3, 4, 5, 4, 0xb0, true},
|
||||
{Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 4, 0x1000, 5,
|
||||
0x40000206, 3, 4, 5, 28, 0xabcd0128, true},
|
||||
0x40000206, 0x00806000, 3, 4, 5, 28, 0xabcd0128, true},
|
||||
{Prospero::PrimitiveType::kTriStrip, 5, 8, 1, 1, 0, 0, 11,
|
||||
0x40000305, 1, 2, 3, 0, 15, false},
|
||||
0x40000305, 0x00c05000, 1, 2, 3, 0, 15, false},
|
||||
{Prospero::PrimitiveType::kTriStrip, 5, 8, 0, 1, 0, 0, 11,
|
||||
0x40000305, 2, 1, 3, 0, 12, false},
|
||||
0x40000305, 0x00c05000, 2, 1, 3, 0, 12, false},
|
||||
{Prospero::PrimitiveType::kTriFan, 32, 6, 0, 3, 2, 0x1002, 0,
|
||||
0x40000406, 0, 4, 5, 8, 0x0123, true},
|
||||
0x40000406, 0x01006000, 0, 4, 5, 8, 0x0123, true},
|
||||
{Prospero::PrimitiveType::kTriFan, 5, 8, 1, 0, 2, 0x1002, 0,
|
||||
0x40000305, 0, 1, 2, 0, 0xabcd, true},
|
||||
0x40000305, 0x00c05000, 0, 1, 2, 0, 0xabcd, true},
|
||||
{Prospero::PrimitiveType::kTriFan, 5, 8, 1, 1, 0, 0, 11,
|
||||
0x40000305, 0, 2, 3, 0, 15, false},
|
||||
0x40000305, 0x00c05000, 0, 2, 3, 0, 15, false},
|
||||
{Prospero::PrimitiveType::kTriFan, 5, 7, 1, 3, 2, 0x1002, 11,
|
||||
0x40000204, 0, 4, 5, 12, 0xabd8, true},
|
||||
0x40000204, 0x00804000, 0, 4, 5, 12, 0xabd8, true},
|
||||
{Prospero::PrimitiveType::kTriFan, 5, 7, 1, 4, 2, 0x1002, 0,
|
||||
0x40000204, 0, 5, 6, 16, 0, false},
|
||||
0x40000204, 0x00804000, 0, 5, 6, 16, 0, false},
|
||||
{Prospero::PrimitiveType::kTriFan, 5, 8, 1, 64, 4, 0x1000, 0,
|
||||
0x41000000, 0, 65, 66, 268, 0, false},
|
||||
0x41000000, 0x00c05000, 0, 65, 66, 268, 0, false},
|
||||
{Prospero::PrimitiveType::kLineList, 5, 17, 3, 0, 0, 0, 11,
|
||||
0x40000204, 0, 1, 0, 0, 23, false},
|
||||
0x40000204, 0x00804000, 0, 1, 0, 0, 23, false},
|
||||
{Prospero::PrimitiveType::kLineList, 6, 17, 2, 4, 2, 0x1002, 11,
|
||||
0x40000205, 8, 9, 0, 32, 0xabd8, true},
|
||||
0x40000205, 0x00805000, 8, 9, 0, 32, 0xabd8, true},
|
||||
{Prospero::PrimitiveType::kLineList, 6, 17, 2, 5, 2, 0x1002, 0,
|
||||
0x40000205, 10, 11, 0, 36, 0, false},
|
||||
0x40000205, 0x00805000, 10, 11, 0, 36, 0, false},
|
||||
{Prospero::PrimitiveType::kLineList, 8, 180, 1, 64, 4, 0x1000, 0,
|
||||
0x41000000, 128, 129, 0, 288, 0, false},
|
||||
0x41000000, 0x01008000, 128, 129, 0, 288, 0, false},
|
||||
{Prospero::PrimitiveType::kPointList, 12, 265, 22, 0, 2, 0x1002, 0,
|
||||
0x40000101, 0, 0, 0, 528, 0xabcd, true},
|
||||
0x40000101, 0x00401000, 0, 0, 0, 528, 0xabcd, true},
|
||||
{Prospero::PrimitiveType::kPointList, 12, 265, 22, 1, 2, 0x1002, 0,
|
||||
0x40000101, 1, 0, 0, 532, 0, false},
|
||||
0x40000101, 0x00401000, 1, 0, 0, 532, 0, false},
|
||||
{Prospero::PrimitiveType::kPointList, 1, 1, 0, 0, 0, 0, 11,
|
||||
0x40000101, 0, 0, 0, 0, 11, false},
|
||||
0x40000101, 0x00401000, 0, 0, 0, 0, 11, false},
|
||||
{Prospero::PrimitiveType::kPointList, 12, 265, 1, 1, 4, 0x1000, 5,
|
||||
0x40000c0c, 1, 0, 0, 52, 0xabcd0128, true},
|
||||
0x40000c0c, 0x0300c000, 1, 0, 0, 52, 0xabcd0128, true},
|
||||
{Prospero::PrimitiveType::kPointList, 12, 265, 1, 64, 4, 0x1000, 0,
|
||||
0x41000000, 64, 0, 0, 304, 0, false},
|
||||
0x41000000, 0x0300c000, 64, 0, 0, 304, 0, false},
|
||||
{Prospero::PrimitiveType::kTriStrip, 40, 40, 0, 32, 0, 0, 11,
|
||||
0x81000608, 32, 33, 34, 0, 43, false, 32},
|
||||
0x81000608, 0x09828000, 32, 33, 34, 0, 43, false, 32},
|
||||
{Prospero::PrimitiveType::kTriList, 64, 64, 0, 0, 0, 0, 11,
|
||||
0x1000153f, 0x0543f000, 0, 1, 2, 0, 11, false, 64, false, 64},
|
||||
{Prospero::PrimitiveType::kTriList, 128, 128, 0, 64, 0, 0, 11,
|
||||
0x2100003e, 0x0a87e000, 192, 193, 194, 0, 75, false, 64, false, 128},
|
||||
{Prospero::PrimitiveType::kTriList, 256, 256, 0, 192, 0, 0, 11,
|
||||
0x4300003f, 0x154ff000, 576, 577, 578, 0, 203, false, 64, false, 256},
|
||||
{Prospero::PrimitiveType::kTriList, 64, 64, 0, 32, 0, 0, 11,
|
||||
0x2100001f, 0x0543f000, 96, 97, 98, 0, 43, false, 32, false, 64},
|
||||
{Prospero::PrimitiveType::kTriList, 128, 128, 0, 32, 0, 0, 11,
|
||||
0x41000a20, 0x0a87e000, 96, 97, 98, 0, 43, false, 32, false, 128},
|
||||
{Prospero::PrimitiveType::kTriList, 256, 256, 0, 64, 0, 0, 11,
|
||||
0x82001520, 0x154ff000, 192, 193, 194, 0, 75, false, 32, false, 256},
|
||||
};
|
||||
for (const uint32_t wave_size : {32u, 64u}) {
|
||||
for (const uint32_t threads : {wave_size, 2u * wave_size}) {
|
||||
@@ -10893,7 +10973,7 @@ void TestMeshInputAssembly() {
|
||||
(lane < wave_size ? 0x101u : 0u);
|
||||
const uint32_t base_vertex = group == 11u ? UINT32_MAX : 11u;
|
||||
cases.push_back({Prospero::PrimitiveType::kPointList, 1, 12, group, lane, 0, 0,
|
||||
base_vertex, wave_info, 0, 0, 0, 0, group + base_vertex, false,
|
||||
base_vertex, wave_info, 0x00401000, 0, 0, 0, 0, group + base_vertex, false,
|
||||
wave_size, true, threads});
|
||||
}
|
||||
}
|
||||
@@ -10956,15 +11036,16 @@ void TestMeshInputAssembly() {
|
||||
ConstantPropagationPass(program.blocks);
|
||||
}
|
||||
std::array<uint32_t, 9> vgprs{};
|
||||
uint32_t sgpr3 = 0;
|
||||
std::array<uint32_t, 4> sgprs{};
|
||||
for (const auto &inst : *program.blocks.front()) {
|
||||
if (inst.GetOpcode() == ValueOpcode::SetVectorRegister) {
|
||||
vgprs[RegIndex(inst.Arg(0).VectorRegister())] = inst.Arg(1).Resolve().U32();
|
||||
} else if (inst.GetOpcode() == ValueOpcode::SetScalarRegister) {
|
||||
sgpr3 = inst.Arg(1).Resolve().U32();
|
||||
sgprs[RegIndex(inst.Arg(0).ScalarRegister())] = inst.Arg(1).Resolve().U32();
|
||||
}
|
||||
}
|
||||
Check(sgpr3 == test.wave_info && vgprs[0] == ((test.first << 2) | (test.second << 18)) &&
|
||||
Check(sgprs[2] == test.group_info && sgprs[3] == test.wave_info &&
|
||||
vgprs[0] == ((test.first << 2) | (test.second << 18)) &&
|
||||
vgprs[1] == test.third * 4 && vgprs[5] == test.vertex_id &&
|
||||
vgprs[test.fast_launch ? 6 : 8] == 9u,
|
||||
"mesh prolog changed input assembly, wave counts, vertex ID, or instance ID");
|
||||
@@ -12981,9 +13062,23 @@ void TestRenderTargetReverseExportMapping() {
|
||||
"inactive reverse MRT mapping was not normalized out of the shader "
|
||||
"cache key");
|
||||
sh.target_output_mode[0] = 4;
|
||||
sh.m_cbShaderMask = 0xf;
|
||||
PrepareProgram(regs, sh, mappings, compiled_info);
|
||||
Check(compiled_info.target_export_mapping[0] == gr32.export_mapping,
|
||||
"active reverse MRT mapping was lost before shader specialization");
|
||||
const auto first_slot_key = MakeStageStaticKey(compiled_info);
|
||||
sh.m_cbShaderMask = 0xf000;
|
||||
mappings[3] = gr32.export_mapping;
|
||||
PrepareProgram(regs, sh, mappings, compiled_info);
|
||||
Check(compiled_info.target_shader_mask == sh.m_cbShaderMask &&
|
||||
compiled_info.target_export_mapping[0] == gr32.export_mapping &&
|
||||
first_slot_key != MakeStageStaticKey(compiled_info),
|
||||
"compact MRT ordinal lost its physical export mapping or cache identity");
|
||||
const auto sparse_slot_key = MakeStageStaticKey(compiled_info);
|
||||
sh.m_cbShaderMask = 0x1000;
|
||||
PrepareProgram(regs, sh, mappings, compiled_info);
|
||||
Check(sparse_slot_key == MakeStageStaticKey(compiled_info),
|
||||
"component-only shader mask changes introduced a duplicate shader variant");
|
||||
}
|
||||
|
||||
void TestBlendMappingClassification() {
|
||||
@@ -14675,13 +14770,14 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
const auto structured_result = compile("structured-phi", structured_phi,
|
||||
{.words = 140,
|
||||
.instructions = 41,
|
||||
{.words = 174,
|
||||
.instructions = 49,
|
||||
.phis = 1,
|
||||
.labels = 8,
|
||||
.loop_merges = 1,
|
||||
.branches = 6,
|
||||
.conditional_branches = 1});
|
||||
.conditional_branches = 1,
|
||||
.ballots = 2});
|
||||
const auto structured_metrics = MeasureSpirv(structured_result.spirv);
|
||||
Check((structured_result.ir_dump.find("Phi") != std::string::npos),
|
||||
"structured Phi size fixture no longer contains an IR Phi");
|
||||
@@ -14692,13 +14788,14 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
|
||||
CheckSpirvPhiParents(structured_result.spirv);
|
||||
const auto structured_repeat =
|
||||
compile("structured-phi-repeat", structured_phi,
|
||||
{.words = 140,
|
||||
.instructions = 41,
|
||||
{.words = 174,
|
||||
.instructions = 49,
|
||||
.phis = 1,
|
||||
.labels = 8,
|
||||
.loop_merges = 1,
|
||||
.branches = 6,
|
||||
.conditional_branches = 1});
|
||||
.conditional_branches = 1,
|
||||
.ballots = 2});
|
||||
Check(structured_repeat.spirv == structured_result.spirv,
|
||||
"deferred Phi patching is not deterministic");
|
||||
|
||||
@@ -14862,8 +14959,8 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
const auto dispatcher_result = compile("dispatcher", dispatcher,
|
||||
{.words = 242,
|
||||
.instructions = 67,
|
||||
{.words = 255,
|
||||
.instructions = 70,
|
||||
.variables = 3,
|
||||
.function_variables = 3,
|
||||
.loads = 3,
|
||||
@@ -14874,7 +14971,8 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
|
||||
.selection_merges = 1,
|
||||
.branches = 10,
|
||||
.conditional_branches = 1,
|
||||
.switches = 1});
|
||||
.switches = 1,
|
||||
.ballots = 1});
|
||||
Check(dispatcher_result.program.dispatcher_fallback &&
|
||||
(dispatcher_result.ir_dump.find("Phi") != std::string::npos) &&
|
||||
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 245u) == 2u &&
|
||||
@@ -15054,6 +15152,7 @@ int main() {
|
||||
TestNewShaderRecompilerStageInputInfo();
|
||||
TestCustomVintrpMovTranslation();
|
||||
TestPerspectiveCentroidInputs();
|
||||
TestPerspectiveSampleInputs();
|
||||
TestGraphicsCreateInterpolantMapping();
|
||||
TestNewShaderRecompilerPixelPipelineEntry();
|
||||
TestComputeLdsAllocationIdentity();
|
||||
|
||||
Reference in New Issue
Block a user