Compare commits

..
Author SHA1 Message Date
nmzik dd9ecd5f65 Revert "shader: separate comparison image bindings"
This reverts commit d762d24f73.
2026-09-04 03:09:00 +02:00
nmzik 81b0c1302d Revert "graphics: create depth images for comparison textures"
This reverts commit 0ce19357f6.
2026-09-04 03:08:56 +02:00
Tarunandnmzik b9b0b87545 Add update checker to launcher (#478)
launcher: add release update checker

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-04 02:38:02 +02:00
nmzik 040b2b9653 tests: align GPU fixtures with binding architecture 2026-09-04 02:06:06 +02:00
nmzik eccd6f6cac graphics: preserve unrestricted viewport depth ranges 2026-09-04 02:06:06 +02:00
nmzik 0ce19357f6 graphics: create depth images for comparison textures 2026-09-04 02:06:06 +02:00
nmzik d762d24f73 shader: separate comparison image bindings 2026-09-04 02:06:06 +02:00
nmzik eb917463ad fix File abstraction destructor 2026-09-04 02:06:06 +02:00
Mehmet Cambaz c2189de88f controller: give the DualShock 4 the DualSense pad path and release p… (#453)
controller: give the DualShock 4 the DualSense pad path and release pads on exit
2026-09-04 00:10:57 +02:00
GodsWill Devandnmzik 2fe5c150bc shader: implement BUFFER_ATOMIC_CMPSWAP (#357)
shader: implement RDNA2 buffer atomic compare swap

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 23:34:34 +02:00
nmzik 5b8e3b51b0 cross-vendor TLS fix: 0x66 prefixes are forced into 64bit call on AMD 2026-09-03 23:03:48 +02:00
nmzik f27a136430 Refactor(simplify) PM4 draw submission arguments 2026-09-03 22:23:13 +02:00
nmzik fb4d97e8a9 graphics: fix PS5 NGG vertex and instance draw offsets
Refs: https://github.com/KytyPS5/KytyPS5/issues/130
2026-09-03 21:52:02 +02:00
nmzik 5ffcd28ca4 launcher: fix rare use after free bug 2026-09-03 11:32:32 +02:00
nmzik b3c8f91a84 graphics: implement RDNA2 buffer atomic swap x2 2026-09-03 10:17:04 +02:00
nmzik c78a5a9fdf graphics: unify atomic feature and width handling 2026-09-03 05:45:45 +02:00
nmzik 858c18468b graphics: implement RDNA2 buffer atomic OR x2 2026-09-03 05:39:05 +02:00
nmzik cd74809086 launcher: fix light mode, group settings into resizable sections 2026-09-03 04:13:34 +02:00
57 changed files with 1494 additions and 732 deletions

No files matched your search

+1
View File
@@ -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);
}
-4
View File
@@ -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) \
+1 -1
View File
@@ -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(); }
@@ -1618,15 +1618,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 +1641,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 +1700,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 +1715,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;
}
+52 -73
View File
@@ -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) {
@@ -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());
+29 -7
View File
@@ -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;
};
+89 -68
View File
@@ -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;
@@ -316,7 +335,9 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
EXIT_IF(colors == nullptr);
const auto& ctx = buffer.GetRegisters();
const auto& vp = ctx.GetScreenViewport();
const auto& vp = ctx.GetScreenViewport();
const auto& viewport_regs = vp.viewports[0];
const auto& clip_control = ctx.GetClipControl();
vk::Extent2D framebuffer_extent {};
if (color_count > 0 && colors[0].image_id) {
framebuffer_extent = colors[0].extent;
@@ -330,20 +351,21 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
vk::Viewport viewport {};
if (ctx.GetClipControl().clip_disable) {
if (clip_control.clip_disable) {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(std::min(limits.maxViewportDimensions[0], 16384u));
viewport.height = static_cast<float>(std::min(limits.maxViewportDimensions[1], 16384u));
} else {
viewport.x = vp.viewports[0].xoffset - vp.viewports[0].xscale;
viewport.y = vp.viewports[0].yoffset - vp.viewports[0].yscale;
viewport.width = vp.viewports[0].xscale * 2.0f;
viewport.height = vp.viewports[0].yscale * 2.0f;
viewport.x = viewport_regs.xoffset - viewport_regs.xscale;
viewport.y = viewport_regs.yoffset - viewport_regs.yscale;
viewport.width = viewport_regs.xscale * 2.0f;
viewport.height = viewport_regs.yscale * 2.0f;
}
viewport.minDepth = vp.viewports[0].zoffset;
viewport.maxDepth = vp.viewports[0].zscale + vp.viewports[0].zoffset;
viewport.minDepth =
viewport_regs.zoffset - (clip_control.dx_clip_space ? 0.0f : viewport_regs.zscale);
viewport.maxDepth = viewport_regs.zoffset + viewport_regs.zscale;
vk_buffer.setViewport(0, 1, &viewport);
vk::Rect2D scissor {};
@@ -505,7 +527,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 +768,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 +902,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 +1015,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 +1124,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 +1146,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 +1158,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 +1261,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 +1301,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 +1321,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 +1364,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 +1393,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 +1430,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 +1476,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,
+3 -2
View File
@@ -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;
@@ -993,7 +1019,8 @@ void WindowContext::CreateVulkan() {
std::vector<const char*> device_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"};
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
"VK_KHR_maintenance1"};
#if defined(__APPLE__)
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
@@ -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,
@@ -352,6 +354,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 +393,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 +407,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 +624,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 +644,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);
@@ -614,9 +614,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 +635,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 +643,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,7 +686,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 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 FloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
@@ -1080,12 +1126,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 +1225,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;
}
@@ -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 {
@@ -418,6 +418,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 +434,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)
+1 -1
View File
@@ -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()
+262 -357
View File
@@ -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 &amp;&amp; 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 &amp;&amp; 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>
+36 -2
View File
@@ -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>&lt;a href=&quot;check&quot;&gt;Check for updates&lt;/a&gt;</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 -1
View File
@@ -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 -1
View File
@@ -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

+3 -3
View File
@@ -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 -1
View File
@@ -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;
+2 -1
View File
@@ -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;
+3 -2
View File
@@ -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);
}
}
+12
View File
@@ -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
+34
View File
@@ -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
+3
View File
@@ -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>
+12 -2
View File
@@ -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();
+4 -14
View File
@@ -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));
}
+29 -35
View File
@@ -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;
+45
View File
@@ -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
+3 -6
View File
@@ -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);
+27 -3
View File
@@ -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();
}
+125
View File
@@ -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);
}
}
+108
View File
@@ -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;
}
+42 -1
View File
@@ -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);
+5 -3
View File
@@ -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;
+7 -6
View File
@@ -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 {
+9 -6
View File
@@ -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
-1
View File
@@ -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,
+121 -10
View File
@@ -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();
+110 -29
View File
@@ -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() {
@@ -11935,6 +12015,7 @@ int main() {
TestComputeDispatchWaveSize();
TestNewShaderRecompilerBufferLoadsGuardedByExec();
TestNewShaderRecompilerBufferAtomicsGuardedByBounds();
TestCapturedBufferAtomicsX2();
TestNewShaderRecompilerPixelImageSampleLodSelection();
TestNewShaderRecompilerBranchConditionForms();
TestNewShaderRecompilerSetpcBranch();