Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d96d0eeb18 | ||
|
|
bd401a1407 | ||
|
|
070730be0a | ||
|
|
94e7d0a189 | ||
|
|
c28b71f8e0 | ||
|
|
27f015ecd1 | ||
|
|
8596627ced | ||
|
|
d4cca82a22 | ||
|
|
dd9ecd5f65 | ||
|
|
81b0c1302d | ||
|
|
b9b0b87545 | ||
|
|
040b2b9653 | ||
|
|
eccd6f6cac | ||
|
|
0ce19357f6 | ||
|
|
d762d24f73 | ||
|
|
eb917463ad | ||
|
|
c2189de88f | ||
|
|
2fe5c150bc | ||
|
|
5b8e3b51b0 | ||
|
|
f27a136430 | ||
|
|
fb4d97e8a9 | ||
|
|
5ffcd28ca4 | ||
|
|
b3c8f91a84 | ||
|
|
c78a5a9fdf | ||
|
|
858c18468b | ||
|
|
cd74809086 |
No files matched your search
@@ -59,6 +59,7 @@ int DbgExitHandler(const char* file, int line, fmt::text_style style, std::strin
|
||||
}
|
||||
|
||||
void DbgExit(int status) {
|
||||
Subsystems::EmergencyShutdownActive();
|
||||
std::fflush(nullptr);
|
||||
std::_Exit(status);
|
||||
}
|
||||
|
||||
@@ -29,11 +29,7 @@ void DbgExit(int status);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
||||
#else
|
||||
#define EXIT_HALT() (std::_Exit(321), 1)
|
||||
#endif
|
||||
|
||||
#ifndef KYTY_FINAL
|
||||
#define EXIT_IF(x) \
|
||||
|
||||
@@ -69,7 +69,7 @@ File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
Close();
|
||||
}
|
||||
|
||||
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
||||
|
||||
@@ -73,13 +73,9 @@ public:
|
||||
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
|
||||
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
|
||||
void SetNumInstances(uint32_t num_instances);
|
||||
void DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 0, const void* object_ids = nullptr,
|
||||
uint32_t render_target_slice_offset = 0, int32_t vertex_offset_add = 0,
|
||||
uint32_t first_instance = 0);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count, uint32_t flags);
|
||||
void DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0);
|
||||
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 DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
@@ -149,10 +145,6 @@ private:
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void SuspendPm4();
|
||||
void SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset, uint32_t first_vertex,
|
||||
uint32_t first_instance);
|
||||
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
|
||||
@@ -24,7 +24,6 @@ constexpr auto MakeContextDispatchTable() {
|
||||
g_hw_ctx_func[Pm4::DB_DEPTH_CLEAR] = HwCtxSetDepthClear;
|
||||
g_hw_ctx_func[Pm4::PA_SC_SCREEN_SCISSOR_TL] = HwCtxSetScreenScissor;
|
||||
g_hw_ctx_func[Pm4::DB_STENCIL_INFO] = HwCtxSetStencilInfo;
|
||||
g_hw_ctx_func[Pm4::DB_SHADING_RATE_ENCODING] = HwCtxSetDepthShadingRateEncoding;
|
||||
g_hw_ctx_func[Pm4::PA_SU_HARDWARE_SCREEN_OFFSET] = HwCtxSetHardwareScreenOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_OFFSET] = HwCtxSetWindowOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_SCISSOR_TL] = HwCtxSetWindowScissor;
|
||||
|
||||
@@ -45,8 +45,6 @@ uint32_t HwCtxSetStencilClear(CommandProcessor&, uint32_t, uint32_t, const uint3
|
||||
uint32_t HwCtxSetDepthClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetScreenScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetStencilInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthShadingRateEncoding(CommandProcessor&, uint32_t, uint32_t,
|
||||
const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetWindowOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
|
||||
@@ -599,17 +599,6 @@ static void HwCtxSetDepthShadingRateEncodingRegister(CommandProcessor& cp, uint3
|
||||
ctx.SetDepthRenderTarget(target);
|
||||
}
|
||||
|
||||
KYTY_HW_CTX_PARSER(HwCtxSetDepthShadingRateEncoding) {
|
||||
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(num_values != 1);
|
||||
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_SHADING_RATE_ENCODING);
|
||||
|
||||
HwCtxSetDepthShadingRateEncodingRegister(cp, buffer[0]);
|
||||
|
||||
return num_values;
|
||||
}
|
||||
|
||||
static HW::EqaaControl ParseEqaaControl(uint32_t value) {
|
||||
HW::EqaaControl r;
|
||||
|
||||
@@ -1618,15 +1607,15 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
|
||||
auto* index_addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
|
||||
uint32_t max_instance_count = buffer[3];
|
||||
auto* object_ids =
|
||||
reinterpret_cast<void*>(buffer[4] | (static_cast<uint64_t>(buffer[5]) << 32u));
|
||||
uint32_t instance_count = buffer[6];
|
||||
uint32_t flags = buffer[7];
|
||||
uint32_t instance_count = buffer[6];
|
||||
uint32_t flags = buffer[7];
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(instance_count > max_instance_count);
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0xa0u) != 0);
|
||||
|
||||
cp.DrawIndex(index_count, index_addr, 0, 1, instance_count, object_ids);
|
||||
cp.DrawIndex({.index_count = index_count,
|
||||
.index_addr = index_addr,
|
||||
.instance_count = instance_count});
|
||||
|
||||
return 8;
|
||||
}
|
||||
@@ -1641,7 +1630,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
|
||||
EXIT_NOT_IMPLEMENTED(index_count > max_index_count);
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
|
||||
|
||||
cp.DrawIndex(index_count, index_addr, 0, 1);
|
||||
cp.DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
|
||||
return 5;
|
||||
}
|
||||
@@ -1700,7 +1689,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexOffset) {
|
||||
EXIT_NOT_IMPLEMENTED(index_count > max_index_size);
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
|
||||
|
||||
cp.DrawIndexOffset(index_offset, index_count, 0);
|
||||
cp.DrawIndexOffset(index_offset, index_count);
|
||||
|
||||
return 4;
|
||||
}
|
||||
@@ -1715,7 +1704,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexAuto) {
|
||||
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0x22u) != 0);
|
||||
|
||||
cp.DrawIndexAuto(index_count, 0);
|
||||
cp.DrawIndexAuto({.vertex_count = index_count});
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,21 @@ static thread_local bool g_gpu_mutex_owned = false;
|
||||
static thread_local bool g_gpu_thread = false;
|
||||
static thread_local GuestGpu* g_gpu_state = nullptr;
|
||||
|
||||
struct DrawIndirectArgs {
|
||||
uint32_t vertex_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
struct DrawIndexedIndirectArgs {
|
||||
uint32_t index_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_index_location;
|
||||
uint32_t base_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
class GpuMutexLock final {
|
||||
public:
|
||||
explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) {
|
||||
@@ -847,36 +862,21 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags,
|
||||
uint32_t type, uint32_t instance_count, const void* object_ids,
|
||||
uint32_t render_target_slice_offset, int32_t vertex_offset_add,
|
||||
uint32_t first_instance) {
|
||||
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
if (instance_count == 0) {
|
||||
instance_count = m_num_instances;
|
||||
args.index_type_and_size = m_index_type_and_size;
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
if (object_ids != nullptr) {
|
||||
LOGF("\t draw indexed multi-instanced objectIds = 0x%016" PRIx64 "\n",
|
||||
reinterpret_cast<uint64_t>(object_ids));
|
||||
if (args.base_vertex != 0 || args.first_instance != 0) {
|
||||
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
|
||||
args.base_vertex, args.first_instance);
|
||||
}
|
||||
if (render_target_slice_offset != 0) {
|
||||
LOGF("\t draw render target slice offset = %" PRIu32 "\n", render_target_slice_offset);
|
||||
}
|
||||
if (vertex_offset_add != 0 || first_instance != 0) {
|
||||
LOGF("\t draw indexed offsets: vertex_offset_add = %" PRId32 ", first_instance = %" PRIu32
|
||||
"\n",
|
||||
vertex_offset_add, first_instance);
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawIndex(
|
||||
m_submit_id, CurrentBuffer(), m_index_type_and_size, index_count, index_addr, flags, type,
|
||||
instance_count, render_target_slice_offset, vertex_offset_add, first_instance);
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count,
|
||||
uint32_t flags) {
|
||||
CheckBuffer();
|
||||
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
@@ -888,25 +888,10 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), m_index_type_and_size,
|
||||
index_count, index_addr, flags, 1, m_num_instances);
|
||||
DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
|
||||
struct DrawIndirectArgs {
|
||||
uint32_t vertex_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
struct DrawIndexedIndirectArgs {
|
||||
uint32_t index_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_index_location;
|
||||
uint32_t base_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
|
||||
@@ -928,8 +913,11 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
}
|
||||
}
|
||||
m_num_instances = args.instance_count;
|
||||
SubmitNonIndexedDraw(args.vertex_count_per_instance, 0, 0, args.start_vertex_location,
|
||||
args.start_instance_location);
|
||||
DrawIndexAuto({.vertex_count = args.vertex_count_per_instance,
|
||||
.instance_count = args.instance_count,
|
||||
.first_vertex = args.start_vertex_location,
|
||||
.first_instance = args.start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -970,28 +958,18 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
}
|
||||
|
||||
m_num_instances = args.instance_count;
|
||||
DrawIndex(index_count, index_addr, 0, 1, args.instance_count, nullptr, 0,
|
||||
static_cast<int32_t>(args.base_vertex_location), args.start_instance_location);
|
||||
DrawIndex({.index_count = index_count,
|
||||
.index_addr = index_addr,
|
||||
.instance_count = args.instance_count,
|
||||
.base_vertex = static_cast<int32_t>(args.base_vertex_location),
|
||||
.first_instance = args.start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
}
|
||||
|
||||
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) {
|
||||
struct DrawIndirectArgs {
|
||||
uint32_t vertex_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
struct DrawIndexedIndirectArgs {
|
||||
uint32_t index_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_index_location;
|
||||
uint32_t base_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
|
||||
@@ -1028,8 +1006,11 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
}
|
||||
m_num_instances = args->instance_count;
|
||||
SubmitNonIndexedDraw(args->vertex_count_per_instance, 0, 0, args->start_vertex_location,
|
||||
args->start_instance_location);
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
.first_vertex = args->start_vertex_location,
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1071,8 +1052,12 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndex(index_count, index_addr, 0, 1, args->instance_count, nullptr, 0,
|
||||
static_cast<int32_t>(args->base_vertex_location), args->start_instance_location);
|
||||
DrawIndex({.index_count = index_count,
|
||||
.index_addr = index_addr,
|
||||
.instance_count = args->instance_count,
|
||||
.base_vertex = static_cast<int32_t>(args->base_vertex_location),
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1163,19 +1148,13 @@ void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
|
||||
DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset) {
|
||||
SubmitNonIndexedDraw(index_count, flags, render_target_slice_offset, 0, 0);
|
||||
}
|
||||
|
||||
void CommandProcessor::SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset,
|
||||
uint32_t first_vertex, uint32_t first_instance) {
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
|
||||
render_target_slice_offset, m_num_instances,
|
||||
first_vertex, first_instance);
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
|
||||
@@ -435,8 +435,6 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
|
||||
|
||||
if (z.z_info.format != Prospero::DepthFormat::kInvalid ||
|
||||
z.stencil_info.format != Prospero::StencilFormat::kInvalid) {
|
||||
EXIT_NOT_IMPLEMENTED(z.shading_rate_encoding != 0);
|
||||
|
||||
if (z.depth_view.current_mip_level != 0x00000000) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
|
||||
@@ -144,6 +144,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
return;
|
||||
}
|
||||
const bool has_htile = z.z_info.htile_acceleration;
|
||||
const bool unsupported_shading_rate_encoding =
|
||||
z.shading_rate_encoding > 1 || (z.shading_rate_encoding != 0 && !has_htile);
|
||||
const auto samples = render_sample_count(z.z_info.num_samples);
|
||||
if (samples == 0) {
|
||||
DepthFatal("unsupported depth fragment count: %u", z.z_info.num_samples);
|
||||
@@ -162,7 +164,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
rc.copy_sample != 0 || z.z_info.expclear_enabled ||
|
||||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
|
||||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
|
||||
z.depth_view.current_mip_level != 0 || z.shading_rate_encoding != 0 ||
|
||||
z.depth_view.current_mip_level != 0 || unsupported_shading_rate_encoding ||
|
||||
z.z_read_base_addr == 0 ||
|
||||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
|
||||
(z.z_read_base_addr & 0xffffu) != 0 ||
|
||||
|
||||
@@ -102,7 +102,7 @@ void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
|
||||
const auto path = Config::GetShaderLogFolder() / fmt::format("{:04d}_new_shader_{}_{:016x}.spv",
|
||||
id++, stage_name, shader_hash);
|
||||
Common::File::CreateDirectories(path.parent_path());
|
||||
Common::File file(path, Common::File::Mode::Write);
|
||||
Common::File file(path);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
@@ -131,7 +131,7 @@ void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
|
||||
}
|
||||
auto path = base;
|
||||
path += suffix;
|
||||
Common::File file(path, Common::File::Mode::Write);
|
||||
Common::File file(path);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
|
||||
@@ -48,6 +48,31 @@ enum class CommandBufferDebugOp : uint32_t {
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class DrawOffsetSource : uint8_t {
|
||||
DrawState,
|
||||
IndirectArgs,
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
uint32_t index_count = 0;
|
||||
const void* index_addr = nullptr;
|
||||
uint32_t instance_count = 0;
|
||||
uint32_t index_type_and_size = 0;
|
||||
int32_t base_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
};
|
||||
|
||||
struct DrawAutoArgs {
|
||||
uint32_t vertex_count = 0;
|
||||
uint32_t instance_count = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
};
|
||||
|
||||
struct SubmitInfo {
|
||||
static constexpr uint32_t MaxSemaphores = 3;
|
||||
|
||||
@@ -129,13 +154,6 @@ public:
|
||||
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
||||
KYTY_CLASS_NO_COPY(RenderExecutor);
|
||||
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_type_and_size,
|
||||
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
|
||||
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
|
||||
uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
|
||||
@@ -148,6 +166,9 @@ public:
|
||||
std::span<PreparedBindings* const> bindings);
|
||||
|
||||
private:
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
|
||||
struct GraphicsBindings {
|
||||
PreparedBindings vertex;
|
||||
std::optional<PreparedBindings> pixel;
|
||||
@@ -191,6 +212,7 @@ private:
|
||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
||||
std::vector<uint32_t> m_image_occurrences;
|
||||
|
||||
friend class CommandProcessor;
|
||||
friend struct RenderExecutorTestAccess;
|
||||
};
|
||||
|
||||
|
||||
@@ -64,6 +64,25 @@ int32_t ResolveVertexOffset(uint32_t index_offset, const ShaderVertexInputInfo&
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ResolveInstanceOffset(const ShaderVertexInputInfo& vs_input_info) {
|
||||
if (!vs_input_info.fetch_embedded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const auto& program = *vs_input_info.stage.program;
|
||||
const auto& resources = vs_input_info.stage.resources;
|
||||
if (program.info.instance_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
|
||||
const auto index =
|
||||
static_cast<uint32_t>(program.info.instance_offset_sgpr) - program.user_data_base;
|
||||
if (index < resources.user_data.size()) {
|
||||
return resources.user_data[index];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::atomic<uint32_t> g_draw_state_log_count = 0;
|
||||
static std::atomic<uint32_t> g_draw_input_log_count = 0;
|
||||
static std::atomic<uint32_t> g_mrt_state_log_count = 0;
|
||||
@@ -98,10 +117,6 @@ static const char* RenderColorTypeName(RenderColorType type) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsDualSourceBlendFactor(uint32_t factor) {
|
||||
return factor >= 0x0fu && factor <= 0x12u;
|
||||
}
|
||||
|
||||
static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& color,
|
||||
const RenderDepthInfo& depth, const CommandBuffer& buffer,
|
||||
uint32_t index_count, uint32_t flags) {
|
||||
@@ -134,12 +149,6 @@ static void LogMrtState(const char* draw_name, const CommandBuffer& buffer,
|
||||
const auto cb_shader_mask = sh_regs.m_cbShaderMask;
|
||||
const auto& bc0 = ctx.GetBlendControl(0);
|
||||
|
||||
bool interesting = rt_mask != 0x0f || (cb_shader_mask & ~0x0fu) != 0 ||
|
||||
IsDualSourceBlendFactor(bc0.color_srcblend) ||
|
||||
IsDualSourceBlendFactor(bc0.color_destblend) ||
|
||||
(bc0.separate_alpha_blend && (IsDualSourceBlendFactor(bc0.alpha_srcblend) ||
|
||||
IsDualSourceBlendFactor(bc0.alpha_destblend)));
|
||||
|
||||
auto log_id = g_mrt_state_log_count.fetch_add(1);
|
||||
if (log_id >= 32) {
|
||||
return;
|
||||
@@ -505,7 +514,6 @@ struct DrawCallInfo {
|
||||
const char* name = nullptr;
|
||||
CommandBufferDebugOp debug_op = CommandBufferDebugOp::DrawIndex;
|
||||
uint32_t index_count = 0;
|
||||
uint32_t flags = 0;
|
||||
uint32_t instance_count = 0;
|
||||
uint32_t first_instance = 0;
|
||||
};
|
||||
@@ -747,6 +755,7 @@ struct DrawEmitInfo {
|
||||
bool indexed = false;
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
};
|
||||
|
||||
struct DrawIndexBufferSource {
|
||||
@@ -880,8 +889,8 @@ static void SetDrawDebugPhase(CommandBuffer& buffer, uint64_t submit_id, const D
|
||||
uint32_t phase) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(draw.debug_op), submit_id, phase, draw.index_count,
|
||||
draw.flags, draw.instance_count, draw.first_instance);
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(draw.debug_op), submit_id, phase, draw.index_count, 0,
|
||||
draw.instance_count, draw.first_instance);
|
||||
}
|
||||
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
@@ -993,7 +1002,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& b
|
||||
static_cast<bool>(state.depth_info.image_id));
|
||||
if (state.color_count == 0 && !with_depth && !state.ps_active) {
|
||||
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
|
||||
draw.index_count, draw.flags);
|
||||
draw.index_count, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1102,7 +1111,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
|
||||
|
||||
if (state.ps_active) {
|
||||
LogDrawTargetState(draw.name, state.color_info[0], state.depth_info, buffer,
|
||||
state.ps_input_info, draw.index_count, draw.flags);
|
||||
state.ps_input_info, draw.index_count, 0);
|
||||
}
|
||||
LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size,
|
||||
draw.index_count, index_addr);
|
||||
@@ -1124,10 +1133,10 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
if (emit.indexed) {
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
draw.first_instance);
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex,
|
||||
draw.first_instance);
|
||||
emit.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
@@ -1136,17 +1145,17 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
}
|
||||
// Sarah
|
||||
EXIT_NOT_IMPLEMENTED(!(draw.index_count == 3 && vs_input_info.buffers_num == 0));
|
||||
vk_buffer.draw(4, draw.instance_count, emit.first_vertex, draw.first_instance);
|
||||
vk_buffer.draw(4, draw.instance_count, emit.first_vertex, emit.first_instance);
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
EXIT_NOT_IMPLEMENTED((draw.index_count & 0x3u) != 0);
|
||||
for (uint32_t i = 0; i < draw.index_count; i += 4) {
|
||||
if (emit.indexed) {
|
||||
vk_buffer.drawIndexed(4, draw.instance_count, i, emit.vertex_offset,
|
||||
draw.first_instance);
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(4, draw.instance_count, i + emit.first_vertex,
|
||||
draw.first_instance);
|
||||
emit.first_instance);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1239,23 +1248,21 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
|
||||
void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t index_type_and_size, uint32_t index_count,
|
||||
const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count, uint32_t render_target_slice_offset,
|
||||
int32_t vertex_offset_add, uint32_t first_instance) {
|
||||
const DrawIndexArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
EXIT_IF(args.offset_source == DrawOffsetSource::DrawState && args.first_instance != 0);
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DrawIndex), submit_id,
|
||||
index_count, flags, type, instance_count,
|
||||
reinterpret_cast<uint64_t>(index_addr));
|
||||
args.index_count, 0, 1, args.instance_count,
|
||||
reinterpret_cast<uint64_t>(args.index_addr));
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (index_count == 0 || instance_count == 0) {
|
||||
if (args.index_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1281,15 +1288,12 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
"\t index_type_and_size = 0x%08" PRIx32 "\n"
|
||||
"\t index_count = 0x%08" PRIx32 "\n"
|
||||
"\t index_addr = 0x%016" PRIx64 "\n"
|
||||
"\t flags = 0x%08" PRIx32 "\n"
|
||||
"\t type = 0x%08" PRIx32 "\n"
|
||||
"\t instance_count = 0x%08" PRIx32 "\n"
|
||||
"\t rt_slice_offset = 0x%08" PRIx32 "\n"
|
||||
"\t vertex_offset_add = 0x%08" PRIx32 "\n"
|
||||
"\t base_vertex = 0x%08" PRIx32 "\n"
|
||||
"\t first_instance = 0x%08" PRIx32 "\n",
|
||||
index_type_and_size, index_count, reinterpret_cast<uint64_t>(index_addr), flags, type,
|
||||
instance_count, render_target_slice_offset, static_cast<uint32_t>(vertex_offset_add),
|
||||
first_instance);
|
||||
args.index_type_and_size, args.index_count,
|
||||
reinterpret_cast<uint64_t>(args.index_addr), args.instance_count,
|
||||
static_cast<uint32_t>(args.base_vertex), args.first_instance);
|
||||
}
|
||||
|
||||
uc_check(ucfg);
|
||||
@@ -1304,44 +1308,42 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
vk::IndexType index_type = vk::IndexType::eUint16;
|
||||
uint64_t index_size = 0;
|
||||
bool expand_index8_to_u16 = false;
|
||||
const bool primitive_restart = ResolvePrimitiveRestart(buffer, topology, index_type_and_size);
|
||||
const bool primitive_restart =
|
||||
ResolvePrimitiveRestart(buffer, topology, args.index_type_and_size);
|
||||
|
||||
switch (static_cast<Prospero::IndexType>(index_type_and_size)) {
|
||||
switch (static_cast<Prospero::IndexType>(args.index_type_and_size)) {
|
||||
case Prospero::IndexType::kIndex16:
|
||||
index_type = vk::IndexType::eUint16;
|
||||
index_size = 2 * static_cast<uint64_t>(index_count);
|
||||
index_size = 2 * static_cast<uint64_t>(args.index_count);
|
||||
break;
|
||||
case Prospero::IndexType::kIndex32:
|
||||
index_type = vk::IndexType::eUint32;
|
||||
index_size = 4 * static_cast<uint64_t>(index_count);
|
||||
index_size = 4 * static_cast<uint64_t>(args.index_count);
|
||||
break;
|
||||
// Some games use it - need vulkan extension
|
||||
case Prospero::IndexType::kIndex8:
|
||||
index_type = vk::IndexType::eUint16;
|
||||
index_size = static_cast<uint64_t>(index_count);
|
||||
index_size = static_cast<uint64_t>(args.index_count);
|
||||
expand_index8_to_u16 = true;
|
||||
break;
|
||||
default: EXIT("unknown index_type_and_size: %u\n", index_type_and_size);
|
||||
default: EXIT("unknown index_type_and_size: %u\n", args.index_type_and_size);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(flags != 0);
|
||||
EXIT_NOT_IMPLEMENTED(type != 1);
|
||||
const DrawCallInfo draw {"DrawIndex", CommandBufferDebugOp::DrawIndex,
|
||||
index_count, flags,
|
||||
instance_count, first_instance};
|
||||
const DrawCallInfo draw {"DrawIndex", CommandBufferDebugOp::DrawIndex, args.index_count,
|
||||
args.instance_count, args.first_instance};
|
||||
std::vector<uint16_t> expanded_indices;
|
||||
if (expand_index8_to_u16) {
|
||||
EXIT_NOT_IMPLEMENTED(index_addr == nullptr);
|
||||
const auto* src = static_cast<const uint8_t*>(index_addr);
|
||||
expanded_indices.resize(index_count);
|
||||
for (uint32_t i = 0; i < index_count; i++) {
|
||||
EXIT_NOT_IMPLEMENTED(args.index_addr == nullptr);
|
||||
const auto* src = static_cast<const uint8_t*>(args.index_addr);
|
||||
expanded_indices.resize(args.index_count);
|
||||
for (uint32_t i = 0; i < args.index_count; i++) {
|
||||
expanded_indices[i] = primitive_restart && src[i] == 0xffu ? 0xffffu : src[i];
|
||||
}
|
||||
}
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
index_source.enabled = true;
|
||||
index_source.address = reinterpret_cast<uint64_t>(index_addr);
|
||||
index_source.address = reinterpret_cast<uint64_t>(args.index_addr);
|
||||
index_source.host_data =
|
||||
expanded_indices.empty() ? nullptr : static_cast<const void*>(expanded_indices.data());
|
||||
index_source.size =
|
||||
@@ -1349,21 +1351,28 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
index_source.type = index_type;
|
||||
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, true, state)) {
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, args.render_target_slice_offset, true,
|
||||
state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshShaders(buffer, draw, true, state);
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, true, false, index_type_and_size, index_addr);
|
||||
LogDrawStateIfNeeded(buffer, draw, state, true, false, args.index_type_and_size,
|
||||
args.index_addr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto vertex_offset =
|
||||
ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) + vertex_offset_add;
|
||||
indirect
|
||||
? args.base_vertex
|
||||
: ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) + args.base_vertex;
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
emit.indexed = true;
|
||||
emit.vertex_offset = vertex_offset;
|
||||
emit.first_instance =
|
||||
indirect ? args.first_instance : ResolveInstanceOffset(state.vs_input_info);
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
|
||||
primitive_restart, true, true, false);
|
||||
@@ -1371,22 +1380,21 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count,
|
||||
uint32_t flags, uint32_t render_target_slice_offset,
|
||||
uint32_t instance_count, uint32_t first_vertex,
|
||||
uint32_t first_instance) {
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
EXIT_IF(args.offset_source == DrawOffsetSource::DrawState && args.first_instance != 0);
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DrawIndexAuto), submit_id,
|
||||
index_count, flags, first_vertex, instance_count, first_instance);
|
||||
args.vertex_count, 0, args.first_vertex, args.instance_count,
|
||||
args.first_instance);
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (index_count == 0 || instance_count == 0) {
|
||||
if (args.vertex_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1409,27 +1417,22 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_
|
||||
hw_print(buffer);
|
||||
|
||||
LOGF("GraphicsRenderDrawIndexAuto():Parameters:\n"
|
||||
"\t index_count = 0x%08" PRIx32 "\n"
|
||||
"\t flags = 0x%08" PRIx32 "\n"
|
||||
"\t rt_slice_offset = 0x%08" PRIx32 "\n"
|
||||
"\t vertex_count = 0x%08" PRIx32 "\n"
|
||||
"\t instance_count = 0x%08" PRIx32 "\n"
|
||||
"\t first_vertex = 0x%08" PRIx32 "\n"
|
||||
"\t first_instance = 0x%08" PRIx32 "\n",
|
||||
index_count, flags, render_target_slice_offset, instance_count, first_vertex,
|
||||
first_instance);
|
||||
args.vertex_count, args.instance_count, args.first_vertex, args.first_instance);
|
||||
}
|
||||
|
||||
uc_check(ucfg);
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(flags != 0);
|
||||
const DrawCallInfo draw {"DrawIndexAuto", CommandBufferDebugOp::DrawIndexAuto,
|
||||
index_count, flags,
|
||||
instance_count, first_instance};
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false,
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, args.render_target_slice_offset, false,
|
||||
state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
@@ -1460,10 +1463,15 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kRectListLegacy, 0,
|
||||
nullptr);
|
||||
|
||||
const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) +
|
||||
static_cast<int32_t>(first_vertex);
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto vertex_offset =
|
||||
indirect ? static_cast<int32_t>(args.first_vertex)
|
||||
: ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) +
|
||||
static_cast<int32_t>(args.first_vertex);
|
||||
DrawEmitInfo emit {};
|
||||
emit.first_vertex = static_cast<uint32_t>(vertex_offset);
|
||||
emit.first_instance =
|
||||
indirect ? args.first_instance : ResolveInstanceOffset(state.vs_input_info);
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
|
||||
|
||||
@@ -7,8 +7,9 @@ namespace Libs::Graphics {
|
||||
|
||||
struct ShaderVertexInputInfo;
|
||||
|
||||
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info);
|
||||
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info);
|
||||
[[nodiscard]] uint32_t ResolveInstanceOffset(const ShaderVertexInputInfo& vs_input_info);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -63,6 +63,17 @@ struct VulkanExtensions {
|
||||
std::vector<vk::LayerProperties> available_layers;
|
||||
};
|
||||
|
||||
vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noexcept {
|
||||
vk::PhysicalDeviceVulkan12Features features {};
|
||||
features.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
|
||||
features.samplerMirrorClampToEdge = VK_TRUE;
|
||||
features.timelineSemaphore = VK_TRUE;
|
||||
features.shaderOutputLayer = VK_TRUE;
|
||||
features.bufferDeviceAddress = VK_TRUE;
|
||||
features.shaderBufferInt64Atomics = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
vk::PhysicalDeviceVulkan13Features WindowContext::RequiredVulkan13Features() noexcept {
|
||||
vk::PhysicalDeviceVulkan13Features features {};
|
||||
features.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
|
||||
@@ -217,6 +228,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
device_features2.pNext = &features13;
|
||||
|
||||
device.getFeatures2(&device_features2);
|
||||
const auto required_features12 = WindowContext::RequiredVulkan12Features();
|
||||
const auto required_features13 = WindowContext::RequiredVulkan13Features();
|
||||
|
||||
const auto queue_family = VulkanFindQueueFamily(device, surface);
|
||||
@@ -249,14 +261,31 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
}
|
||||
#endif
|
||||
|
||||
if (features12.samplerMirrorClampToEdge != VK_TRUE) {
|
||||
if (required_features12.samplerMirrorClampToEdge == VK_TRUE &&
|
||||
features12.samplerMirrorClampToEdge != VK_TRUE) {
|
||||
LOGF("samplerMirrorClampToEdge is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features12.timelineSemaphore != VK_TRUE) {
|
||||
if (required_features12.timelineSemaphore == VK_TRUE &&
|
||||
features12.timelineSemaphore != VK_TRUE) {
|
||||
LOGF("timelineSemaphore is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderOutputLayer == VK_TRUE &&
|
||||
features12.shaderOutputLayer != VK_TRUE) {
|
||||
LOGF("shaderOutputLayer is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.bufferDeviceAddress == VK_TRUE &&
|
||||
features12.bufferDeviceAddress != VK_TRUE) {
|
||||
LOGF("bufferDeviceAddress is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderBufferInt64Atomics == VK_TRUE &&
|
||||
features12.shaderBufferInt64Atomics != VK_TRUE) {
|
||||
LOGF("shaderBufferInt64Atomics is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features13.robustImageAccess != VK_TRUE) {
|
||||
LOGF("robustImageAccess is not supported\n");
|
||||
skip_device = true;
|
||||
@@ -291,11 +320,6 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
LOGF("largePoints is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features12.shaderOutputLayer != VK_TRUE) {
|
||||
LOGF("shaderOutputLayer is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
|
||||
LOGF("fragmentStoresAndAtomics is not supported\n");
|
||||
skip_device = true;
|
||||
@@ -547,10 +571,8 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
#endif
|
||||
depth_clip_control.depthClipControl = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceVulkan12Features features12 {};
|
||||
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
|
||||
features12.pNext = &depth_clip_control;
|
||||
features12.samplerMirrorClampToEdge = VK_TRUE;
|
||||
auto features12 = WindowContext::RequiredVulkan12Features();
|
||||
features12.pNext = &depth_clip_control;
|
||||
|
||||
vk::PhysicalDeviceVulkan13Features supported_features13 {};
|
||||
supported_features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
|
||||
@@ -586,8 +608,12 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
supported_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
|
||||
supported_features2.pNext = &supported_features13;
|
||||
physical_device.getFeatures2(&supported_features2);
|
||||
const auto required_features12 = WindowContext::RequiredVulkan12Features();
|
||||
const auto required_features13 = WindowContext::RequiredVulkan13Features();
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.timelineSemaphore != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.samplerMirrorClampToEdge == VK_TRUE &&
|
||||
supported_features12.samplerMirrorClampToEdge != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.timelineSemaphore == VK_TRUE &&
|
||||
supported_features12.timelineSemaphore != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features13.dynamicRendering == VK_TRUE &&
|
||||
supported_features13.dynamicRendering != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features13.synchronization2 == VK_TRUE &&
|
||||
@@ -599,15 +625,15 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.bufferDeviceAddress != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.shaderOutputLayer == VK_TRUE &&
|
||||
supported_features12.shaderOutputLayer != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.bufferDeviceAddress == VK_TRUE &&
|
||||
supported_features12.bufferDeviceAddress != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.shaderBufferInt64Atomics == VK_TRUE &&
|
||||
supported_features12.shaderBufferInt64Atomics != VK_TRUE);
|
||||
#if !defined(__APPLE__)
|
||||
EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE);
|
||||
#endif
|
||||
features12.timelineSemaphore = VK_TRUE;
|
||||
features12.shaderOutputLayer = VK_TRUE;
|
||||
features12.bufferDeviceAddress = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
|
||||
@@ -814,6 +814,7 @@ static void WindowCreate(WindowContext& context) {
|
||||
int width = static_cast<int>(context.graphic_ctx.screen_width);
|
||||
int height = static_cast<int>(context.graphic_ctx.screen_height);
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0");
|
||||
@@ -899,6 +900,7 @@ void WindowRun() {
|
||||
|
||||
void WindowShutdown() {
|
||||
if (g_window != nullptr) {
|
||||
Controller::EmergencyShutdown();
|
||||
g_window.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ struct WindowContext {
|
||||
~WindowContext();
|
||||
KYTY_CLASS_NO_COPY(WindowContext);
|
||||
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan12Features RequiredVulkan12Features() noexcept;
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept;
|
||||
[[nodiscard]] static uint32_t InitialWindowFlags(bool fullscreen) noexcept;
|
||||
void CreateVulkan();
|
||||
|
||||
@@ -234,8 +234,10 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
uint32_t wave_size) {
|
||||
EmbeddedFetchData data;
|
||||
data.loads.reserve(input_info->resources_num);
|
||||
int32_t offset_candidate = -1;
|
||||
bool offset_conflict = false;
|
||||
int32_t vertex_offset_candidate = -1;
|
||||
int32_t instance_offset_candidate = -1;
|
||||
bool vertex_offset_conflict = false;
|
||||
bool instance_offset_conflict = false;
|
||||
|
||||
const int shift_regs = 8;
|
||||
const int attrib_reg = input_info->fetch_attrib_reg + shift_regs;
|
||||
@@ -263,26 +265,34 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
|
||||
for (const auto& inst: decoded.instructions) {
|
||||
// Fetch shaders accumulate the draw's vertex offset in v0. The PS5 NGG ABI
|
||||
// seeds S_NGG_VERTEX_INDEX in v5 and applies the same offset there before fetching.
|
||||
// seeds S_NGG_VERTEX_INDEX in v5 and S_NGG_INSTANCE_INDEX in v8, then applies the
|
||||
// corresponding direct-draw offsets before fetching.
|
||||
const bool vertex_index_accumulator =
|
||||
IsDecodedVgpr(inst.dst) &&
|
||||
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5));
|
||||
const bool instance_index_accumulator =
|
||||
IsDecodedVgpr(inst.dst) && (inst.dst.reg == (user_data_base == 8 ? 8u : 3u));
|
||||
uint32_t sad_zero = 0;
|
||||
const bool vertex_offset_add =
|
||||
vertex_index_accumulator && IsDecodedSgpr(inst.src0) &&
|
||||
const bool index_offset_add =
|
||||
(vertex_index_accumulator || instance_index_accumulator) &&
|
||||
IsDecodedSgpr(inst.src0) &&
|
||||
((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) &&
|
||||
inst.src1.reg == inst.dst.reg) ||
|
||||
(user_data_base == 8 && inst.dst.reg == 5 &&
|
||||
(user_data_base == 8 && (inst.dst.reg == 5 || inst.dst.reg == 8) &&
|
||||
inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) &&
|
||||
inst.src2.reg == inst.dst.reg &&
|
||||
TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0));
|
||||
if (data.loads.empty() && vertex_offset_add) {
|
||||
if (data.loads.empty() && index_offset_add) {
|
||||
const auto reg = DecodedSgprReg(inst.src0);
|
||||
if (reg >= user_data_base && reg - user_data_base < user_data_count) {
|
||||
if (offset_candidate >= 0 && offset_candidate != static_cast<int32_t>(reg)) {
|
||||
offset_conflict = true;
|
||||
auto& candidate = vertex_index_accumulator ? vertex_offset_candidate
|
||||
: instance_offset_candidate;
|
||||
auto& conflict = vertex_index_accumulator ? vertex_offset_conflict
|
||||
: instance_offset_conflict;
|
||||
if (candidate >= 0 && candidate != static_cast<int32_t>(reg)) {
|
||||
conflict = true;
|
||||
} else {
|
||||
offset_candidate = static_cast<int32_t>(reg);
|
||||
candidate = static_cast<int32_t>(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,8 +451,13 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
load.attrib_id = buffer.attrib_id;
|
||||
load.components = DecodedDstSize(inst);
|
||||
load.prolog_loads = buffer.prolog_loads;
|
||||
if (data.loads.empty() && !offset_conflict) {
|
||||
data.vertex_offset_sgpr = offset_candidate;
|
||||
if (data.loads.empty()) {
|
||||
if (!vertex_offset_conflict) {
|
||||
data.vertex_offset_sgpr = vertex_offset_candidate;
|
||||
}
|
||||
if (!instance_offset_conflict) {
|
||||
data.instance_offset_sgpr = instance_offset_candidate;
|
||||
}
|
||||
}
|
||||
data.loads.push_back(load);
|
||||
}
|
||||
@@ -615,7 +630,8 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
IR::TrackResources(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
if (options.stage == ShaderType::Vertex) {
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
ir.info.instance_offset_sgpr = embedded_fetch.instance_offset_sgpr;
|
||||
}
|
||||
|
||||
TranslateResult result;
|
||||
|
||||
@@ -189,6 +189,10 @@ void AnalyzeProgramRequirements(IR::Program& program) {
|
||||
const auto MarkBallot = [&] { requirements.subgroup_ballot = true; };
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::Atomic &&
|
||||
inst.GetType() == IR::Type::U64) {
|
||||
requirements.buffer_int64_atomics = true;
|
||||
}
|
||||
const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access;
|
||||
if (address_access != IR::AddressAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
|
||||
@@ -39,6 +39,7 @@ enum : uint32_t {
|
||||
MemoryModelGLSL450 = 1,
|
||||
CapabilityShader = 1,
|
||||
CapabilityInt64 = 11,
|
||||
CapabilityInt64Atomics = 12,
|
||||
CapabilityImageGatherExtended = 25,
|
||||
CapabilityClipDistance = 32,
|
||||
CapabilityCullDistance = 33,
|
||||
@@ -73,6 +74,7 @@ enum : uint32_t {
|
||||
DecorationBuiltIn = 11,
|
||||
DecorationNoPerspective = 13,
|
||||
DecorationFlat = 14,
|
||||
DecorationAliased = 20,
|
||||
DecorationLocation = 30,
|
||||
DecorationArrayStride = 6,
|
||||
DecorationBinding = 33,
|
||||
@@ -128,6 +130,7 @@ enum : uint32_t {
|
||||
MemorySemanticsAcquireRelease = 0x00000008u,
|
||||
MemorySemanticsUniformMemory = 0x00000040u,
|
||||
MemorySemanticsWorkgroupMemory = 0x00000100u,
|
||||
MemorySemanticsImageMemory = 0x00000800u,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
@@ -352,6 +355,7 @@ struct EmitterState {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
uint32_t storage_buffer_u64_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
@@ -390,6 +394,7 @@ uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeU32(EmitterState& state);
|
||||
uint32_t TypeU64(EmitterState& state);
|
||||
uint32_t TypeDeviceAddress(EmitterState& state);
|
||||
uint32_t TypeScalarU64(EmitterState& state);
|
||||
uint32_t TypeU32Pair(EmitterState& state);
|
||||
uint32_t TypeI32(EmitterState& state);
|
||||
uint32_t TypeI32Pair(EmitterState& state);
|
||||
@@ -403,6 +408,8 @@ uint32_t TypePointer(EmitterState& state, uint32_t storage_class, uint32_t point
|
||||
uint32_t TypeFunction(EmitterState& state);
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferU64Pointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferU64ElementPointer(EmitterState& state);
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state);
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state);
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state);
|
||||
@@ -618,11 +625,17 @@ struct MemoryResourceAccess {
|
||||
uint32_t object_pointer = 0;
|
||||
uint32_t length = 0;
|
||||
uint32_t index_offset = 0;
|
||||
uint32_t byte_offset = 0;
|
||||
bool add_index_offset = false;
|
||||
};
|
||||
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem);
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable,
|
||||
uint32_t pointer_type);
|
||||
|
||||
uint32_t EmitMemoryElementIndex(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t raw_index);
|
||||
|
||||
@@ -632,6 +645,10 @@ uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAcce
|
||||
uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t index);
|
||||
|
||||
uint32_t EmitStorageBufferElementPointer(EmitterState& state,
|
||||
const MemoryResourceAccess& access, uint32_t index,
|
||||
uint32_t pointer_type);
|
||||
|
||||
uint32_t EmitTBufferBitcastF32ToU32(EmitterState& state, uint32_t value);
|
||||
|
||||
uint32_t EmitTBufferBitcastU32ToF32(EmitterState& state, uint32_t value);
|
||||
@@ -655,6 +672,9 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
|
||||
void EmitDeviceAtomicMemoryBarrier(EmitterState& state);
|
||||
|
||||
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
|
||||
bool max_value);
|
||||
|
||||
uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
|
||||
|
||||
uint32_t EmitSelectValueU32(EmitterState& state, uint32_t cond, uint32_t true_value,
|
||||
@@ -770,6 +790,47 @@ uint32_t EmitValueOrZeroIfCondition(EmitterState& state, uint32_t condition, Fn&
|
||||
std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, Fn&& desired) {
|
||||
const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
|
||||
const auto memory = [&] {
|
||||
switch (kind) {
|
||||
case IR::ResourceKind::Lds: return MemorySemanticsWorkgroupMemory;
|
||||
case IR::ResourceKind::Image: return MemorySemanticsImageMemory;
|
||||
default: return MemorySemanticsUniformMemory;
|
||||
}
|
||||
}();
|
||||
const auto preheader = state.builder.AllocateId();
|
||||
const auto header = state.builder.AllocateId();
|
||||
const auto cont = state.builder.AllocateId();
|
||||
const auto merge = state.builder.AllocateId();
|
||||
const auto initial = state.builder.AllocateId();
|
||||
const auto observed = state.builder.AllocateId();
|
||||
const auto exchanged = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBranch, preheader});
|
||||
EmitLabel(state, preheader);
|
||||
state.builder.AddFunction({OpAtomicLoad, TypeU32(state), initial, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone)});
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, header);
|
||||
state.builder.AddFunction(
|
||||
{OpPhi, TypeU32(state), observed, initial, preheader, exchanged, cont});
|
||||
const auto next = desired(observed);
|
||||
state.builder.AddFunction({OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone),
|
||||
ConstantU32(state, MemorySemanticsNone), next, observed});
|
||||
const auto success = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpIEqual, TypeBool(state), success, exchanged, observed});
|
||||
state.builder.AddFunction({OpLoopMerge, merge, cont, LoopControlNone});
|
||||
state.builder.AddFunction({OpBranchConditional, success, merge, cont});
|
||||
EmitLabel(state, cont);
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, merge);
|
||||
state.builder.AddFunction({OpMemoryBarrier, ConstantU32(state, scope),
|
||||
ConstantU32(state, MemorySemanticsAcquireRelease | memory)});
|
||||
return observed;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_INTERNAL_H_ */
|
||||
@@ -481,41 +481,6 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, Fn&& desired) {
|
||||
const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
|
||||
const auto memory = kind == IR::ResourceKind::Lds ? MemorySemanticsWorkgroupMemory
|
||||
: MemorySemanticsUniformMemory;
|
||||
const auto preheader = state.builder.AllocateId();
|
||||
const auto header = state.builder.AllocateId();
|
||||
const auto cont = state.builder.AllocateId();
|
||||
const auto merge = state.builder.AllocateId();
|
||||
const auto initial = state.builder.AllocateId();
|
||||
const auto observed = state.builder.AllocateId();
|
||||
const auto exchanged = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBranch, preheader});
|
||||
EmitLabel(state, preheader);
|
||||
state.builder.AddFunction({OpAtomicLoad, TypeU32(state), initial, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone)});
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, header);
|
||||
state.builder.AddFunction(
|
||||
{OpPhi, TypeU32(state), observed, initial, preheader, exchanged, cont});
|
||||
const auto next = desired(observed);
|
||||
state.builder.AddFunction({OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone),
|
||||
ConstantU32(state, MemorySemanticsNone), next, observed});
|
||||
const auto success = Binary(state, OpIEqual, TypeBool(state), exchanged, observed);
|
||||
state.builder.AddFunction({OpLoopMerge, merge, cont, LoopControlNone});
|
||||
state.builder.AddFunction({OpBranchConditional, success, merge, cont});
|
||||
EmitLabel(state, cont);
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, merge);
|
||||
state.builder.AddFunction({OpMemoryBarrier, ConstantU32(state, scope),
|
||||
ConstantU32(state, MemorySemanticsAcquireRelease | memory)});
|
||||
return observed;
|
||||
}
|
||||
|
||||
void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource, uint32_t address, uint32_t index,
|
||||
uint32_t bits, uint32_t data) {
|
||||
@@ -614,9 +579,11 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t AtomicOpcode(IR::ValueOpcode opcode) {
|
||||
uint32_t SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::ValueOpcode::BufferAtomicCmpSwap32: return OpAtomicCompareExchange;
|
||||
case IR::ValueOpcode::BufferAtomicSwap32:
|
||||
case IR::ValueOpcode::BufferAtomicSwap64:
|
||||
case IR::ValueOpcode::SharedAtomicSwap32: return OpAtomicExchange;
|
||||
case IR::ValueOpcode::BufferAtomicIAdd32:
|
||||
case IR::ValueOpcode::SharedAtomicIAdd32: return OpAtomicIAdd;
|
||||
@@ -633,6 +600,7 @@ uint32_t AtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::BufferAtomicAnd32:
|
||||
case IR::ValueOpcode::SharedAtomicAnd32: return OpAtomicAnd;
|
||||
case IR::ValueOpcode::BufferAtomicOr32:
|
||||
case IR::ValueOpcode::BufferAtomicOr64:
|
||||
case IR::ValueOpcode::SharedAtomicOr32: return OpAtomicOr;
|
||||
case IR::ValueOpcode::BufferAtomicXor32:
|
||||
case IR::ValueOpcode::SharedAtomicXor32: return OpAtomicXor;
|
||||
@@ -640,23 +608,37 @@ uint32_t AtomicOpcode(IR::ValueOpcode opcode) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EmitAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
bool return_value) {
|
||||
uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t pointer,
|
||||
uint32_t scope) {
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::BufferAtomicCmpSwap32) {
|
||||
const auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
|
||||
const auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpAtomicCompareExchange, TypeU32(ctx.state), old, pointer, ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, MemorySemanticsNone),
|
||||
ConstantU32(ctx.state, MemorySemanticsNone), desired, comparator});
|
||||
} else {
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction(
|
||||
{SpirvAtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old, pointer,
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, MemorySemanticsNone), value});
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
uint32_t EmitAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
const auto result =
|
||||
EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
|
||||
return EmitValueOrZeroIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index),
|
||||
[&]() {
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
const auto scope =
|
||||
mem.kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
|
||||
ctx.state.builder.AddFunction(
|
||||
{AtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old,
|
||||
EmitMemoryElementPointer(ctx.state, access.resource, access.index),
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, MemorySemanticsNone),
|
||||
value});
|
||||
const auto pointer =
|
||||
EmitMemoryElementPointer(ctx.state, access.resource, access.index);
|
||||
const auto old = EmitAtomicOperation(ctx, inst, pointer, scope);
|
||||
if (mem.kind == IR::ResourceKind::Lds) {
|
||||
const auto semantics =
|
||||
MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
|
||||
@@ -669,40 +651,36 @@ uint32_t EmitAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
|
||||
return old;
|
||||
});
|
||||
});
|
||||
return return_value ? result : ConstantU32(ctx.state, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t FloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
|
||||
bool max_value) {
|
||||
struct OrderedBits {
|
||||
uint32_t nan;
|
||||
uint32_t zero;
|
||||
uint32_t key;
|
||||
};
|
||||
const auto classify = [&](uint32_t bits) {
|
||||
const auto value = Unary(state, OpBitcast, TypeF32(state), bits);
|
||||
const auto cls = EmitClassifyF32(state, value);
|
||||
const auto negative = Binary(
|
||||
state, OpINotEqual, TypeBool(state),
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state), bits, ConstantU32(state, 0x80000000u)),
|
||||
ConstantU32(state, 0));
|
||||
const auto negative_key = Unary(state, OpNot, TypeU32(state), bits);
|
||||
const auto positive_key =
|
||||
Binary(state, OpBitwiseXor, TypeU32(state), bits, ConstantU32(state, 0x80000000u));
|
||||
return OrderedBits {cls.nan, cls.zero,
|
||||
Select(state, TypeU32(state), negative, negative_key, positive_key)};
|
||||
};
|
||||
const auto source_class = classify(source);
|
||||
const auto old_class = classify(old);
|
||||
const auto unordered =
|
||||
Binary(state, OpLogicalOr, TypeBool(state),
|
||||
Binary(state, OpLogicalOr, TypeBool(state), source_class.nan, old_class.nan),
|
||||
Binary(state, OpLogicalAnd, TypeBool(state), source_class.zero, old_class.zero));
|
||||
const auto ordered = Unary(state, OpLogicalNot, TypeBool(state), unordered);
|
||||
const auto compare = Binary(state, max_value ? OpUGreaterThan : OpULessThan, TypeBool(state),
|
||||
source_class.key, old_class.key);
|
||||
return Select(state, TypeU32(state),
|
||||
Binary(state, OpLogicalAnd, TypeBool(state), ordered, compare), source, old);
|
||||
uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU64(state), ConstantU64(state, 0), [&]() {
|
||||
const auto resource = PrepareStorageBufferResourceAccess(
|
||||
state, mem, state.storage_buffer_u64_variable, TypeStorageBufferU64Pointer(state));
|
||||
const auto byte_address = Binary(state, OpIAdd, TypeU32(state),
|
||||
ByteAddress(ctx, inst, mem), resource.byte_offset);
|
||||
const auto index = Binary(state, OpShiftRightLogical, TypeU32(state), byte_address,
|
||||
ConstantU32(state, 3u));
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, EmitMemoryElementInBounds(state, resource, index), TypeU64(state),
|
||||
ConstantU64(state, 0), [&]() {
|
||||
const auto value = Unary(state, OpBitcast, TypeScalarU64(state),
|
||||
ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), old,
|
||||
EmitStorageBufferElementPointer(
|
||||
state, resource, index, TypeStorageBufferU64ElementPointer(state)),
|
||||
ConstantU32(state, ScopeDevice),
|
||||
ConstantU32(state, MemorySemanticsNone), value});
|
||||
EmitDeviceAtomicMemoryBarrier(state);
|
||||
return Unary(state, OpBitcast, TypeU64(state), old);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t FloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
@@ -715,7 +693,7 @@ uint32_t FloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memo
|
||||
EmitMemoryElementPointer(ctx.state, access.resource, access.index);
|
||||
const auto source = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
return AtomicUpdate(ctx.state, pointer, mem.kind, [&](uint32_t old) {
|
||||
return FloatAtomicReplacement(ctx.state, old, source, max_value);
|
||||
return EmitFloatAtomicReplacement(ctx.state, old, source, max_value);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1080,12 +1058,15 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
const auto buffer_components = IR::BufferComponentCount(op);
|
||||
if (buffer_components > 1u) {
|
||||
if (IR::BufferAccessOf(op) == IR::BufferAccess::Read) {
|
||||
const auto access = IR::BufferAccessOf(op);
|
||||
if (access == IR::BufferAccess::Read) {
|
||||
ctx.Define(inst, LoadWideBuffer(ctx, inst, buffer_components));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
if (access == IR::BufferAccess::Write) {
|
||||
StoreWideBuffer(ctx, inst, buffer_components);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const auto shared_components = IR::SharedComponentCount(op);
|
||||
if (shared_components > 1u) {
|
||||
@@ -1176,9 +1157,13 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
StoreWord(ctx, inst, mem);
|
||||
return true;
|
||||
}
|
||||
const auto atomic_opcode = AtomicOpcode(op);
|
||||
const auto atomic_opcode = SpirvAtomicOpcode(op);
|
||||
if (atomic_opcode != 0 && inst.GetType() == IR::Type::U64) {
|
||||
ctx.Define(inst, EmitBufferAtomic64(ctx, inst, ctx.Memory(inst)));
|
||||
return true;
|
||||
}
|
||||
if (atomic_opcode != 0) {
|
||||
ctx.Define(inst, EmitAtomic(ctx, inst, ctx.Memory(inst), true));
|
||||
ctx.Define(inst, EmitAtomic(ctx, inst, ctx.Memory(inst)));
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::BufferAtomicFMin32 || op == IR::ValueOpcode::BufferAtomicFMax32) {
|
||||
|
||||
@@ -81,6 +81,27 @@ static void EnsureLdsStorage(EmitterState& state) {
|
||||
state.builder.AddName(state.lds_variable, "lds_dwords");
|
||||
}
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable,
|
||||
uint32_t pointer_type) {
|
||||
if (variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
|
||||
"storage buffer descriptor array was not emitted");
|
||||
}
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
|
||||
MemoryResourceAccess access {.kind = mem.kind};
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, pointer_type, access.object_pointer, variable,
|
||||
ConstantU32(state, array_index)});
|
||||
access.byte_offset = state.memory_byte_offsets[array_index];
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpArrayLength, TypeU32(state), access.length, access.object_pointer, 0});
|
||||
return access;
|
||||
}
|
||||
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem) {
|
||||
MemoryResourceAccess access {.kind = mem.kind};
|
||||
switch (mem.kind) {
|
||||
@@ -113,22 +134,13 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
EXIT("physical address memory must use the BDA emitter\n");
|
||||
case IR::ResourceKind::ScalarBuffer:
|
||||
case IR::ResourceKind::Buffer: {
|
||||
if (state.storage_buffer_variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers,
|
||||
mem.resource,
|
||||
"storage buffer descriptor array was not emitted");
|
||||
}
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferPointer(state),
|
||||
access.object_pointer, state.storage_buffer_variable,
|
||||
ConstantU32(state, array_index)});
|
||||
access = PrepareStorageBufferResourceAccess(
|
||||
state, mem, state.storage_buffer_variable, TypeStorageBufferPointer(state));
|
||||
access.index_offset =
|
||||
EmitBinaryU32(state, OpShiftRightLogical, state.memory_byte_offsets[array_index],
|
||||
EmitBinaryU32(state, OpShiftRightLogical, access.byte_offset,
|
||||
ConstantU32(state, 2u));
|
||||
access.add_index_offset = true;
|
||||
break;
|
||||
return access;
|
||||
}
|
||||
default: EXIT("unsupported memory resource kind: %u\n", static_cast<unsigned>(mem.kind));
|
||||
}
|
||||
@@ -152,17 +164,25 @@ uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAcce
|
||||
|
||||
uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
if (access.kind == IR::ResourceKind::Lds || access.kind == IR::ResourceKind::Scratch) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto storage_class = access.kind == IR::ResourceKind::Scratch ? StorageClassFunction
|
||||
: state.stage == ShaderType::Compute ? StorageClassWorkgroup
|
||||
: StorageClassFunction;
|
||||
state.builder.AddFunction({OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, index});
|
||||
} else {
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
access.object_pointer, ConstantU32(state, 0), index});
|
||||
return pointer;
|
||||
}
|
||||
return EmitStorageBufferElementPointer(state, access, index,
|
||||
TypeStorageBufferElementPointer(state));
|
||||
}
|
||||
|
||||
uint32_t EmitStorageBufferElementPointer(EmitterState& state,
|
||||
const MemoryResourceAccess& access, uint32_t index,
|
||||
uint32_t pointer_type) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, pointer_type, pointer, access.object_pointer,
|
||||
ConstantU32(state, 0), index});
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@@ -305,6 +325,39 @@ void EmitDeviceAtomicMemoryBarrier(EmitterState& state) {
|
||||
{OpMemoryBarrier, ConstantU32(state, ScopeDevice), ConstantU32(state, semantics)});
|
||||
}
|
||||
|
||||
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
|
||||
bool max_value) {
|
||||
struct OrderedBits {
|
||||
uint32_t nan;
|
||||
uint32_t zero;
|
||||
uint32_t key;
|
||||
};
|
||||
const auto classify = [&](uint32_t bits) {
|
||||
const auto value = EmitBitcastU32ToF32(state, bits);
|
||||
const auto cls = EmitClassifyF32(state, value);
|
||||
const auto negative = EmitCompareU32Constant(
|
||||
state, OpINotEqual, EmitAndConstant(state, bits, 0x80000000u), 0u);
|
||||
const auto negative_key = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpNot, TypeU32(state), negative_key, bits});
|
||||
const auto positive_key = EmitBinaryU32(
|
||||
state, OpBitwiseXor, bits, ConstantU32(state, 0x80000000u));
|
||||
return OrderedBits {
|
||||
cls.nan, cls.zero,
|
||||
EmitSelectValueU32(state, negative, negative_key, positive_key)};
|
||||
};
|
||||
const auto source_class = classify(source);
|
||||
const auto old_class = classify(old);
|
||||
const auto unordered = EmitLogicalOrBool(
|
||||
state, EmitLogicalOrBool(state, source_class.nan, old_class.nan),
|
||||
EmitLogicalAndBool(state, source_class.zero, old_class.zero));
|
||||
const auto compare = state.builder.AllocateId();
|
||||
state.builder.AddFunction({max_value ? OpUGreaterThan : OpULessThan, TypeBool(state), compare,
|
||||
source_class.key, old_class.key});
|
||||
return EmitSelectValueU32(
|
||||
state, EmitLogicalAndBool(state, EmitLogicalNotBool(state, unordered), compare), source,
|
||||
old);
|
||||
}
|
||||
|
||||
uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
|
||||
if ((control & 0xc000u) == 0xc000u) {
|
||||
const uint32_t mask = control & 0x1fu;
|
||||
|
||||
@@ -29,6 +29,10 @@ uint32_t TypeU64(EmitterState& state) {
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddress(EmitterState& state) {
|
||||
return TypeScalarU64(state);
|
||||
}
|
||||
|
||||
uint32_t TypeScalarU64(EmitterState& state) {
|
||||
return state.builder.Type(OpTypeInt, {64, 0});
|
||||
}
|
||||
|
||||
@@ -94,6 +98,25 @@ uint32_t TypeStorageBufferElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeU32(state));
|
||||
}
|
||||
|
||||
uint32_t StorageU64RuntimeArrayType(EmitterState& state) {
|
||||
return state.builder.DecoratedType(OpTypeRuntimeArray, {TypeScalarU64(state)},
|
||||
{{OpDecorate, {DecorationArrayStride, sizeof(uint64_t)}}});
|
||||
}
|
||||
|
||||
uint32_t StorageBufferU64Type(EmitterState& state) {
|
||||
return state.builder.DecoratedType(
|
||||
OpTypeStruct, {StorageU64RuntimeArrayType(state)},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, 0}}, {OpDecorate, {DecorationBlock}}});
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferU64Pointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, StorageBufferU64Type(state));
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferU64ElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeScalarU64(state));
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeDeviceAddress(state));
|
||||
}
|
||||
@@ -162,6 +185,13 @@ void DefineDescriptorVariables(EmitterState& state) {
|
||||
const auto pointer_type = TypePointer(state, StorageClassStorageBuffer, array_type);
|
||||
state.storage_buffer_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
|
||||
if (state.requirements.buffer_int64_atomics) {
|
||||
const auto u64_array_type =
|
||||
state.builder.Type(OpTypeArray, {StorageBufferU64Type(state), count});
|
||||
state.storage_buffer_u64_variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, StorageClassStorageBuffer, u64_array_type),
|
||||
StorageClassStorageBuffer);
|
||||
}
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::BdaPagetable) != nullptr) {
|
||||
state.bda_pagetable_variable = state.builder.DefineGlobalVariable(
|
||||
@@ -570,6 +600,14 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
if (state.storage_buffer_variable != 0) {
|
||||
Decorate(state.storage_buffer_variable, "buffers", IR::DescriptorBindingKind::Buffers);
|
||||
}
|
||||
if (state.storage_buffer_u64_variable != 0) {
|
||||
Decorate(state.storage_buffer_u64_variable, "buffers_u64",
|
||||
IR::DescriptorBindingKind::Buffers);
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, state.storage_buffer_variable, DecorationAliased});
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, state.storage_buffer_u64_variable, DecorationAliased});
|
||||
}
|
||||
if (state.bda_pagetable_variable != 0) {
|
||||
Decorate(state.bda_pagetable_variable, "bda_pagetable",
|
||||
IR::DescriptorBindingKind::BdaPagetable);
|
||||
@@ -627,6 +665,10 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireCapability(CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
}
|
||||
if (state.requirements.buffer_int64_atomics) {
|
||||
state.builder.RequireCapability(CapabilityInt64);
|
||||
state.builder.RequireCapability(CapabilityInt64Atomics);
|
||||
}
|
||||
if (state.clip_distance_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityClipDistance);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x1eu, Opcode::BUFFER_STORE_DWORDX4, 4, 32},
|
||||
{0x1fu, Opcode::BUFFER_STORE_DWORDX3, 3, 32},
|
||||
{0x30u, Opcode::BUFFER_ATOMIC_SWAP, 1, 32},
|
||||
{0x31u, Opcode::BUFFER_ATOMIC_CMPSWAP, 1, 32},
|
||||
{0x32u, Opcode::BUFFER_ATOMIC_ADD, 1, 32},
|
||||
{0x33u, Opcode::BUFFER_ATOMIC_SUB, 1, 32},
|
||||
{0x35u, Opcode::BUFFER_ATOMIC_SMIN, 1, 32},
|
||||
@@ -58,6 +59,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x3bu, Opcode::BUFFER_ATOMIC_XOR, 1, 32},
|
||||
{0x3fu, Opcode::BUFFER_ATOMIC_FMIN, 1, 32},
|
||||
{0x40u, Opcode::BUFFER_ATOMIC_FMAX, 1, 32},
|
||||
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32},
|
||||
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
||||
|
||||
@@ -592,6 +592,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::TBUFFER_STORE_FORMAT_XYZ:
|
||||
case Opcode::TBUFFER_STORE_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_ATOMIC_SWAP:
|
||||
case Opcode::BUFFER_ATOMIC_CMPSWAP:
|
||||
case Opcode::BUFFER_ATOMIC_SWAP_X2:
|
||||
case Opcode::BUFFER_ATOMIC_ADD:
|
||||
case Opcode::BUFFER_ATOMIC_SUB:
|
||||
case Opcode::BUFFER_ATOMIC_SMIN:
|
||||
@@ -600,6 +602,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_ATOMIC_UMAX:
|
||||
case Opcode::BUFFER_ATOMIC_AND:
|
||||
case Opcode::BUFFER_ATOMIC_OR:
|
||||
case Opcode::BUFFER_ATOMIC_OR_X2:
|
||||
case Opcode::BUFFER_ATOMIC_XOR:
|
||||
case Opcode::BUFFER_ATOMIC_FMIN:
|
||||
case Opcode::BUFFER_ATOMIC_FMAX:
|
||||
|
||||
@@ -455,6 +455,8 @@ enum class Opcode {
|
||||
TBUFFER_STORE_FORMAT_XYZ,
|
||||
TBUFFER_STORE_FORMAT_XYZW,
|
||||
BUFFER_ATOMIC_SWAP,
|
||||
BUFFER_ATOMIC_CMPSWAP,
|
||||
BUFFER_ATOMIC_SWAP_X2,
|
||||
BUFFER_ATOMIC_ADD,
|
||||
BUFFER_ATOMIC_SUB,
|
||||
BUFFER_ATOMIC_SMIN,
|
||||
@@ -463,6 +465,7 @@ enum class Opcode {
|
||||
BUFFER_ATOMIC_UMAX,
|
||||
BUFFER_ATOMIC_AND,
|
||||
BUFFER_ATOMIC_OR,
|
||||
BUFFER_ATOMIC_OR_X2,
|
||||
BUFFER_ATOMIC_XOR,
|
||||
BUFFER_ATOMIC_FMIN,
|
||||
BUFFER_ATOMIC_FMAX,
|
||||
|
||||
@@ -522,10 +522,24 @@ bool Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetBufferResource(memory);
|
||||
const auto address = ReadBufferAddress(inst, 1);
|
||||
const auto result = ir.Emit(opcode,
|
||||
{resource, address.index, address.offset, address.soffset,
|
||||
ReadU32(MemorySourceAt(inst, 0)), ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
const auto data_src = MemorySourceAt(inst, 0);
|
||||
const auto flags = AddMemoryInfo(memory, inst.pc);
|
||||
IR::Value result;
|
||||
if (opcode == IR::ValueOpcode::BufferAtomicCmpSwap32) {
|
||||
const auto desired = ReadU32(data_src);
|
||||
const auto comparator = ReadU32(OffsetOperand(data_src, 1u));
|
||||
result = ir.Emit(opcode,
|
||||
{resource, address.index, address.offset, address.soffset, desired,
|
||||
comparator, ir.GetExec()},
|
||||
flags);
|
||||
} else {
|
||||
const IR::Value value =
|
||||
memory.data_dwords == 2u ? IR::Value(ReadU64(data_src)) : IR::Value(ReadU32(data_src));
|
||||
result = ir.Emit(opcode,
|
||||
{resource, address.index, address.offset, address.soffset, value,
|
||||
ir.GetExec()},
|
||||
flags);
|
||||
}
|
||||
if (inst.glc) {
|
||||
WriteOperand(inst.dst, result);
|
||||
}
|
||||
@@ -919,6 +933,10 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_SWAP:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSwap32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_CMPSWAP:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicCmpSwap32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_SWAP_X2:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSwap64);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_ADD:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicIAdd32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_SUB:
|
||||
@@ -935,6 +953,8 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicAnd32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_OR:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicOr32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_OR_X2:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicOr64);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_XOR:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicXor32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_FMIN:
|
||||
|
||||
@@ -950,7 +950,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
result.cfg_failure_kind = options.cfg_failure_kind;
|
||||
result.fallback_reason = options.fallback_reason;
|
||||
if (options.embedded_fetch != nullptr) {
|
||||
result.info.vertex_offset_sgpr = options.embedded_fetch->vertex_offset_sgpr;
|
||||
result.info.vertex_offset_sgpr = options.embedded_fetch->vertex_offset_sgpr;
|
||||
result.info.instance_offset_sgpr = options.embedded_fetch->instance_offset_sgpr;
|
||||
}
|
||||
|
||||
uint32_t vector_limit = 1u;
|
||||
|
||||
@@ -19,7 +19,8 @@ struct EmbeddedFetchLoad {
|
||||
|
||||
struct EmbeddedFetchPlan {
|
||||
std::vector<EmbeddedFetchLoad> loads;
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
int32_t instance_offset_sgpr = -1;
|
||||
};
|
||||
|
||||
struct TranslateOptions {
|
||||
|
||||
@@ -184,7 +184,6 @@ enum class StageOutputKind {
|
||||
struct PositionExportComponent {
|
||||
uint32_t clip_distance = UINT32_MAX;
|
||||
uint32_t cull_distance = UINT32_MAX;
|
||||
bool valid = false;
|
||||
bool point_size = false;
|
||||
bool layer = false;
|
||||
bool viewport = false;
|
||||
@@ -213,7 +212,6 @@ inline PositionExportComponent DecodePositionExportComponent(uint32_t control,
|
||||
return result;
|
||||
}
|
||||
|
||||
result.valid = true;
|
||||
if (vector == 0) {
|
||||
result.point_size = component == 0 && (control & (1u << 16u)) != 0;
|
||||
result.layer = component == 2 && (control & (1u << 18u)) != 0;
|
||||
@@ -418,6 +416,7 @@ struct ShaderInfo {
|
||||
std::vector<StageOutput> outputs;
|
||||
std::array<uint8_t, 32> vertex_fetch_components {};
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
int32_t instance_offset_sgpr = -1;
|
||||
bool has_bitwise_xor = false;
|
||||
bool uses_dma = false;
|
||||
|
||||
@@ -433,6 +432,7 @@ struct SpirvRequirements {
|
||||
bool function_lds = false;
|
||||
bool function_scratch = false;
|
||||
bool pixel_valid_mask = false;
|
||||
bool buffer_int64_atomics = false;
|
||||
};
|
||||
|
||||
struct BlockInfo {
|
||||
|
||||
@@ -111,6 +111,8 @@ BufferAccess BufferAccessOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::StoreBufferU32x3:
|
||||
case ValueOpcode::StoreBufferU32x4: return BufferAccess::Write;
|
||||
case ValueOpcode::BufferAtomicSwap32:
|
||||
case ValueOpcode::BufferAtomicCmpSwap32:
|
||||
case ValueOpcode::BufferAtomicSwap64:
|
||||
case ValueOpcode::BufferAtomicIAdd32:
|
||||
case ValueOpcode::BufferAtomicISub32:
|
||||
case ValueOpcode::BufferAtomicSMin32:
|
||||
@@ -119,6 +121,7 @@ BufferAccess BufferAccessOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::BufferAtomicUMax32:
|
||||
case ValueOpcode::BufferAtomicAnd32:
|
||||
case ValueOpcode::BufferAtomicOr32:
|
||||
case ValueOpcode::BufferAtomicOr64:
|
||||
case ValueOpcode::BufferAtomicXor32:
|
||||
case ValueOpcode::BufferAtomicFMin32:
|
||||
case ValueOpcode::BufferAtomicFMax32: return BufferAccess::Atomic;
|
||||
@@ -128,6 +131,8 @@ BufferAccess BufferAccessOf(ValueOpcode opcode) {
|
||||
|
||||
uint32_t BufferComponentCount(ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case ValueOpcode::BufferAtomicSwap64:
|
||||
case ValueOpcode::BufferAtomicOr64:
|
||||
case ValueOpcode::LoadBufferU32x2:
|
||||
case ValueOpcode::StoreBufferU32x2: return 2u;
|
||||
case ValueOpcode::LoadBufferU32x3:
|
||||
|
||||
@@ -222,6 +222,8 @@ VALUE_OPCODE(StoreBufferU32x2, Void, BufferResource, U32, U32, U32, U32x2, U1)
|
||||
VALUE_OPCODE(StoreBufferU32x3, Void, BufferResource, U32, U32, U32, U32x3, U1)
|
||||
VALUE_OPCODE(StoreBufferU32x4, Void, BufferResource, U32, U32, U32, U32x4, U1)
|
||||
VALUE_OPCODE(BufferAtomicSwap32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicCmpSwap32, U32, BufferResource, U32, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicSwap64, U64, BufferResource, U32, U32, U32, U64, U1)
|
||||
VALUE_OPCODE(BufferAtomicIAdd32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicISub32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicSMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
@@ -230,6 +232,7 @@ VALUE_OPCODE(BufferAtomicSMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicUMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicAnd32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicOr32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicOr64, U64, BufferResource, U32, U32, U32, U64, U1)
|
||||
VALUE_OPCODE(BufferAtomicXor32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicFMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicFMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
|
||||
@@ -311,10 +311,6 @@ void CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex,
|
||||
}
|
||||
const auto output = DecodePositionExportComponent(
|
||||
vertex->pa_cl_vs_out_cntl, export_info.index, component);
|
||||
if (!output.valid) {
|
||||
return Fail(fmt::format("unsupported POS{} auxiliary/stereo export",
|
||||
export_info.index));
|
||||
}
|
||||
if (output.viewport) {
|
||||
return Fail("vertex viewport-index export is unsupported");
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ add_executable(launcher WIN32
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets)
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets kyty_git_version)
|
||||
if(WIN32)
|
||||
target_link_libraries(launcher setupapi)
|
||||
endif()
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>560</width>
|
||||
<height>450</height>
|
||||
<width>760</width>
|
||||
<height>620</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -17,363 +17,278 @@
|
||||
<locale language="English" country="UnitedStates"/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="horizontalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QFrame" name="configuaration_groupbox">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<layout class="QGridLayout" name="sectionsLayout" columnstretch="1,1">
|
||||
<property name="horizontalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_6">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QFrame" name="frame_4">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_vulkan_validation">
|
||||
<property name="toolTip">
|
||||
<string>Enable Vulkan validation layers</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Vulkan validation</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_shader_validation">
|
||||
<property name="toolTip">
|
||||
<string>Validate SPIR-V binary</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Shader validation</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cmd_dump">
|
||||
<property name="toolTip">
|
||||
<string>Dump command buffers</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Command buffer dump</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_renderdoc_capture">
|
||||
<property name="toolTip">
|
||||
<string>Enable RenderDoc capture</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RenderDoc capture</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_fullscreen">
|
||||
<property name="toolTip">
|
||||
<string>Run in borderless desktop fullscreen</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_red_zone_protection">
|
||||
<property name="toolTip">
|
||||
<string>Helps prevent crashes in some games on Windows</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Windows SysV red zone crash protection (Experimental)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_user_name">
|
||||
<property name="text">
|
||||
<string>User name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_user_name">
|
||||
<property name="toolTip">
|
||||
<string>Local user name exposed to games</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_user_id">
|
||||
<property name="text">
|
||||
<string>User ID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_user_id">
|
||||
<property name="toolTip">
|
||||
<string>Local user ID exposed to games</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2147483647</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Screen resolution:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
||||
<property name="toolTip">
|
||||
<string>Window resolution</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_present_mode">
|
||||
<property name="text">
|
||||
<string>Present mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="comboBox_present_mode">
|
||||
<property name="toolTip">
|
||||
<string>Vulkan swapchain presentation mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_vblank_frequency">
|
||||
<property name="text">
|
||||
<string>Vblank frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_vblank_frequency">
|
||||
<property name="toolTip">
|
||||
<string>Virtual display refresh rate used for frame pacing</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>360</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>60</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_console_language">
|
||||
<property name="text">
|
||||
<string>Console language:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="comboBox_console_language">
|
||||
<property name="toolTip">
|
||||
<string>Language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Shader optimization type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_optimization_type">
|
||||
<property name="toolTip">
|
||||
<string>Optimize shaders for code size or performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Shader log direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_log_direction">
|
||||
<property name="toolTip">
|
||||
<string>Dump shaders to file or console window. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Shader log folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
|
||||
<property name="toolTip">
|
||||
<string>Specify directory to dump shaders</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Command buffer dump folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
|
||||
<property name="toolTip">
|
||||
<string>Specify directory to dump command buffers</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Printf direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QComboBox" name="comboBox_printf_direction">
|
||||
<property name="toolTip">
|
||||
<string>Print logs to file or console window. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Printf output file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
|
||||
<property name="toolTip">
|
||||
<string>Specify file to dump logs</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label_27">
|
||||
<property name="text">
|
||||
<string>Profiler direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QComboBox" name="comboBox_profiler_direction">
|
||||
<property name="toolTip">
|
||||
<string>Enable or disable profiler. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="ok_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
<property name="verticalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="user_profile_group">
|
||||
<property name="title">
|
||||
<string>User profile</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="userProfileLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_user_name">
|
||||
<property name="text"><string>User name:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_user_name">
|
||||
<property name="toolTip"><string>Local user name exposed to games</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_user_id">
|
||||
<property name="text"><string>User ID:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_user_id">
|
||||
<property name="toolTip"><string>Local user ID exposed to games</string></property>
|
||||
<property name="maximum"><number>2147483647</number></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_console_language">
|
||||
<property name="text"><string>Console language:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboBox_console_language">
|
||||
<property name="toolTip"><string>Language</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="2">
|
||||
<widget class="QGroupBox" name="graphics_group">
|
||||
<property name="title">
|
||||
<string>Graphics && display</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="graphicsLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_screen_resolution">
|
||||
<property name="text"><string>Screen resolution:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
||||
<property name="toolTip"><string>Window resolution</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_present_mode">
|
||||
<property name="text"><string>Present mode:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboBox_present_mode">
|
||||
<property name="toolTip"><string>Vulkan swapchain presentation mode</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_vblank_frequency">
|
||||
<property name="text"><string>Vblank frequency:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_vblank_frequency">
|
||||
<property name="toolTip"><string>Virtual display refresh rate used for frame pacing</string></property>
|
||||
<property name="minimum"><number>30</number></property>
|
||||
<property name="maximum"><number>360</number></property>
|
||||
<property name="value"><number>60</number></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_shader_optimization">
|
||||
<property name="text"><string>Shader optimization:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_optimization_type">
|
||||
<property name="toolTip"><string>Optimize shaders for code size or performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_fullscreen">
|
||||
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
|
||||
<property name="text"><string>Fullscreen</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="debug_group">
|
||||
<property name="title">
|
||||
<string>Debugging && logs</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="debugLayout" columnstretch="0,1,0,1">
|
||||
<property name="horizontalSpacing"><number>8</number></property>
|
||||
<property name="verticalSpacing"><number>6</number></property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_vulkan_validation">
|
||||
<property name="toolTip"><string>Enable Vulkan validation layers</string></property>
|
||||
<property name="text"><string>Vulkan validation</string></property>
|
||||
<property name="checked"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_shader_validation">
|
||||
<property name="toolTip"><string>Validate SPIR-V binary</string></property>
|
||||
<property name="text"><string>Shader validation</string></property>
|
||||
<property name="checked"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_renderdoc_capture">
|
||||
<property name="toolTip"><string>Enable RenderDoc capture</string></property>
|
||||
<property name="text"><string>RenderDoc capture</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_cmd_dump">
|
||||
<property name="toolTip"><string>Dump command buffers</string></property>
|
||||
<property name="text"><string>Command buffer dump</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_shader_log_direction">
|
||||
<property name="text"><string>Shader logging:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_log_direction">
|
||||
<property name="toolTip"><string>Dump shaders to file or console window. If enabled may decrease emulator performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="label_shader_log_folder">
|
||||
<property name="text"><string>Shader log folder:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
|
||||
<property name="toolTip"><string>Specify directory to dump shaders</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_printf_direction">
|
||||
<property name="text"><string>Printf output:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_printf_direction">
|
||||
<property name="toolTip"><string>Print logs to file or console window. If enabled may decrease emulator performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="label_printf_file">
|
||||
<property name="text"><string>Printf output file:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
|
||||
<property name="toolTip"><string>Specify file to dump logs</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_profiler_direction">
|
||||
<property name="text"><string>Profiler:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="comboBox_profiler_direction">
|
||||
<property name="toolTip"><string>Enable or disable profiler. If enabled may decrease emulator performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QLabel" name="label_cmd_dump_folder">
|
||||
<property name="text"><string>Command buffer folder:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
|
||||
<property name="toolTip"><string>Specify directory to dump command buffers</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="compatibility_group">
|
||||
<property name="title">
|
||||
<string>Compatibility</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="compatibilityLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_red_zone_protection">
|
||||
<property name="toolTip"><string>Helps prevent crashes in some games on Windows</string></property>
|
||||
<property name="text"><string>Windows SysV red zone crash protection (Experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="compatibilitySpacer">
|
||||
<property name="orientation"><enum>Qt::Horizontal</enum></property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size><width>40</width><height>20</height></size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="buttonLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="clear_button">
|
||||
<property name="minimumSize"><size><width>80</width><height>25</height></size></property>
|
||||
<property name="text"><string>Reset</string></property>
|
||||
<property name="toolTip"><string>Restore default settings</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="buttonSpacer">
|
||||
<property name="orientation"><enum>Qt::Horizontal</enum></property>
|
||||
<property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="minimumSize"><size><width>80</width><height>25</height></size></property>
|
||||
<property name="text"><string>Cancel</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QPushButton" name="ok_button">
|
||||
<property name="minimumSize"><size><width>80</width><height>25</height></size></property>
|
||||
<property name="text"><string>Save</string></property>
|
||||
<property name="default"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clear_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -392,16 +307,6 @@
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ConfigurationEditDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>496</x>
|
||||
<y>65</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>280</x>
|
||||
<y>180</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -72,9 +72,43 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Version">
|
||||
<layout class="QHBoxLayout" name="versionLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Version">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="check_updates_link">
|
||||
<property name="text">
|
||||
<string><a href="check">Check for updates</a></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="versionSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="check_updates_on_startup">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string>Check for updates on startup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M8 4h11l5 5v7M24 9h-5V4M16 27H8a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"/>
|
||||
<path d="m15 24 1-4 8-8 4 4-8 8-4 1Z"/>
|
||||
<path d="m22 14 4 4" opacity=".8"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M5 8h3M14 8h13M5 16h13M24 16h3M5 24h6M17 24h10"/>
|
||||
<circle cx="11" cy="8" r="3"/>
|
||||
<circle cx="21" cy="16" r="3"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
@@ -1,9 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M10 10h12c3.6 0 5.8 2.3 6.6 6.8l.9 5.1c.4 2.4-.8 4.1-2.8 4.1-1.4 0-2.5-.7-3.6-2.3L21.3 21H10.7l-1.8 2.7C7.8 25.3 6.7 26 5.3 26c-2 0-3.2-1.7-2.8-4.1l.9-5.1C4.2 12.3 6.4 10 10 10Z"/>
|
||||
<path d="M8 16h6M11 13v6"/>
|
||||
<circle cx="22" cy="15" r="1" fill="#e8f1ff" stroke="none"/>
|
||||
<circle cx="25" cy="18" r="1" fill="#e8f1ff" stroke="none"/>
|
||||
<circle cx="22" cy="15" r="1" fill="#000000" stroke="none"/>
|
||||
<circle cx="25" cy="18" r="1" fill="#000000" stroke="none"/>
|
||||
<path d="M13 7h6M16 4v6" opacity=".8"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 574 B After Width: | Height: | Size: 574 B |
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M6 9h20M12 9V5h8v4M9 9l1 18h12l1-18"/>
|
||||
<path d="M14 14v8M18 14v8" opacity=".8"/>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 272 B |
@@ -10,6 +10,7 @@ class QByteArray;
|
||||
class QGroupBox;
|
||||
class QListWidget;
|
||||
class QMoveEvent;
|
||||
class QResizeEvent;
|
||||
class QToolButton;
|
||||
class QSettings;
|
||||
class QWidget;
|
||||
@@ -48,6 +49,7 @@ protected:
|
||||
void AddGameDirectoryItem(const QString& dir);
|
||||
|
||||
void moveEvent(QMoveEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
static QByteArray g_last_geometry;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include <memory>
|
||||
@@ -14,7 +15,7 @@ class QTreeWidget;
|
||||
class QWidget;
|
||||
|
||||
class Configuration;
|
||||
class ConfigurationItem: public QTreeWidgetItem {
|
||||
class ConfigurationItem: public QObject, public QTreeWidgetItem {
|
||||
public:
|
||||
explicit ConfigurationItem(std::unique_ptr<Configuration> info, QTreeWidget* parent);
|
||||
~ConfigurationItem() override;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
class ConfigurationItem;
|
||||
class CompatibilityDatabase;
|
||||
class QEvent;
|
||||
class QTreeWidgetItem;
|
||||
class QPoint;
|
||||
class MainDialog;
|
||||
@@ -52,6 +53,9 @@ signals:
|
||||
void Run();
|
||||
void Select();
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
public slots:
|
||||
void WriteSettings();
|
||||
void ReadSettings();
|
||||
@@ -74,6 +78,7 @@ private:
|
||||
void ClearCustomSettings(ConfigurationItem* item);
|
||||
void SelectItem(QTreeWidgetItem* witem);
|
||||
void ApplyCompatibility();
|
||||
void UpdateToolbarIcons();
|
||||
[[nodiscard]] bool HasValidGameDirectory() const;
|
||||
|
||||
ConfigurationItem* m_selected_item = nullptr;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef GAME_LIST_TREE_WIDGET_H
|
||||
#define GAME_LIST_TREE_WIDGET_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
@@ -44,7 +43,9 @@ protected:
|
||||
painter.fillRect(event->rect(), palette().brush(QPalette::Base));
|
||||
} else {
|
||||
painter.drawPixmap(m_scaled_pos, m_scaled);
|
||||
painter.fillRect(event->rect(), QColor(0, 0, 0, 115));
|
||||
auto overlay = palette().color(QPalette::Base);
|
||||
overlay.setAlpha(185);
|
||||
painter.fillRect(event->rect(), overlay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef LAUNCHER_THEME_H
|
||||
#define LAUNCHER_THEME_H
|
||||
|
||||
class QApplication;
|
||||
|
||||
namespace LauncherTheme {
|
||||
|
||||
void Initialize(QApplication& application);
|
||||
|
||||
} // namespace LauncherTheme
|
||||
|
||||
#endif // LAUNCHER_THEME_H
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef UPDATE_CHECKER_H
|
||||
#define UPDATE_CHECKER_H
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
class QByteArray;
|
||||
class QWidget;
|
||||
|
||||
class UpdateChecker final: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UpdateChecker(QWidget* parent);
|
||||
|
||||
[[nodiscard]] static bool IsSupported();
|
||||
void Check(bool manual);
|
||||
|
||||
signals:
|
||||
void CheckingChanged(bool checking);
|
||||
|
||||
private:
|
||||
struct UpdateInfo;
|
||||
|
||||
static UpdateInfo ParseUpdateInfo(const QByteArray& data);
|
||||
void FetchUpdateInfo(const char* url, bool fallback, bool manual);
|
||||
void ShowUpdateResult(const UpdateInfo& info, bool manual);
|
||||
|
||||
QWidget* m_parent = nullptr;
|
||||
QNetworkAccessManager m_network;
|
||||
bool m_checking_updates = false;
|
||||
};
|
||||
|
||||
#endif // UPDATE_CHECKER_H
|
||||
@@ -5,4 +5,7 @@
|
||||
<file alias="input-mapping.svg">icons/input-mapping.svg</file>
|
||||
<file alias="remove-configuration.svg">icons/remove-configuration.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/styles">
|
||||
<file alias="launcher.qss">styles/launcher.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <QSettings>
|
||||
#include <QSpinBox>
|
||||
#include <QStyle>
|
||||
@@ -115,7 +116,10 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
||||
m_ui->lineEdit_printf_file->setEnabled(log == Configuration::LogDirection::File);
|
||||
});
|
||||
|
||||
layout()->setSizeConstraint(QLayout::SetFixedSize);
|
||||
// Keep the controls at a usable minimum while allowing the settings window
|
||||
// and its expanding fields to use any additional space the user gives them.
|
||||
layout()->setSizeConstraint(QLayout::SetMinimumSize);
|
||||
setSizeGripEnabled(true);
|
||||
|
||||
restoreGeometry(g_last_geometry);
|
||||
|
||||
@@ -248,7 +252,8 @@ void ConfigurationEditDialog::SetGameDirectories(const QStringList& dirs) {
|
||||
|
||||
m_game_dirs_group->setVisible(true);
|
||||
update_game_directory_buttons();
|
||||
adjustSize();
|
||||
layout()->activate();
|
||||
resize(size().expandedTo(minimumSizeHint()));
|
||||
}
|
||||
|
||||
QStringList ConfigurationEditDialog::GetGameDirectories() const {
|
||||
@@ -288,6 +293,11 @@ void ConfigurationEditDialog::moveEvent(QMoveEvent* event) {
|
||||
g_last_geometry = saveGeometry();
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::resizeEvent(QResizeEvent* event) {
|
||||
QDialog::resizeEvent(event);
|
||||
g_last_geometry = saveGeometry();
|
||||
}
|
||||
|
||||
static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
||||
info.user_name = ui.lineEdit_user_name->text().trimmed();
|
||||
info.user_id = ui.spinBox_user_id->value();
|
||||
|
||||
@@ -127,10 +127,12 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
|
||||
layout->setSpacing(6);
|
||||
|
||||
m_status_indicator = new QLabel(m_status_widget);
|
||||
m_status_indicator->setObjectName(QStringLiteral("game_status_indicator"));
|
||||
m_status_indicator->setFixedSize(QSize(12, 12));
|
||||
layout->addWidget(m_status_indicator);
|
||||
|
||||
m_status_combo = new NoWheelComboBox(m_status_widget);
|
||||
m_status_combo->setObjectName(QStringLiteral("game_status_editor"));
|
||||
MakeTransparent(m_status_combo);
|
||||
AddStatus(m_status_combo, Configuration::GameStatus::Unknown);
|
||||
AddStatus(m_status_combo, Configuration::GameStatus::MainMenu);
|
||||
@@ -139,26 +141,16 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
|
||||
AddStatus(m_status_combo, Configuration::GameStatus::DoesntBoot);
|
||||
m_status_combo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
m_status_combo->setFixedWidth(125);
|
||||
m_status_combo->setStyleSheet(QStringLiteral(
|
||||
"QComboBox { background: transparent; border: 0; color: #eef4ff; padding-left: 2px; }"
|
||||
"QComboBox:focus { background: rgba(255,255,255,35); border-radius: 3px; }"
|
||||
"QComboBox::drop-down { border: 0; }"
|
||||
"QComboBox QAbstractItemView { background: #181c24; color: #eef4ff; "
|
||||
"selection-background-color: #267bd8; }"));
|
||||
layout->addWidget(m_status_combo);
|
||||
layout->addStretch(1);
|
||||
|
||||
parent->setItemWidget(this, StatusColumn, m_status_widget);
|
||||
|
||||
m_comment_edit = new QLineEdit(parent);
|
||||
m_comment_edit->setObjectName(QStringLiteral("game_comment_editor"));
|
||||
MakeTransparent(m_comment_edit);
|
||||
m_comment_edit->setClearButtonEnabled(true);
|
||||
m_comment_edit->setFrame(false);
|
||||
m_comment_edit->setStyleSheet(QStringLiteral(
|
||||
"QLineEdit { background: transparent; border: 0; color: #eef4ff; padding-left: 2px; "
|
||||
"selection-background-color: #267bd8; }"
|
||||
"QLineEdit:focus { background: rgba(255,255,255,35); "
|
||||
"border: 1px solid rgba(255,255,255,90); border-radius: 3px; }"));
|
||||
parent->setItemWidget(this, CommentsColumn, m_comment_edit);
|
||||
|
||||
Update();
|
||||
@@ -260,8 +252,6 @@ void ConfigurationItem::UpdateIcon() {
|
||||
|
||||
void ConfigurationItem::UpdateStatusIndicator() {
|
||||
m_status_indicator->setStyleSheet(
|
||||
QStringLiteral(
|
||||
"background-color: %1; border: 1px solid rgba(255, 255, 255, 90); border-radius: 6px;")
|
||||
.arg(GetStatusColor(m_info->game_status)));
|
||||
QStringLiteral("background-color: %1;").arg(GetStatusColor(m_info->game_status)));
|
||||
m_status_indicator->setToolTip(GetStatusText(m_info->game_status));
|
||||
}
|
||||
@@ -30,6 +30,8 @@
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
@@ -131,7 +133,7 @@ static QString GetPic0Path(const Configuration& info) {
|
||||
return QFileInfo::exists(path) ? QFileInfo(path).absoluteFilePath() : QString();
|
||||
}
|
||||
|
||||
static void ApplyGameListStyle(Ui::ConfigurationListWidget* ui) {
|
||||
static void ConfigureGameList(Ui::ConfigurationListWidget* ui) {
|
||||
ui->cfgs_list->setAlternatingRowColors(false);
|
||||
ui->cfgs_list->setAllColumnsShowFocus(true);
|
||||
ui->cfgs_list->setIndentation(0);
|
||||
@@ -142,35 +144,6 @@ static void ApplyGameListStyle(Ui::ConfigurationListWidget* ui) {
|
||||
ui->cfgs_list->header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
ui->cfgs_list->header()->setHighlightSections(false);
|
||||
ui->cfgs_list->header()->setStretchLastSection(true);
|
||||
|
||||
ui->cfgs_list->setStyleSheet(QStringLiteral(
|
||||
"QTreeWidget { background: transparent; border: 1px solid rgba(255,255,255,42); "
|
||||
"color: #eef4ff; outline: 0; }"
|
||||
"QTreeWidget::item { background: rgba(8,10,16,92); "
|
||||
"border-bottom: 1px solid rgba(255,255,255,24); padding: 4px; }"
|
||||
"QTreeWidget::item:selected { background: rgba(32,118,210,150); color: #ffffff; }"
|
||||
"QHeaderView::section { background: rgba(22,25,31,235); color: #dce6f6; border: 0; "
|
||||
"border-right: 1px solid rgba(255,255,255,35); border-bottom: 1px solid "
|
||||
"rgba(255,255,255,45); "
|
||||
"padding: 5px 6px; }"
|
||||
"QScrollBar:vertical { background: rgba(12,14,18,150); width: 14px; margin: 0; }"
|
||||
"QScrollBar::handle:vertical { background: rgba(220,230,245,120); min-height: 36px; }"
|
||||
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }"));
|
||||
|
||||
ui->search_line_edit->setStyleSheet(QStringLiteral(
|
||||
"QLineEdit { background: rgba(12,14,18,210); border: 1px solid rgba(255,255,255,42); "
|
||||
"border-radius: 4px; color: #eef4ff; padding: 5px 8px; selection-background-color: "
|
||||
"#267bd8; }"
|
||||
"QLineEdit:focus { border-color: rgba(80,160,255,180); }"));
|
||||
ui->global_settings_button->setStyleSheet(QStringLiteral(
|
||||
"QToolButton { background: rgba(255,255,255,18); border: 1px solid transparent; "
|
||||
"border-radius: 5px; padding: 3px; }"
|
||||
"QToolButton:hover { background: rgba(255,255,255,45); border-color: rgba(255,255,255,70); "
|
||||
"}"
|
||||
"QToolButton:disabled { background: transparent; }"));
|
||||
ui->edit_button->setStyleSheet(ui->global_settings_button->styleSheet());
|
||||
ui->delete_button->setStyleSheet(ui->global_settings_button->styleSheet());
|
||||
ui->input_mapping_button->setStyleSheet(ui->global_settings_button->styleSheet());
|
||||
}
|
||||
|
||||
static void AddSaveDataDir(QStringList* dirs, QSet<QString>* seen, const QString& root,
|
||||
@@ -226,14 +199,11 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
|
||||
m_compatibility = new CompatibilityDatabase(
|
||||
QCoreApplication::arguments().contains(QStringLiteral("--local")), this);
|
||||
m_ui->setupUi(this);
|
||||
ApplyGameListStyle(m_ui);
|
||||
ConfigureGameList(m_ui);
|
||||
|
||||
m_ui->global_settings_button->setIcon(QIcon(QStringLiteral(":/icons/global-settings.svg")));
|
||||
UpdateToolbarIcons();
|
||||
m_ui->global_settings_button->setToolTip(tr("Edit global settings and game folders"));
|
||||
m_ui->input_mapping_button->setIcon(QIcon(QStringLiteral(":/icons/input-mapping.svg")));
|
||||
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
|
||||
m_ui->edit_button->setIcon(QIcon(QStringLiteral(":/icons/edit-configuration.svg")));
|
||||
m_ui->delete_button->setIcon(QIcon(QStringLiteral(":/icons/remove-configuration.svg")));
|
||||
|
||||
m_ui->delete_button->setEnabled(false);
|
||||
m_ui->edit_button->setEnabled(false);
|
||||
@@ -287,6 +257,30 @@ ConfigurationListWidget::~ConfigurationListWidget() {
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::changeEvent(QEvent* event) {
|
||||
QWidget::changeEvent(event);
|
||||
if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::PaletteChange) {
|
||||
UpdateToolbarIcons();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::UpdateToolbarIcons() {
|
||||
const auto color = palette().color(QPalette::Window).lightness() < 128 ? QColor(Qt::white)
|
||||
: QColor(Qt::black);
|
||||
const auto set_icon = [&color](QToolButton* button, const QString& resource) {
|
||||
auto pixmap = QIcon(resource).pixmap(button->iconSize(), button->devicePixelRatioF());
|
||||
QPainter painter(&pixmap);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(pixmap.rect(), color);
|
||||
button->setIcon(QIcon(pixmap));
|
||||
};
|
||||
|
||||
set_icon(m_ui->global_settings_button, QStringLiteral(":/icons/global-settings.svg"));
|
||||
set_icon(m_ui->input_mapping_button, QStringLiteral(":/icons/input-mapping.svg"));
|
||||
set_icon(m_ui->edit_button, QStringLiteral(":/icons/edit-configuration.svg"));
|
||||
set_icon(m_ui->delete_button, QStringLiteral(":/icons/remove-configuration.svg"));
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::WriteSettings() {
|
||||
QFile file = QFile(QDir(".").absoluteFilePath(CONF_FILE_NAME));
|
||||
std::unique_ptr<QSettings> s;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "launcherTheme.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
namespace {
|
||||
|
||||
class PaletteChangeFilter final: public QObject {
|
||||
public:
|
||||
explicit PaletteChangeFilter(QApplication& application)
|
||||
: QObject(&application), m_application(application) {}
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* watched, QEvent* event) override {
|
||||
if (event->type() == QEvent::ApplicationPaletteChange && !m_refresh_pending) {
|
||||
m_refresh_pending = true;
|
||||
QTimer::singleShot(0, &m_application, [this] {
|
||||
m_application.setStyleSheet(m_application.styleSheet());
|
||||
m_refresh_pending = false;
|
||||
});
|
||||
}
|
||||
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
private:
|
||||
QApplication& m_application;
|
||||
bool m_refresh_pending = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace LauncherTheme {
|
||||
|
||||
void Initialize(QApplication& application) {
|
||||
QFile style_sheet_file(QStringLiteral(":/styles/launcher.qss"));
|
||||
if (style_sheet_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
application.setStyleSheet(QString::fromUtf8(style_sheet_file.readAll()));
|
||||
application.installEventFilter(new PaletteChangeFilter(application));
|
||||
}
|
||||
}
|
||||
} // namespace LauncherTheme
|
||||
@@ -1,18 +1,15 @@
|
||||
#include "mainDialog.h"
|
||||
#include "launcherTheme.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QArgument>
|
||||
#include <QObject>
|
||||
#include <QStyleFactory>
|
||||
|
||||
class QStyle;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
MainDialog w;
|
||||
LauncherTheme::Initialize(a);
|
||||
|
||||
QStyle* s = QStyleFactory::create("fusion");
|
||||
QApplication::setStyle(s);
|
||||
MainDialog w;
|
||||
|
||||
QObject::connect(&a, &QApplication::aboutToQuit, &w, &MainDialog::Quit);
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
#include "configurationItem.h"
|
||||
#include "configurationListWidget.h"
|
||||
#include "patchesDialog.h"
|
||||
#include "updateChecker.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QByteArray>
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
@@ -15,6 +17,7 @@
|
||||
#include <QIODevice>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPointer>
|
||||
#include <QProcess>
|
||||
#include <QRadioButton>
|
||||
#include <QRegularExpression>
|
||||
@@ -53,6 +56,7 @@ constexpr DWORD CMD_Y_CHARS = 1000;
|
||||
#endif
|
||||
constexpr char SETTINGS_MAIN_DIALOG[] = "MainDialog";
|
||||
constexpr char SETTINGS_MAIN_LAST_GEOMETRY[] = "geometry";
|
||||
constexpr char SETTINGS_CHECK_UPDATES[] = "check_updates_on_startup";
|
||||
|
||||
class DetachableProcess: public QProcess {
|
||||
Q_OBJECT;
|
||||
@@ -88,17 +92,20 @@ public:
|
||||
|
||||
private:
|
||||
static QByteArray g_last_geometry;
|
||||
static bool g_check_updates_on_startup;
|
||||
|
||||
Ui::MainDialog* m_ui = {nullptr};
|
||||
MainDialog* m_main_dialog = nullptr;
|
||||
Ui::MainDialog* m_ui = {nullptr};
|
||||
MainDialog* m_main_dialog = nullptr;
|
||||
UpdateChecker* m_update_checker = nullptr;
|
||||
QString m_interpreter;
|
||||
|
||||
/*DetachableProcess*/ QProcess m_process;
|
||||
|
||||
ConfigurationItem* m_running_item = nullptr;
|
||||
QPointer<ConfigurationItem> m_running_item;
|
||||
};
|
||||
|
||||
QByteArray MainDialogPrivate::g_last_geometry;
|
||||
bool MainDialogPrivate::g_check_updates_on_startup = true;
|
||||
|
||||
MainDialog::MainDialog(QWidget* parent): QDialog(parent), m_p(new MainDialogPrivate(this)) {
|
||||
m_p->Setup(this);
|
||||
@@ -113,7 +120,11 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
m_ui->setupUi(main_dialog);
|
||||
|
||||
m_main_dialog = main_dialog;
|
||||
m_update_checker = new UpdateChecker(main_dialog);
|
||||
m_ui->widget->SetMainDialog(main_dialog);
|
||||
m_ui->check_updates_on_startup->setChecked(g_check_updates_on_startup);
|
||||
m_ui->check_updates_link->setVisible(UpdateChecker::IsSupported());
|
||||
m_ui->check_updates_on_startup->setVisible(UpdateChecker::IsSupported());
|
||||
|
||||
main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/);
|
||||
|
||||
@@ -121,6 +132,14 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
Qt::QueuedConnection);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Run, this, &MainDialogPrivate::Run);
|
||||
connect(m_ui->check_updates_link, &QLabel::linkActivated, this,
|
||||
[this](const QString&) { m_update_checker->Check(true); });
|
||||
connect(m_update_checker, &UpdateChecker::CheckingChanged, m_ui->check_updates_link,
|
||||
&QLabel::setDisabled);
|
||||
connect(m_ui->check_updates_on_startup, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
g_check_updates_on_startup = checked;
|
||||
m_ui->widget->WriteSettings();
|
||||
});
|
||||
connect(main_dialog, &MainDialog::Resize, [this]() {
|
||||
g_last_geometry = m_main_dialog->saveGeometry();
|
||||
m_ui->widget->WriteSettings();
|
||||
@@ -188,6 +207,9 @@ void MainDialogPrivate::FindInterpreter() {
|
||||
m_ui->label_settings_file->setText(tr("Settings file: ") + m_ui->widget->GetSettingsFile());
|
||||
|
||||
Update();
|
||||
if (m_ui->check_updates_on_startup->isChecked()) {
|
||||
m_update_checker->Check(false);
|
||||
}
|
||||
}
|
||||
|
||||
static QString BoolArg(bool value) {
|
||||
@@ -447,6 +469,7 @@ void MainDialogPrivate::WriteSettings(QSettings& s) {
|
||||
if (!g_last_geometry.isEmpty()) {
|
||||
s.setValue(SETTINGS_MAIN_LAST_GEOMETRY, g_last_geometry);
|
||||
}
|
||||
s.setValue(SETTINGS_CHECK_UPDATES, g_check_updates_on_startup);
|
||||
|
||||
s.endGroup();
|
||||
}
|
||||
@@ -455,6 +478,7 @@ void MainDialogPrivate::ReadSettings(QSettings& s) {
|
||||
s.beginGroup(SETTINGS_MAIN_DIALOG);
|
||||
|
||||
g_last_geometry = s.value(SETTINGS_MAIN_LAST_GEOMETRY, g_last_geometry).toByteArray();
|
||||
g_check_updates_on_startup = s.value(SETTINGS_CHECK_UPDATES, true).toBool();
|
||||
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "updateChecker.h"
|
||||
|
||||
#include "kytyGitVersion.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char DEFAULT_FEED_URL[] = "https://kytyps5.github.io/data/updates.json";
|
||||
constexpr char FALLBACK_FEED_URL[] =
|
||||
"https://api.github.com/repos/KytyPS5/KytyPS5/releases/latest";
|
||||
|
||||
} // namespace
|
||||
|
||||
struct UpdateChecker::UpdateInfo {
|
||||
QString tag;
|
||||
QUrl page_url;
|
||||
QString error;
|
||||
};
|
||||
|
||||
UpdateChecker::UpdateChecker(QWidget* parent): QObject(parent), m_parent(parent), m_network(this) {}
|
||||
|
||||
bool UpdateChecker::IsSupported() {
|
||||
#if defined(KYTY_OFFICIAL_BUILD) && defined(NDEBUG)
|
||||
return !QString::fromLatin1(KYTY_GIT_HASH).endsWith(QStringLiteral("-dirty"));
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdateChecker::UpdateInfo UpdateChecker::ParseUpdateInfo(const QByteArray& data) {
|
||||
const auto document = QJsonDocument::fromJson(data);
|
||||
if (!document.isObject()) {
|
||||
return {{}, {}, tr("Invalid update feed")};
|
||||
}
|
||||
|
||||
const auto root = document.object();
|
||||
UpdateInfo info {root.value(QStringLiteral("tag")).toString(),
|
||||
QUrl(root.value(QStringLiteral("html_url")).toString()), {}};
|
||||
if (info.tag.isEmpty()) {
|
||||
info.tag = root.value(QStringLiteral("tag_name")).toString();
|
||||
}
|
||||
if (info.tag.isEmpty() || !info.page_url.isValid() ||
|
||||
info.page_url.scheme() != QStringLiteral("https")) {
|
||||
info.error = tr("Incomplete update feed");
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
void UpdateChecker::Check(bool manual) {
|
||||
if (!IsSupported() || m_checking_updates) {
|
||||
return;
|
||||
}
|
||||
m_checking_updates = true;
|
||||
emit CheckingChanged(true);
|
||||
FetchUpdateInfo(DEFAULT_FEED_URL, false, manual);
|
||||
}
|
||||
|
||||
void UpdateChecker::FetchUpdateInfo(const char* url, bool fallback, bool manual) {
|
||||
QNetworkRequest request(QUrl(QString::fromLatin1(url)));
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
request.setRawHeader("User-Agent", "Kyty-Launcher");
|
||||
request.setRawHeader("Accept", "application/vnd.github+json");
|
||||
request.setTransferTimeout(15000);
|
||||
|
||||
auto* reply = m_network.get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, fallback, manual]() {
|
||||
UpdateInfo info;
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
info = ParseUpdateInfo(reply->readAll());
|
||||
} else {
|
||||
info.error = reply->errorString();
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
if (!info.error.isEmpty() && !fallback) {
|
||||
FetchUpdateInfo(FALLBACK_FEED_URL, true, manual);
|
||||
return;
|
||||
}
|
||||
|
||||
m_checking_updates = false;
|
||||
emit CheckingChanged(false);
|
||||
ShowUpdateResult(info, manual);
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateChecker::ShowUpdateResult(const UpdateInfo& info, bool manual) {
|
||||
if (!info.error.isEmpty()) {
|
||||
qWarning() << "Could not check for updates:" << info.error;
|
||||
if (manual) {
|
||||
QMessageBox::warning(m_parent, tr("Update Check"),
|
||||
tr("Could not check for updates:\n%1").arg(info.error));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const bool current = info.tag == QString::fromLatin1(KYTY_RELEASE_TAG);
|
||||
if (current) {
|
||||
if (manual) {
|
||||
QMessageBox::information(m_parent, tr("Update Check"),
|
||||
tr("You are using the latest version (%1).").arg(info.tag));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto message =
|
||||
tr("An update is available.\n\nCurrent: %1\nLatest: %2\n\n"
|
||||
"Open the release page?")
|
||||
.arg(QString::fromLatin1(KYTY_RELEASE_TAG), info.tag);
|
||||
if (QMessageBox::question(m_parent, tr("KytyPS5 Update"), message,
|
||||
QMessageBox::Open | QMessageBox::Cancel,
|
||||
QMessageBox::Open) == QMessageBox::Open) {
|
||||
QDesktopServices::openUrl(info.page_url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
GameListTreeWidget#cfgs_list {
|
||||
background: transparent;
|
||||
border: 1px solid palette(mid);
|
||||
color: palette(text);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
GameListTreeWidget#cfgs_list::item {
|
||||
background: transparent;
|
||||
border-bottom: 1px solid palette(mid);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
GameListTreeWidget#cfgs_list::item:selected {
|
||||
background: palette(highlight);
|
||||
color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
GameListTreeWidget#cfgs_list QHeaderView::section {
|
||||
background: palette(button);
|
||||
color: palette(button-text);
|
||||
border: 0;
|
||||
border-right: 1px solid palette(mid);
|
||||
border-bottom: 1px solid palette(mid);
|
||||
padding: 5px 6px;
|
||||
}
|
||||
|
||||
QLineEdit#search_line_edit {
|
||||
background: palette(base);
|
||||
border: 1px solid palette(mid);
|
||||
border-radius: 4px;
|
||||
color: palette(text);
|
||||
padding: 5px 8px;
|
||||
selection-background-color: palette(highlight);
|
||||
selection-color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
QLineEdit#search_line_edit:focus {
|
||||
border-color: palette(highlight);
|
||||
}
|
||||
|
||||
QToolButton#global_settings_button,
|
||||
QToolButton#input_mapping_button,
|
||||
QToolButton#edit_button,
|
||||
QToolButton#delete_button {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
QToolButton#global_settings_button:hover,
|
||||
QToolButton#input_mapping_button:hover,
|
||||
QToolButton#edit_button:hover,
|
||||
QToolButton#delete_button:hover {
|
||||
background: palette(button);
|
||||
border-color: palette(mid);
|
||||
}
|
||||
|
||||
QToolButton#global_settings_button:pressed,
|
||||
QToolButton#input_mapping_button:pressed,
|
||||
QToolButton#edit_button:pressed,
|
||||
QToolButton#delete_button:pressed {
|
||||
background: palette(midlight);
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: palette(text);
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor:focus {
|
||||
background: palette(button);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor::drop-down {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor QAbstractItemView {
|
||||
background: palette(base);
|
||||
color: palette(text);
|
||||
selection-background-color: palette(highlight);
|
||||
selection-color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
QLineEdit#game_comment_editor {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: palette(text);
|
||||
padding-left: 2px;
|
||||
selection-background-color: palette(highlight);
|
||||
selection-color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
QLineEdit#game_comment_editor:focus {
|
||||
background: palette(base);
|
||||
border: 1px solid palette(mid);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QLabel#game_status_indicator {
|
||||
border: 1px solid palette(base);
|
||||
border-radius: 6px;
|
||||
}
|
||||
@@ -23,8 +23,8 @@ LIB_NAME("Pad", "Pad");
|
||||
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
|
||||
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
|
||||
|
||||
// SDL limits rumble commands to 0xffff ms; zero strengths stop immediately.
|
||||
constexpr uint32_t RUMBLE_DURATION_MS = 0xffff;
|
||||
constexpr uint32_t RELEASE_FLUSH_MS = 50;
|
||||
|
||||
struct PadControllerInformation {
|
||||
float touch_pixel_density;
|
||||
@@ -102,6 +102,7 @@ public:
|
||||
void RightStick(int id, int x, int y);
|
||||
void TouchPad(int id, int finger, bool down, float x, float y);
|
||||
void ResetInputState();
|
||||
void ReleaseHostPads();
|
||||
void GetConnectionInfo(bool* flag, int* count);
|
||||
void SetVibration(uint8_t large_motor, uint8_t small_motor);
|
||||
void SetLightBar(uint8_t r, uint8_t g, uint8_t b);
|
||||
@@ -252,10 +253,17 @@ void Initialize() {
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
EmergencyShutdown();
|
||||
delete g_controller;
|
||||
g_controller = nullptr;
|
||||
}
|
||||
|
||||
void EmergencyShutdown() {
|
||||
if (g_controller != nullptr) {
|
||||
g_controller->ReleaseHostPads();
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::Connect(int id) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
@@ -404,6 +412,39 @@ void GameController::ResetInputState() {
|
||||
AddState();
|
||||
}
|
||||
|
||||
void GameController::ReleaseHostPads() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
std::vector<SDL_GameController*> pads;
|
||||
for (const auto id: m_connected_ids) {
|
||||
if (id == HOST_INPUT_CONTROLLER_ID) {
|
||||
continue;
|
||||
}
|
||||
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id));
|
||||
pad != nullptr) {
|
||||
if (SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) {
|
||||
DualSenseEffects effect {};
|
||||
effect.enable_bits = 0x0c;
|
||||
effect.right_trigger[0] = 0x05;
|
||||
effect.left_trigger[0] = 0x05;
|
||||
(void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect));
|
||||
}
|
||||
(void)SDL_GameControllerRumble(pad, 0, 0, 0);
|
||||
(void)SDL_GameControllerSetLED(pad, 0, 0, 0);
|
||||
pads.push_back(pad);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pads.empty()) {
|
||||
SDL_Delay(RELEASE_FLUSH_MS);
|
||||
for (auto* pad: pads) {
|
||||
SDL_GameControllerClose(pad);
|
||||
}
|
||||
}
|
||||
|
||||
m_connected_ids.clear();
|
||||
}
|
||||
|
||||
void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ namespace Libs::Controller {
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
void EmergencyShutdown();
|
||||
|
||||
struct Lifecycle {
|
||||
static constexpr const char* name = "Controller";
|
||||
static constexpr auto initialize = Libs::Controller::Initialize;
|
||||
static constexpr auto shutdown = Libs::Controller::Shutdown;
|
||||
static constexpr const char* name = "Controller";
|
||||
static constexpr auto initialize = Libs::Controller::Initialize;
|
||||
static constexpr auto shutdown = Libs::Controller::Shutdown;
|
||||
static constexpr auto emergency_shutdown = Libs::Controller::EmergencyShutdown;
|
||||
};
|
||||
|
||||
constexpr int HOST_INPUT_CONTROLLER_ID = -1000;
|
||||
|
||||
@@ -1330,6 +1330,15 @@ static int KYTY_SYSV_ABI KernelIsTrinityMode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
|
||||
PRINT_NAME();
|
||||
|
||||
*mode = 2; // PS5 Base
|
||||
*submode = 0; // None
|
||||
LOGF("\t mode = %d, submode = %d\n", *mode, *submode);
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI KernelFsync(int fd) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -3325,6 +3334,7 @@ LIB_DEFINE(InitLibKernel_1) {
|
||||
LIB_FUNC("8OnWXlgQlvo", LibKernel::KernelRtldThreadAtexitDecrement);
|
||||
LIB_FUNC("959qrazPIrg", LibKernel::KernelGetProcParam);
|
||||
LIB_FUNC("tU5e3f9gSiU", LibKernel::KernelIsTrinityMode);
|
||||
LIB_FUNC("NH6xARDOVv8", LibKernel::KernelGetOperationMode);
|
||||
LIB_FUNC("fTx66l5iWIA", LibKernel::KernelFsync);
|
||||
LIB_FUNC("uvT2iYBBnkY", LibKernel::KernelSync);
|
||||
LIB_FUNC("HoLVWNanBBc", LibKernel::getpid);
|
||||
|
||||
@@ -153,6 +153,30 @@ static int KYTY_SYSV_ABI PadClose(int handle) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
// PPSA02385
|
||||
static int KYTY_SYSV_ABI PadOpenExtStub(int user_id, int type, int index, const void* param) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t user_id = %d\n"
|
||||
"\t type = %d\n"
|
||||
"\t index = %d\n"
|
||||
"\t param = 0x%016" PRIx64 "\n",
|
||||
user_id, type, index, reinterpret_cast<uint64_t>(param));
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
// PPSA02385
|
||||
static int KYTY_SYSV_ABI PadGetExtControllerInformationStub(int handle, void* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t handle = %d\n"
|
||||
"\t info = 0x%016" PRIx64 "\n",
|
||||
handle, reinterpret_cast<uint64_t>(info));
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PadUnknownN3kSX62fgNo(uint64_t arg0, uint64_t arg1, uint64_t arg2,
|
||||
uint64_t arg3, uint64_t arg4, uint64_t arg5) {
|
||||
PRINT_NAME();
|
||||
@@ -173,6 +197,7 @@ LIB_DEFINE(InitPad_1) {
|
||||
|
||||
LIB_FUNC("hv1luiJrqQM", Controller::PadInit);
|
||||
LIB_FUNC("xk0AcarP3V4", Controller::PadOpen);
|
||||
LIB_FUNC("WFIiSfXGUq8", PadOpenExtStub);
|
||||
LIB_FUNC("u1GRHp+oWoY", Controller::PadGetHandle);
|
||||
LIB_FUNC("AcslpN1jHR8", PadDeviceClassGetExtendedInformation);
|
||||
LIB_FUNC("IHPqcbc0zCA", PadDeviceClassParseData);
|
||||
@@ -181,6 +206,7 @@ LIB_DEFINE(InitPad_1) {
|
||||
LIB_FUNC("rIZnR6eSpvk", Controller::PadResetOrientation);
|
||||
LIB_FUNC("vDLMoJLde8I", PadSetTiltCorrectionState);
|
||||
LIB_FUNC("gjP9-KQzoUk", Controller::PadGetControllerInformation);
|
||||
LIB_FUNC("hGbf2QTBmqc", PadGetExtControllerInformationStub);
|
||||
LIB_FUNC("2JgFB2n9oUM", Controller::PadSetTriggerEffect);
|
||||
LIB_FUNC("YndgXqQVV7c", Controller::PadReadState);
|
||||
LIB_FUNC("q1cHNfGycLI", Controller::PadRead);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "common/abi.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
namespace Libs {
|
||||
|
||||
LIB_VERSION("WebBrowserDialog", 1, "WebBrowserDialog", 1, 1);
|
||||
|
||||
namespace WebBrowserDialog {
|
||||
|
||||
constexpr int WEB_BROWSER_DIALOG_STATUS_NONE = 0;
|
||||
|
||||
static int KYTY_SYSV_ABI WebBrowserDialogGetStatus() {
|
||||
PRINT_NAME();
|
||||
|
||||
return WEB_BROWSER_DIALOG_STATUS_NONE;
|
||||
}
|
||||
|
||||
} // namespace WebBrowserDialog
|
||||
|
||||
LIB_DEFINE(InitWebBrowserDialog_1) {
|
||||
LIB_FUNC("CFTG6a8TjOU", WebBrowserDialog::WebBrowserDialogGetStatus);
|
||||
}
|
||||
|
||||
} // namespace Libs
|
||||
@@ -91,6 +91,7 @@ LIB_DEFINE(InitSysmodule_1);
|
||||
LIB_DEFINE(InitSystemService_1);
|
||||
LIB_DEFINE(InitTextToSpeech2_1);
|
||||
LIB_DEFINE(InitUserService_1);
|
||||
LIB_DEFINE(InitWebBrowserDialog_1);
|
||||
|
||||
void InitAll(Loader::SymbolDatabase* s) {
|
||||
InitAudio_1(s);
|
||||
@@ -130,6 +131,7 @@ void InitAll(Loader::SymbolDatabase* s) {
|
||||
InitUserService_1(s);
|
||||
VideoDec2::InitVideoDec2_1(s);
|
||||
LibGen5::InitVideoOut_1(s);
|
||||
InitWebBrowserDialog_1(s);
|
||||
}
|
||||
|
||||
namespace LibContentExport {
|
||||
|
||||
@@ -69,21 +69,22 @@ struct Call9 {
|
||||
template <class Handler>
|
||||
void SetFunc(Handler func) {
|
||||
auto func_addr = reinterpret_cast<int64_t>(reinterpret_cast<void*>(func));
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[5]);
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[6]);
|
||||
auto offset64 = func_addr - rip_addr;
|
||||
auto offset32 = static_cast<uint32_t>(static_cast<uint64_t>(offset64) & 0xffffffffu);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&code[1]) = offset32;
|
||||
*reinterpret_cast<uint32_t*>(&code[2]) = offset32;
|
||||
}
|
||||
|
||||
void SetOutputReg(uint8_t reg) { code[7] = 0xc0u | (reg & 7u); }
|
||||
void SetOutputReg(uint8_t reg) { code[8] = 0xc0u | (reg & 7u); }
|
||||
|
||||
static uint64_t GetSize() { return 9; }
|
||||
|
||||
// call func
|
||||
// rex.w; call func
|
||||
// mov rax,rax
|
||||
// nop
|
||||
uint8_t code[9] = {0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC0, 0x90};
|
||||
// REX.W is required because AMD processors honor a retained 0x66 operand-size
|
||||
// prefix on a near call and would otherwise execute callw.
|
||||
uint8_t code[9] = {0x48, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC0};
|
||||
};
|
||||
|
||||
struct TlsRegStub {
|
||||
|
||||
@@ -1143,7 +1143,8 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!program->elf->IsShared() && program->tls.handler_vaddr != 0) {
|
||||
if (!program->elf->IsShared() && program->tls.handler_vaddr != 0 &&
|
||||
size >= Jit::Call9::GetSize()) {
|
||||
// Replace:
|
||||
// 66 66 66
|
||||
// mov <reg>, qword ptr fs:[0x00]
|
||||
@@ -1151,7 +1152,8 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) {
|
||||
// call <handler>
|
||||
// mov <reg>,rax
|
||||
// nop ...
|
||||
const uint8_t tls_pattern[5] = {0x64, 0x48, 0x8B, 0x00, 0x25};
|
||||
const uint8_t tls_pattern[5] = {0x64, 0x48, 0x8B, 0x00, 0x25};
|
||||
const uint8_t zero_displacement[4] = {};
|
||||
|
||||
EXIT_IF(Jit::Call9::GetSize() != 9);
|
||||
|
||||
@@ -1173,14 +1175,15 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) {
|
||||
const uint8_t modrm = inst_ptr[3];
|
||||
if (memcmp(inst_ptr, tls_pattern, 3) == 0 && (modrm & 0xc7u) == 0x04u &&
|
||||
inst_ptr[4] == tls_pattern[4] &&
|
||||
*reinterpret_cast<const uint32_t*>(inst_ptr + 5) == 0) {
|
||||
LOGF("Patch tls at addr: [%016" PRIx64 "]\n",
|
||||
reinterpret_cast<uint64_t>(inst_ptr));
|
||||
memcmp(inst_ptr + 5, zero_displacement, sizeof(zero_displacement)) == 0) {
|
||||
LOGF("Patch tls at addr: [%016" PRIx64 "]\n", reinterpret_cast<uint64_t>(inst_ptr));
|
||||
|
||||
const auto reg = (modrm >> 3u) & 7u;
|
||||
EXIT_NOT_IMPLEMENTED(reg == 4u);
|
||||
|
||||
// A raw scan can encounter a 0x66 in the preceding instruction.
|
||||
// A raw scan can encounter a 0x66 in the preceding instruction, so do not
|
||||
// overwrite it. Call9 starts with REX.W to neutralize genuine 0x66
|
||||
// prefixes on AMD processors (before it could turn E8 into callw 16bit).
|
||||
auto* code = new (inst_ptr) Jit::Call9;
|
||||
code->SetFunc(reg == 0
|
||||
? program->tls.handler_vaddr
|
||||
|
||||
@@ -70,7 +70,6 @@ void CheckSaveRename(const std::filesystem::path &root,
|
||||
Common::File result(root / "SDATA000.DAT", Common::File::Mode::Read);
|
||||
Check(!result.IsInvalid(), "open renamed save file");
|
||||
const auto data = result.ReadWholeBuffer();
|
||||
result.Close();
|
||||
const std::string expected = std::string(payload) + Suffix;
|
||||
Check(data.Size() == expected.size(), "renamed save size");
|
||||
Check(std::memcmp(data.GetData(), expected.data(), expected.size()) == 0,
|
||||
|
||||
@@ -1350,6 +1350,18 @@ std::array<u32, 64> MakeStorageTextureData(Prospero::BufferFormat format) {
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string StorageUint2DImageBindingName(bool atomic) {
|
||||
ShaderRecompiler::IR::ImageResource image{};
|
||||
image.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage;
|
||||
image.numeric_class = Prospero::TextureNumericClass::Uint;
|
||||
image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
image.atomic = atomic;
|
||||
const auto binding = ShaderRecompiler::IR::DescriptorBindingForImage(image);
|
||||
Require("StorageUint2DImageBindingName", "descriptor binding", binding.has_value(),
|
||||
"storage image has no descriptor binding");
|
||||
return "image_" + std::to_string(static_cast<uint32_t>(*binding));
|
||||
}
|
||||
|
||||
CompiledShader CompileFragmentCase(const GraphicsCase &test) {
|
||||
const auto user_data =
|
||||
MakeNativeUserData(test.has_user_data ? &test.user_data : nullptr);
|
||||
@@ -8496,6 +8508,7 @@ public:
|
||||
phased_depth_target.stencil_info.format = Prospero::StencilFormat::k8UInt;
|
||||
phased_depth_target.stencil_info.texture_compatibility =
|
||||
Prospero::TextureCompatibleStencil::kEnable;
|
||||
phased_depth_target.stencil_info.htile_stencil_disabled = false;
|
||||
phased_depth_target.z_read_base_addr = phased_depth_address;
|
||||
phased_depth_target.z_write_base_addr = phased_depth_address;
|
||||
phased_depth_target.stencil_read_base_addr = phased_stencil_address;
|
||||
@@ -12226,6 +12239,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
|
||||
case Opcode::TBUFFER_STORE_FORMAT_XYZ:
|
||||
case Opcode::TBUFFER_STORE_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_ATOMIC_SWAP:
|
||||
case Opcode::BUFFER_ATOMIC_CMPSWAP:
|
||||
case Opcode::BUFFER_ATOMIC_ADD:
|
||||
case Opcode::BUFFER_ATOMIC_SUB:
|
||||
case Opcode::BUFFER_ATOMIC_SMIN:
|
||||
@@ -18748,6 +18762,7 @@ TestCase ScratchIsPrivatePerInvocation() {
|
||||
return test;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
TestCase FlatStoreVariants() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -18795,6 +18810,7 @@ TestCase FlatStoreVariants() {
|
||||
O::FLAT_STORE_DWORDX3, O::FLAT_STORE_DWORDX4, O::S_ENDPGM}};
|
||||
return test;
|
||||
}
|
||||
#endif
|
||||
|
||||
TestCase DsReadWriteVariants() {
|
||||
using O = ShaderOpcode;
|
||||
@@ -19450,6 +19466,52 @@ TestCase BufferAtomicVariants() {
|
||||
O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase BufferAtomicCmpSwapExactRaw() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
const auto Store = [&](u32 value, u32 index) {
|
||||
AppendVMovU32(&code, 31, index * sizeof(u32));
|
||||
code.push_back(EncodeMubuf0(0x1cu));
|
||||
code.push_back(EncodeMubuf1(value, 8, 31));
|
||||
};
|
||||
AppendVMovU32(&code, 4, 1);
|
||||
AppendVMovLiteral(&code, 1, 0x22222222u);
|
||||
AppendVMovLiteral(&code, 2, 0x11111111u);
|
||||
code.push_back(0xe0c46000u);
|
||||
code.push_back(0x80080104u);
|
||||
Store(1, 2);
|
||||
|
||||
AppendVMovLiteral(&code, 1, 0x33333333u);
|
||||
AppendVMovLiteral(&code, 2, 0x44444444u);
|
||||
code.push_back(0xe0c46000u);
|
||||
code.push_back(0x80080104u);
|
||||
Store(1, 3);
|
||||
Store(2, 4);
|
||||
|
||||
AppendVMovLiteral(&code, 1, 0x55555555u);
|
||||
AppendVMovLiteral(&code, 2, 0x22222222u);
|
||||
code.push_back(EncodeMubuf0(0x31u, 0, true, false));
|
||||
code.push_back(0x80080104u);
|
||||
Store(1, 5);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "BufferAtomicCmpSwapExactRaw";
|
||||
test.code = std::move(code);
|
||||
test.initial = {0xaaaaaaaau, 0x11111111u, 0, 0, 0, 0};
|
||||
test.expected = {0xaaaaaaaau, 0x55555555u, 0x11111111u,
|
||||
0x22222222u, 0x44444444u, 0x55555555u};
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_ATOMIC_CMPSWAP,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpAtomicCompareExchange"};
|
||||
const auto descriptor = MakeStructuredStorageBufferData(
|
||||
sizeof(u32), static_cast<u32>(test.initial.size()));
|
||||
std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 32);
|
||||
test.has_user_data = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferAtomicGlc0DoesNotReturnOldValue() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -20832,7 +20894,7 @@ TestCase ImageStoreR32SintUsesRawUintView() {
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.expected_storage_image_r32ui = expected_image;
|
||||
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
||||
"image_29"};
|
||||
StorageUint2DImageBindingName(false)};
|
||||
test.forbidden_spirv = {"R32ui"};
|
||||
return test;
|
||||
}
|
||||
@@ -20862,7 +20924,7 @@ TestCase ImageStoreR32UintUsesFormatlessStorageImage() {
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.expected_storage_image_r32ui = expected_image;
|
||||
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
||||
"image_29"};
|
||||
StorageUint2DImageBindingName(false)};
|
||||
test.forbidden_spirv = {"R32ui"};
|
||||
return test;
|
||||
}
|
||||
@@ -20896,7 +20958,8 @@ TestCase ImageStorePackedUintSaturatesChannels() {
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.expected_storage_image_r32ui = std::move(expected_image);
|
||||
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
||||
"image_29", "OpULessThan", "OpSelect"};
|
||||
StorageUint2DImageBindingName(false), "OpULessThan",
|
||||
"OpSelect"};
|
||||
test.forbidden_spirv = {"R32ui"};
|
||||
return test;
|
||||
}
|
||||
@@ -20928,7 +20991,7 @@ TestCase ImageStorePackedUintHonorsSparseDmask() {
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.expected_storage_image_r32ui = std::move(expected_image);
|
||||
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
||||
"image_29"};
|
||||
StorageUint2DImageBindingName(false)};
|
||||
test.forbidden_spirv = {"R32ui"};
|
||||
return test;
|
||||
}
|
||||
@@ -20989,9 +21052,10 @@ TestCase ImageStoreAndAtomicShareTypedBinding() {
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.expected_storage_image_r32ui = std::move(expected_image);
|
||||
test.required_spirv = {"OpImageWrite", "OpImageTexelPointer", "R32ui",
|
||||
"image_34"};
|
||||
StorageUint2DImageBindingName(true)};
|
||||
test.forbidden_spirv = {"StorageImageReadWithoutFormat",
|
||||
"StorageImageWriteWithoutFormat", "image_29"};
|
||||
"StorageImageWriteWithoutFormat",
|
||||
StorageUint2DImageBindingName(false)};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -21063,8 +21127,8 @@ TestCase ImageStoreAndAtomicUseSeparateBindings() {
|
||||
"OpImageWrite",
|
||||
"OpImageTexelPointer",
|
||||
"R32ui",
|
||||
"image_29",
|
||||
"image_34"};
|
||||
StorageUint2DImageBindingName(false),
|
||||
StorageUint2DImageBindingName(true)};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -21095,10 +21159,12 @@ TestCase ImageAtomicVariants() {
|
||||
O::IMAGE_ATOMIC_UMIN, O::IMAGE_ATOMIC_AND,
|
||||
O::IMAGE_ATOMIC_OR, O::IMAGE_ATOMIC_XOR,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpImageTexelPointer", "R32ui", "image_34"};
|
||||
test.required_spirv = {"OpImageTexelPointer", "R32ui",
|
||||
StorageUint2DImageBindingName(true)};
|
||||
test.forbidden_spirv = {"OpTypeSampler", "OpTypeSampledImage",
|
||||
"StorageImageReadWithoutFormat",
|
||||
"StorageImageWriteWithoutFormat", "image_29"};
|
||||
"StorageImageWriteWithoutFormat",
|
||||
StorageUint2DImageBindingName(false)};
|
||||
test.storage_image_rgba = MakeRgbaImage(4, 4);
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
for (u32 i = 0; i < static_cast<u32>(std::size(initial)); i++) {
|
||||
@@ -21654,6 +21720,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(DsBpermuteCapturedExecOffsetAndWrap);
|
||||
AddCase(DsBpermuteWave64UsesIndependentHalves);
|
||||
AddCase(BufferAtomicVariants);
|
||||
AddCase(BufferAtomicCmpSwapExactRaw);
|
||||
AddCase(BufferAtomicGlc0DoesNotReturnOldValue);
|
||||
AddCase(BufferAtomicFMinExactRawGlcModes);
|
||||
AddCase(BufferAtomicFMinSpecialValues);
|
||||
@@ -21909,6 +21976,18 @@ void CheckEmbeddedFetchVertexOffset() {
|
||||
return ResolveVertexOffset(index_offset, vertex);
|
||||
};
|
||||
|
||||
const auto ResolveInstance = [](const CompiledShader &result) {
|
||||
ShaderVertexInputInfo vertex;
|
||||
vertex.fetch_embedded = true;
|
||||
ShaderRecompiler::IR::CompiledShaderInfo program{};
|
||||
program.user_data_base = result.program.user_data_base;
|
||||
program.info.instance_offset_sgpr =
|
||||
result.program.info.instance_offset_sgpr;
|
||||
vertex.stage.program = &program;
|
||||
vertex.stage.resources = result.resources;
|
||||
return ResolveInstanceOffset(vertex);
|
||||
};
|
||||
|
||||
const auto valid =
|
||||
Compile("EmbeddedFetchVertexOffset", MakeFetch({{18, 0}}), 7);
|
||||
Require(
|
||||
@@ -21928,6 +22007,33 @@ void CheckEmbeddedFetchVertexOffset() {
|
||||
"PS5 NGG vertex-index offset or register index-offset precedence is "
|
||||
"wrong");
|
||||
|
||||
const auto ngg_instance =
|
||||
Compile("EmbeddedFetchNggInstanceOffset",
|
||||
MakeFetch({{18, 8}}, {}, 8, true), 81);
|
||||
Require("EmbeddedFetchNggInstanceOffset", "parse",
|
||||
ngg_instance.program.info.vertex_offset_sgpr == -1 &&
|
||||
ngg_instance.program.info.instance_offset_sgpr == 18 &&
|
||||
ResolveInstance(ngg_instance) == 81,
|
||||
"PS5 NGG instance-index offset was not detected or resolved");
|
||||
|
||||
const auto instance_conflict = Compile(
|
||||
"EmbeddedFetchConflictingInstanceOffset",
|
||||
MakeFetch({{17, 8}, {18, 8}}, {}, 8, true), 81);
|
||||
const auto instance_malformed =
|
||||
Compile("EmbeddedFetchMalformedInstanceOffset",
|
||||
MakeFetch({{18, 7}}, {}, 7, true), 81);
|
||||
const auto instance_outside =
|
||||
Compile("EmbeddedFetchOutsideInstanceOffset",
|
||||
MakeFetch({{19, 8}}, {}, 8, true), 81);
|
||||
for (const auto *result :
|
||||
{&instance_conflict, &instance_malformed, &instance_outside}) {
|
||||
Require("EmbeddedFetchNggInstanceOffset", "fail closed",
|
||||
result->program.info.instance_offset_sgpr == -1 &&
|
||||
ResolveInstance(*result) == 0,
|
||||
"conflicting, malformed, or out-of-window instance add was "
|
||||
"classified");
|
||||
}
|
||||
|
||||
const auto pointer = 0x5b7c5100u;
|
||||
const auto late = Compile("EmbeddedFetchLateOffset",
|
||||
MakeFetch({}, std::pair<u32, u32>{18, 0}), pointer);
|
||||
@@ -25802,6 +25908,11 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, BufferOffsetsUsePackedLaneAndStorageFallback());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--buffer-cmpswap-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, BufferAtomicCmpSwapExactRaw());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--mapped-range-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckGpuMappedRangeLifecycle();
|
||||
|
||||
@@ -8557,40 +8557,120 @@ void TestNewShaderRecompilerBufferLoadsGuardedByExec() {
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerBufferAtomicsGuardedByBounds() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeMubuf0(0x32, 0, true, true),
|
||||
EncodeMubuf1(0, 0, 1), // buffer_atomic_add
|
||||
EncodeSopp(0x01),
|
||||
struct Case {
|
||||
std::array<uint32_t, 3> shader;
|
||||
const char *decoded;
|
||||
const char *ir;
|
||||
const char *spirv;
|
||||
};
|
||||
const Case cases[] = {
|
||||
{{EncodeMubuf0(0x32, 0, true, true), EncodeMubuf1(0, 0, 1),
|
||||
EncodeSopp(0x01)},
|
||||
"BUFFER_ATOMIC_ADD", "BufferAtomicIAdd32", "OpAtomicIAdd"},
|
||||
{{0xe0c46000u, 0x80080104u, EncodeSopp(0x01)},
|
||||
"BUFFER_ATOMIC_CMPSWAP", "BufferAtomicCmpSwap32",
|
||||
"OpAtomicCompareExchange"},
|
||||
};
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check(Common::ContainsStr(result.decoded_dump, "BUFFER_ATOMIC_ADD"),
|
||||
"buffer atomic bounds regression did not decode MUBUF atomic");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
for (const auto &test : cases) {
|
||||
const auto result = RecompileForTest(test.shader, options);
|
||||
Check(Common::ContainsStr(result.decoded_dump, test.decoded),
|
||||
"buffer atomic bounds regression did not decode MUBUF atomic");
|
||||
Check(Common::ContainsStr(result.ir_dump, test.ir),
|
||||
"buffer atomic did not lower to its native IR opcode");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
const auto array_length =
|
||||
Common::FindIndex(source, std::string("OpArrayLength"), 0);
|
||||
const auto bounds_branch = Common::FindIndex(
|
||||
source, std::string("OpBranchConditional"), array_length);
|
||||
const auto atomic = Common::FindIndex(source, std::string("OpAtomicIAdd"), 0);
|
||||
const auto memory_barrier =
|
||||
Common::FindIndex(source, std::string("OpMemoryBarrier"), atomic);
|
||||
Check(array_length != Common::FIND_INVALID_INDEX,
|
||||
"buffer atomic SPIR-V lacks storage buffer array-length bounds check");
|
||||
Check(bounds_branch != Common::FIND_INVALID_INDEX,
|
||||
"buffer atomic SPIR-V lacks storage buffer bounds branch");
|
||||
Check(atomic != Common::FIND_INVALID_INDEX,
|
||||
"buffer atomic SPIR-V lacks atomic operation");
|
||||
Check(memory_barrier != Common::FIND_INVALID_INDEX,
|
||||
"buffer atomic SPIR-V lacks memory barrier after atomic operation");
|
||||
Check(bounds_branch < atomic,
|
||||
"buffer atomic was emitted before bounds guard");
|
||||
Check(atomic < memory_barrier,
|
||||
"buffer atomic memory barrier was emitted before atomic");
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
const auto array_length =
|
||||
Common::FindIndex(source, std::string("OpArrayLength"), 0);
|
||||
const auto bounds_branch = Common::FindIndex(
|
||||
source, std::string("OpBranchConditional"), array_length);
|
||||
const auto atomic = Common::FindIndex(source, std::string(test.spirv), 0);
|
||||
const auto memory_barrier =
|
||||
Common::FindIndex(source, std::string("OpMemoryBarrier"), atomic);
|
||||
Check(array_length != Common::FIND_INVALID_INDEX &&
|
||||
bounds_branch != Common::FIND_INVALID_INDEX &&
|
||||
atomic != Common::FIND_INVALID_INDEX &&
|
||||
memory_barrier != Common::FIND_INVALID_INDEX,
|
||||
"buffer atomic SPIR-V lacks its bounds guard, operation, or barrier");
|
||||
Check(bounds_branch < atomic && atomic < memory_barrier,
|
||||
"buffer atomic bounds guard, operation, and barrier are misordered");
|
||||
}
|
||||
}
|
||||
|
||||
void TestCapturedBufferAtomicsX2() {
|
||||
using ShaderRecompiler::IR::ValueOpcode;
|
||||
struct Case {
|
||||
std::array<uint32_t, 2> words;
|
||||
uint32_t data_vgpr;
|
||||
uint32_t spirv_opcode;
|
||||
const char *decoded_name;
|
||||
const char *ir_name;
|
||||
const char *spirv_name;
|
||||
ValueOpcode ir_opcode;
|
||||
};
|
||||
const Case cases[] = {
|
||||
{{0xe1680018u, 0x80000000u}, 0u, 241u, "BUFFER_ATOMIC_OR_X2",
|
||||
"BufferAtomicOr64", "OpAtomicOr", ValueOpcode::BufferAtomicOr64},
|
||||
{{0xe1402000u, 0x80000913u}, 9u, 229u, "BUFFER_ATOMIC_SWAP_X2",
|
||||
"BufferAtomicSwap64", "OpAtomicExchange", ValueOpcode::BufferAtomicSwap64},
|
||||
};
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
for (const auto &test : cases) {
|
||||
const std::array shader = {test.words[0], test.words[1], EncodeSopp(0x01)};
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check(Common::ContainsStr(result.decoded_dump, test.decoded_name),
|
||||
"captured 64-bit MUBUF atomic decoded incorrectly");
|
||||
Check(Common::ContainsStr(result.ir_dump, test.ir_name),
|
||||
"64-bit MUBUF atomic did not lower to its native IR opcode");
|
||||
Check(!Common::ContainsStr(result.ir_dump, "SetVectorRegister"),
|
||||
"GLC=0 64-bit MUBUF atomic unexpectedly returned the old value");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
Check(Common::ContainsStr(source, "OpCapability Int64Atomics"),
|
||||
"64-bit buffer atomic SPIR-V lacks Int64Atomics capability");
|
||||
Check(Common::ContainsStr(source, "ArrayStride 8"),
|
||||
"64-bit buffer atomic storage view does not use eight-byte elements");
|
||||
Check(CountSourceOccurrences(source, "Aliased") == 2u,
|
||||
"both storage-buffer views must declare that they alias");
|
||||
const auto array_length =
|
||||
Common::FindIndex(source, std::string("OpArrayLength"), 0);
|
||||
const auto bounds_branch = Common::FindIndex(
|
||||
source, std::string("OpBranchConditional"), array_length);
|
||||
const auto atomic =
|
||||
Common::FindIndex(source, std::string(test.spirv_name), 0);
|
||||
const auto memory_barrier =
|
||||
Common::FindIndex(source, std::string("OpMemoryBarrier"), atomic);
|
||||
Check(array_length != Common::FIND_INVALID_INDEX &&
|
||||
bounds_branch != Common::FIND_INVALID_INDEX &&
|
||||
atomic != Common::FIND_INVALID_INDEX && bounds_branch < atomic,
|
||||
"64-bit buffer atomic was not guarded by storage-buffer bounds");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, test.spirv_opcode) == 1u,
|
||||
"64-bit buffer atomic did not lower to one native SPIR-V atomic");
|
||||
Check(memory_barrier != Common::FIND_INVALID_INDEX && atomic < memory_barrier,
|
||||
"64-bit buffer atomic lacks a following device-memory barrier");
|
||||
|
||||
const std::array glc_shader = {
|
||||
test.words[0] | (1u << 14u), test.words[1],
|
||||
EncodeMubuf0(0x1d, 32, false),
|
||||
EncodeMubuf1(test.data_vgpr, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
auto glc_result = RecompileForTest(glc_shader, options);
|
||||
const ShaderRecompiler::IR::Inst *glc_atomic = nullptr;
|
||||
for (const auto *block : glc_result.program.blocks) {
|
||||
for (const auto &inst : *block) {
|
||||
if (inst.GetOpcode() == test.ir_opcode) glc_atomic = &inst;
|
||||
}
|
||||
}
|
||||
Check(glc_atomic != nullptr && glc_atomic->UseCount() == 2u,
|
||||
"GLC=1 64-bit MUBUF atomic did not return both dwords");
|
||||
CheckSpirvBinaryValidates(glc_result.spirv);
|
||||
}
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerBranchConditionForms() {
|
||||
@@ -8982,6 +9062,18 @@ void TestNewShaderRecompilerAuxPositionExports() {
|
||||
std::vector<uint32_t>({0u}),
|
||||
"CCDIST1 did not shift across a disabled CCDIST0 vector");
|
||||
|
||||
const uint32_t unmapped[] = {
|
||||
EncodeExp0(0x0d, 0x4, false), EncodeExp1(0, 0, 8, 0),
|
||||
EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3),
|
||||
0xbf810000u,
|
||||
};
|
||||
const auto ignored = compile(unmapped, 0u);
|
||||
CheckSpirvBinaryValidates(ignored.spirv);
|
||||
Check(ignored.program.info.outputs.size() == 1u &&
|
||||
ignored.program.info.outputs[0].kind ==
|
||||
ShaderRecompiler::IR::StageOutputKind::Position,
|
||||
"unmapped auxiliary position export was not ignored");
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
const auto rejects = [](uint32_t control, uint32_t en,
|
||||
const char *message) {
|
||||
@@ -9000,9 +9092,6 @@ void TestNewShaderRecompilerAuxPositionExports() {
|
||||
rejects(0x00280000u, 0x4u,
|
||||
"viewport-index auxiliary position export did not terminate "
|
||||
"compilation");
|
||||
rejects(0u, 0x1u,
|
||||
"unsupported auxiliary/stereo position export did not terminate "
|
||||
"compilation");
|
||||
#endif
|
||||
|
||||
ShaderVertexInputInfo key0{};
|
||||
@@ -11935,6 +12024,7 @@ int main() {
|
||||
TestComputeDispatchWaveSize();
|
||||
TestNewShaderRecompilerBufferLoadsGuardedByExec();
|
||||
TestNewShaderRecompilerBufferAtomicsGuardedByBounds();
|
||||
TestCapturedBufferAtomicsX2();
|
||||
TestNewShaderRecompilerPixelImageSampleLodSelection();
|
||||
TestNewShaderRecompilerBranchConditionForms();
|
||||
TestNewShaderRecompilerSetpcBranch();
|
||||
|
||||