Compare commits

...
Author SHA1 Message Date
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
Brandon Strongandnmzik e21c5f37d0 graphics: honour DCC fixed clear codes on 16-bit and float colour targets (#471)
* Honour DCC fixed clear codes on 16-bit and float colour targets

* tests: DCC fixed clear codes materialise on an RGBA16F target

* graphics: remove fixed clear format comment

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 03:41:28 +02:00
Brandon Strong 2d5023fda2 shader: support IMAGE_ATOMIC_SWAP (#456) 2026-09-03 03:37:55 +02:00
nmzik 98200fed19 shader: rework resource specialization and optimize pipeline caching 2026-09-03 03:04:17 +02:00
nmzik c48a8bd6ae shader: evaluate uniform first-lane sampler descriptors 2026-09-03 03:04:17 +02:00
Brandon Strongandnmzik 16ea36eb4b libkernel: register scePthreadRwlockTryrdlock and the timed rwlock variants (#472)
* libkernel: register scePthreadRwlockTryrdlock and the timed rwlock variants

* fix: initialize static rwlocks in newly registered APIs

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 01:58:38 +02:00
Brandon Strong 8f6f65f4c9 libs: implement Json Value::referValue by key and Value::toString (#455) 2026-09-03 01:58:23 +02:00
93 changed files with 4375 additions and 2998 deletions

No files matched your search

+6 -7
View File
@@ -431,16 +431,15 @@ add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
)
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ShaderStageRuntimeTests.cpp"
add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceMaterializationTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/shaderStageRuntime.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
)
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
target_link_libraries(resource_materialization_tests fmt::fmt)
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
@@ -542,7 +541,7 @@ if(BUILD_TESTING)
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
add_test(NAME resource_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
@@ -598,7 +597,7 @@ if(BUILD_TESTING)
page_manager_tests
bit_array_tests
shader_vertex_metadata_tests
shader_stage_runtime_tests
resource_materialization_tests
resource_tracking_tests
event_queue_lifetime_tests
kernel_file_system_tests
+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) {
+3 -2
View File
@@ -939,7 +939,7 @@ void TextureCache::UploadImage(Image& image, const ImageDesc& desc, Buffer& sour
image.Upload(copies, linear.buffer, linear.offset, linear.size);
};
if (desc.type != BindingType::DepthTarget) {
if (!info.IsDepth()) {
auto plan = BuildColorTransfer(image, desc.type, TransferDirection::Upload);
if (!plan.valid) {
EXIT("TextureCache: invalid color upload: binding=%u addr=0x%016" PRIx64
@@ -963,7 +963,8 @@ void TextureCache::UploadImage(Image& image, const ImageDesc& desc, Buffer& sour
return;
}
if (desc.type != BindingType::DepthTarget || info.samples != 1 || image.backing.samples != 1 ||
if ((desc.type != BindingType::Texture && desc.type != BindingType::DepthTarget) ||
info.samples != 1 || image.backing.samples != 1 ||
info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
Prospero::NumBytesPerElement(info.guest_format) != info.bytes_per_block) {
EXIT("TextureCache: invalid depth upload\n");
@@ -28,12 +28,27 @@ static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool h
info.metadata_clear_supported =
has_dcc && DecodePackedColorClear(format, packed_clear, info.color_clear_value);
switch (format) {
case vk::Format::eR8Unorm:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2R10G10B10UnormPack32:
case vk::Format::eR5G6B5UnormPack16:
case vk::Format::eA1R5G5B5UnormPack16:
case vk::Format::eR4G4B4A4UnormPack16:
case vk::Format::eR16Unorm:
case vk::Format::eR16G16Unorm:
case vk::Format::eR16G16B16A16Unorm:
case vk::Format::eR16Sfloat:
case vk::Format::eR16G16Sfloat:
case vk::Format::eR16G16B16A16Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32G32Sfloat:
case vk::Format::eR32G32B32A32Sfloat:
case vk::Format::eB10G11R11UfloatPack32:
info.metadata_fixed_clear_supported = has_dcc;
break;
default: info.metadata_fixed_clear_supported = false; break;
@@ -65,7 +65,7 @@ vk::DescriptorType NativeDescriptorType(BindingKind kind) {
case BindingKind::BdaPagetable:
case BindingKind::FaultBuffer:
case BindingKind::FlattenedSrt:
case BindingKind::UserData: return vk::DescriptorType::eStorageBuffer;
case BindingKind::ShaderData: return vk::DescriptorType::eStorageBuffer;
case BindingKind::Count: EXIT("invalid native descriptor binding kind");
}
EXIT("invalid native descriptor binding kind");
@@ -108,12 +108,6 @@ static void BindNullStorageBuffer(RenderContext& context, BufferView& dst) {
dst.range = 16;
}
static void CopyNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& source,
std::span<uint32_t> destination) {
EXIT_IF(source.dword_count != destination.size());
std::copy_n(source.dwords.begin(), destination.size(), destination.begin());
}
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
const auto type = descriptor.Type();
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
@@ -518,6 +512,20 @@ struct NullImageSpec {
Prospero::BufferFormat guest_format;
};
static vk::Format TextureBackingFormat(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::BufferFormat guest_format,
vk::Format view_format) {
if (!resource.depth_compare) {
return view_format;
}
const auto* policy = FindGuestDepthFormatPolicy(guest_format);
if (!IsSupportedSampledDepthResource(resource) || policy == nullptr) {
EXIT("unsupported comparison-sampled texture: format=%u\n",
static_cast<uint32_t>(guest_format));
}
return policy->depth_attachment_format;
}
static NullImageSpec NullTextureSpec(const ShaderRecompiler::IR::ImageResource& resource) {
switch (resource.numeric_class) {
case Prospero::TextureNumericClass::Float:
@@ -535,7 +543,7 @@ static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::Image
TextureCache::BindingType binding) {
const auto spec = NullTextureSpec(resource);
TextureCache::ImageDesc desc {};
desc.info.pixel_format = spec.format;
desc.info.pixel_format = TextureBackingFormat(resource, spec.guest_format, spec.format);
desc.info.guest_format = spec.guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {1, 1, 1};
@@ -543,7 +551,7 @@ static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::Image
desc.info.bytes_per_block = 4;
desc.info.samples = 1;
desc.info.mip_layout[0] = {0, 0, 1, 1};
desc.view_info.format = desc.info.pixel_format;
desc.view_info.format = spec.format;
desc.view_info.type = vk::ImageViewType::e2D;
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.usage = binding == TextureCache::BindingType::Storage
@@ -658,8 +666,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderTextureResource descriptor;
CopyNativeDescriptor(value, descriptor.fields);
auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(value);
const bool storage = resource.written;
if (storage) {
ValidateStorageImageResource(resource);
@@ -760,7 +767,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format);
TextureCache::ImageDesc desc {};
desc.info.data = {address, size.size};
desc.info.pixel_format = pixel_format;
desc.info.pixel_format = TextureBackingFormat(resource, format, pixel_format);
desc.info.guest_format = format;
desc.info.type = TextureBaseType(type);
desc.info.extent = {width, height, volume ? depth : 1u};
@@ -802,17 +809,11 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
}
static vk::Sampler NativeSampler(RenderContext& context,
const ShaderRecompiler::IR::Program& program, uint32_t index,
const ShaderRecompiler::IR::CompiledShaderInfo& program,
uint32_t index,
const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderSamplerResource descriptor;
CopyNativeDescriptor(value, descriptor.fields);
const bool depth_compare = std::any_of(program.info.sampled_pairs.begin(),
program.info.sampled_pairs.end(), [&](const auto& pair) {
return pair.sampler == index &&
pair.image < program.info.images.size() &&
program.info.images[pair.image].depth_compare;
});
if (!depth_compare) {
auto descriptor = DecodeNativeDescriptor<ShaderSamplerResource>(value);
if (!program.info.samplers[index].depth_compare) {
descriptor.fields[0] &= ~(0x7u << 12u);
}
if (program.info.samplers[index].force_point_filtering) {
@@ -869,15 +870,10 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime);
const auto& program = *runtime.program;
const auto& snapshot = *runtime.resources;
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot)) {
EXIT("invalid native shader runtime snapshot: hash=0x%016" PRIx64 " stage=%u\n",
program.shader_hash, static_cast<unsigned>(program.stage));
}
const auto& snapshot = runtime.resources;
PreparedBindings prepared;
prepared.program = runtime.program.get();
prepared.snapshot = runtime.resources.get();
prepared.program = runtime.program;
prepared.snapshot = &runtime.resources;
auto& descriptors = prepared.resources;
descriptors.buffers.reserve(program.info.buffers.size());
descriptors.images.reserve(program.info.images.size());
@@ -890,17 +886,11 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
descriptors.samplers.push_back(NativeSampler(m_context, program, i, snapshot.samplers[i]));
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
nullptr) {
prepared.flattened_srt.assign(snapshot.flattened_srt.begin(), snapshot.flattened_srt.end());
}
prepared.user_data.reserve(program.bindings.ShaderDataDwords());
prepared.shader_data.reserve(program.bindings.ShaderDataDwords());
for (const auto reg: program.bindings.user_data_registers) {
prepared.user_data.push_back(snapshot.user_data[reg - program.user_data_base]);
prepared.shader_data.push_back(snapshot.user_data[reg - program.user_data_base]);
}
prepared.user_data.resize(program.bindings.ShaderDataDwords());
prepared.shader_data.resize(program.bindings.ShaderDataDwords());
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
descriptors.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
@@ -915,22 +905,21 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
const auto& snapshot = *prepared.snapshot;
auto& cache = m_context.GetBufferCache();
prepared.buffer_ids.clear();
prepared.buffer_ids.reserve(program.info.buffers.size());
prepared.buffer_sources.clear();
prepared.buffer_sources.reserve(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[i]);
const auto address = descriptor.Base48();
const auto stride = descriptor.Stride();
const auto records = descriptor.NumRecords();
EXIT_IF(stride != 0 && records > UINT64_MAX / stride);
const auto requested_size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || requested_size == 0) {
prepared.buffer_ids.emplace_back();
prepared.buffer_sources.emplace_back(descriptor, BufferId {});
continue;
}
const auto size = Libs::LibKernel::Memory::ClampRangeSize(address, requested_size);
prepared.buffer_ids.push_back(cache.FindBuffer(address, size));
prepared.buffer_sources.emplace_back(descriptor, cache.FindBuffer(address, size));
}
}
@@ -942,32 +931,33 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
const auto& snapshot = *prepared.snapshot;
auto& resources = prepared.resources;
const auto& layout = program.bindings;
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size());
EXIT_IF(prepared.buffer_sources.size() != program.info.buffers.size());
resources.buffers.clear();
resources.buffers.reserve(program.info.buffers.size());
EXIT_IF(prepared.user_data.size() != layout.ShaderDataDwords());
std::fill(prepared.user_data.begin() + layout.memory_offset_dword, prepared.user_data.end(), 0);
EXIT_IF(prepared.shader_data.size() != layout.ShaderDataDwords());
std::fill(prepared.shader_data.begin() + layout.memory_offset_dword,
prepared.shader_data.end(), 0);
auto pack_memory_offset = [&](uint32_t index, uint32_t offset) {
const auto dword = layout.memory_offset_dword + index / 4u;
const auto shift = (index % 4u) * 8u;
prepared.user_data[dword] |= offset << shift;
prepared.shader_data[dword] |= offset << shift;
};
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
const auto& [descriptor, buffer_id] = prepared.buffer_sources[i];
uint32_t buffer_offset = 0;
resources.buffers.push_back(NativeStorageBuffer(m_context, descriptor,
program.info.buffers[i], program.stage, i,
buffer_offset, prepared.buffer_ids[i]));
buffer_offset, buffer_id));
pack_memory_offset(i, buffer_offset);
}
if (!prepared.flattened_srt.empty()) {
resources.flattened_srt = NativeUpload(m_context, prepared.flattened_srt);
if (ShaderRecompiler::IR::FindBinding(
layout, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
resources.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
resources.user_data = NativeUpload(m_context, prepared.user_data);
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::ShaderData) != nullptr) {
resources.shader_data = NativeUpload(m_context, prepared.shader_data);
}
}
@@ -1056,14 +1046,13 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
auto vk_buffer = buffer.Handle();
size_t descriptor_count = 0;
size_t write_count = 0;
ShaderRecompiler::IR::PushData push_data;
bool has_push_data = false;
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
const auto push_constant_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
? vk::ShaderStageFlags {GraphicsStages}
: vk::ShaderStageFlags {vk::ShaderStageFlagBits::eCompute};
for (const auto* prepared: prepared_bindings) {
EXIT_IF(prepared == nullptr || prepared->program == nullptr ||
prepared->snapshot == nullptr || prepared->committed);
prepared->snapshot == nullptr);
write_count += prepared->program->bindings.descriptors.size();
for (const auto& binding: prepared->program->bindings.descriptors) {
descriptor_count += NativeDescriptorCount(binding);
@@ -1077,7 +1066,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
m_descriptor_buffers.clear();
m_descriptor_images.clear();
m_descriptor_writes.clear();
m_push_constants.fill(0);
m_descriptor_buffers.reserve(descriptor_count);
m_descriptor_images.reserve(descriptor_count);
m_descriptor_writes.reserve(write_count);
@@ -1167,13 +1155,13 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
break;
}
case BindingKind::FlattenedSrt:
case BindingKind::UserData:
case BindingKind::ShaderData:
case BindingKind::Gds: {
const BufferView* view = &descriptors.gds;
if (binding.kind == BindingKind::FlattenedSrt) {
view = &descriptors.flattened_srt;
} else if (binding.kind == BindingKind::UserData) {
view = &descriptors.user_data;
} else if (binding.kind == BindingKind::ShaderData) {
view = &descriptors.shader_data;
}
EXIT_IF(view->buffer == nullptr);
m_descriptor_buffers.emplace_back(view->buffer, view->offset, view->range);
@@ -1206,20 +1194,22 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
EXIT_IF(m_image_occurrences[i] != expected);
}
if (program.bindings.push_constant_size != 0) {
const auto offset = program.bindings.push_constant_offset;
EXIT_IF(offset % sizeof(uint32_t) != 0 ||
program.bindings.push_constant_size !=
prepared->user_data.size() * sizeof(uint32_t) ||
offset + program.bindings.push_constant_size >
ShaderRecompiler::IR::NativePushConstantSize);
std::copy(prepared->user_data.begin(), prepared->user_data.end(),
m_push_constants.begin() + offset / sizeof(uint32_t));
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
EXIT_IF(prepared->shader_data.size() != shader_data_dwords);
if (program.bindings.UsesPushData()) {
std::ranges::copy(prepared->shader_data,
push_data.dwords.begin() + program.bindings.push_data_start_dword);
has_push_data = true;
}
}
vk_buffer.pushConstants(pipeline.pipeline_layout, push_constant_stages, 0,
ShaderRecompiler::IR::NativePushConstantSize,
m_push_constants.data());
if (has_push_data) {
const auto stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
? vk::ShaderStageFlags {GraphicsStages}
: vk::ShaderStageFlags {vk::ShaderStageFlagBits::eCompute};
vk_buffer.pushConstants(pipeline.pipeline_layout, stages, 0, sizeof(push_data),
push_data.dwords.data());
}
if (!m_descriptor_writes.empty()) {
EXIT_IF(pipeline.descriptor_set_layout == nullptr);
@@ -1239,9 +1229,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
0, nullptr);
}
}
for (auto* prepared: prepared_bindings) {
prepared->committed = true;
}
}
} // namespace Libs::Graphics
@@ -40,17 +40,15 @@ struct NativeDescriptors {
std::vector<vk::Sampler> samplers;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
BufferView shader_data;
};
struct PreparedBindings {
const ShaderRecompiler::IR::Program* program = nullptr;
const ShaderRecompiler::IR::CompiledShaderInfo* program = nullptr;
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
NativeDescriptors resources;
std::vector<BufferId> buffer_ids;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
bool committed = false;
std::vector<std::pair<ShaderBufferResource, BufferId>> buffer_sources;
std::vector<uint32_t> shader_data;
};
[[nodiscard]] vk::DescriptorType
@@ -1,6 +1,7 @@
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
@@ -11,7 +12,9 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/shaderCompiler.h"
#include "kernel/memory.h"
#include "kytyGitVersion.h"
#include "loader/systemContent.h"
@@ -20,13 +23,14 @@
#include <cctype>
#include <cstdio>
#include <cstring>
#include <fmt/format.h>
#include <limits>
#include <span>
#include <spirv-tools/libspirv.hpp>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <xxhash.h>
namespace Libs::Graphics {
@@ -84,42 +88,188 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
static_params.scissor_ltrb[3] = 1;
}
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
return value != nullptr &&
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
}
void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
const std::vector<uint32_t>& spirv) {
if (!Config::GraphicsDebugDumpEnabled()) {
return;
}
static std::atomic_int id = 0;
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);
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());
return;
}
file.Write(spirv.data(), spirv.size() * sizeof(uint32_t));
}
void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
std::span<const uint32_t> code, const std::string& decoded_dump) {
if (!Config::GraphicsDebugDumpEnabled()) {
return;
}
EXIT_IF(code.empty());
static std::atomic_int id = 0;
const auto base = Config::GetShaderLogFolder() / "original" /
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, stage_name, shader_hash);
Common::File::CreateDirectories(base.parent_path());
for (const auto& [suffix, data, size]: {
std::tuple {".bin", static_cast<const void*>(code.data()), code.size_bytes()},
std::tuple {".rdna2", static_cast<const void*>(decoded_dump.data()),
decoded_dump.size()},
}) {
if (size == 0) {
continue;
}
auto path = base;
path += suffix;
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());
} else {
file.Write(data, size);
}
}
}
bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
const std::vector<uint32_t>& spirv) {
if (!Config::ShaderValidationEnabled()) {
return true;
}
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_3);
std::string messages;
tools.SetMessageConsumer([&messages](spv_message_level_t, const char*,
const spv_position_t& position, const char* message) {
messages += fmt::format("{}: {} ({}) {}\n", static_cast<int>(position.line),
static_cast<int>(position.column), static_cast<int>(position.index),
message);
});
if (tools.Validate(spirv)) {
return true;
}
spvtools::SpirvTools disassembler(SPV_ENV_VULKAN_1_2);
std::string text;
disassembler.Disassemble(spirv, &text,
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COMMENT) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR));
LOGF_COLOR(Log::Color::BrightRed, "%s SPIR-V validation failed hash=0x%016" PRIx64 ":\n%s",
label, shader_hash, messages.c_str());
LOGF("%s\n", text.c_str());
return false;
}
} // namespace
struct PipelineCache::ProgramCache {
struct SourceKey {
ShaderType stage = ShaderType::Unknown;
uint64_t hash = 0;
struct ProgramKey {
ShaderType stage = ShaderType::Unknown;
uint64_t hash = 0;
uint32_t user_data_count = 0;
uint32_t code_size = 0;
std::vector<uint32_t> static_state;
bool operator==(const SourceKey&) const = default;
bool operator==(const ProgramKey&) const = default;
};
struct Permutation {
std::vector<uint32_t> static_key;
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
ShaderProgram handle;
ShaderRecompiler::IR::ResourceSpecialization specialization;
ShaderRecompiler::IR::CompiledShaderInfo program;
ShaderProgram handle;
};
struct SourceKeyHash {
std::size_t operator()(const SourceKey& key) const {
struct SourceEntry {
explicit SourceEntry(ShaderRecompiler::IR::ResourcePlan plan)
: resource_plan(std::move(plan)) {
permutations.reserve(8);
}
ShaderRecompiler::IR::ResourcePlan resource_plan;
std::vector<Permutation> permutations;
};
struct ProgramKeyHash {
std::size_t operator()(const ProgramKey& key) const {
std::size_t hash = static_cast<std::size_t>(key.stage);
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash));
if constexpr (sizeof(std::size_t) < sizeof(uint64_t)) {
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash >> 32u));
}
PipelineKeyHash::Mix(hash, key.user_data_count);
PipelineKeyHash::Mix(hash, key.code_size);
PipelineKeyHash::Mix(hash, key.static_state.size());
// Bucket same-shape static variants by source. ProgramKey equality performs the one
// exact state comparison needed on a stable hit without hashing up to 429 words first.
return hash;
}
};
static uint64_t MixId(uint64_t hash, uint64_t value) {
return hash ^ (value + 0x9e3779b97f4a7c15ull + (hash << 6u) + (hash >> 2u));
static constexpr std::size_t MaxStaticKeyWords = 13 + ShaderVertexInputInfo::RES_MAX * 13;
template <ShaderType Stage>
Permutation CompilePermutation(const ShaderParams& params,
const ShaderRecompiler::CompileOptions& options,
ShaderRecompiler::TranslateResult translated,
ShaderRecompiler::IR::ResourceSpecialization specialization,
uint32_t push_data_start_dword) {
constexpr const char* stage_name = [] {
if constexpr (Stage == ShaderType::Vertex) {
return "vs";
} else if constexpr (Stage == ShaderType::Pixel) {
return "ps";
} else {
static_assert(Stage == ShaderType::Compute);
return "cs";
}
}();
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
specialization, push_data_start_dword);
DumpShaderOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) {
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label,
options.shader_hash);
}
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
vk::ShaderModuleCreateInfo create_info {};
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
create_info.pCode = result.spirv.data();
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
"create recompiled shader module");
EXIT_IF(module == nullptr);
if (options.dump_ir) {
if (!options.early_dump) {
LOGF("%s decoded RDNA2:\n%s", options.dump_label, result.decoded_dump.c_str());
LOGF("%s IR:\n%s", options.dump_label, result.ir_dump.c_str());
}
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
static_cast<uint64_t>(result.spirv.size()), options.wave_size);
}
return {
.specialization = std::move(specialization),
.program = std::move(result.program).TakeCompiledInfo(),
.handle = {.id = ++next_shader_id, .module = module},
};
}
static constexpr std::size_t MaxStaticKeyWords =
12 + ShaderVertexInputInfo::RES_MAX * 17;
template <typename InputInfo>
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info) {
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info,
uint32_t& push_data_cursor) {
constexpr ShaderType stage = [] {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
return ShaderType::Vertex;
@@ -131,36 +281,106 @@ struct PipelineCache::ProgramCache {
}
}();
BuildStageStaticKey(input_info, key_scratch);
auto& permutations = programs[{stage, params.hash}];
for (const auto& permutation: permutations) {
if (permutation.static_key == key_scratch &&
MaterializeProgram(permutation.program, params, input_info)) {
return permutation.handle;
lookup_key.stage = stage;
lookup_key.hash = params.hash;
lookup_key.user_data_count = static_cast<uint32_t>(params.user_data.size());
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
BuildStageStaticKey(input_info, lookup_key.static_state);
auto entry = programs.find(lookup_key);
ShaderRecompiler::IR::ResourceSnapshot resources;
ShaderRecompiler::IR::ResourceSpecialization specialization;
const ShaderRecompiler::IR::SrtRuntime runtime {
.user_data = params.user_data,
.shader_base = params.Base(),
.read_specialization_memory = ReadShaderGuestMemory,
};
if (entry != programs.end()) {
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
entry->second.resource_plan, runtime, resources, specialization));
if (const auto permutation = std::ranges::find_if(
entry->second.permutations, [&](const Permutation& candidate) {
const auto& layout = candidate.program.bindings;
return layout.push_data_start_dword ==
ShaderRecompiler::IR::PushData::StartFor(
push_data_cursor, layout.ShaderDataDwords()) &&
candidate.specialization == specialization;
});
permutation != entry->second.permutations.end()) {
input_info.stage = {.program = &permutation->program,
.resources = std::move(resources)};
permutation->program.bindings.AdvancePushData(push_data_cursor);
return permutation->handle;
}
}
const auto module = CompileProgram(device, params, input_info);
EXIT_IF(module == nullptr || !input_info.stage);
uint64_t id = MixId(MixId(params.hash, static_cast<uint64_t>(stage)), permutations.size());
if (id == 0) {
id = 1;
ShaderStageInputInfo stage_input {};
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
stage_input.vertex = &input_info;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
stage_input.pixel = &input_info;
} else {
stage_input.compute = &input_info;
}
const ShaderProgram handle {.id = id, .module = module};
permutations.push_back({key_scratch, input_info.stage.program, handle});
constexpr const char* label = [] {
if constexpr (stage == ShaderType::Vertex) {
return "ShaderRecompiler VS";
} else if constexpr (stage == ShaderType::Pixel) {
return "ShaderRecompiler PS";
} else {
return "ShaderRecompiler CS";
}
}();
ShaderRecompiler::CompileOptions options;
options.stage = stage;
options.shader_hash = params.hash;
options.user_data = params.user_data;
options.dump_ir = Config::GetShaderLogDirection() != Config::ShaderLogDirection::Silent;
options.early_dump = options.dump_ir;
options.dump_label = label;
options.input_info = stage_input;
if constexpr (stage == ShaderType::Vertex) {
options.user_data_base = 8;
options.scratch_dwords = input_info.scratch_size_dwords;
} else if constexpr (stage == ShaderType::Pixel) {
options.scratch_dwords = input_info.scratch_size_dwords;
} else {
options.scratch_dwords = input_info.scratch_size_dwords;
options.wave_size = input_info.wave_size;
}
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
if (entry == programs.end()) {
auto resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(resource_plan, runtime, resources,
specialization));
entry = programs.try_emplace(lookup_key, std::move(resource_plan)).first;
}
entry->second.permutations.push_back(CompilePermutation<stage>(
params, options, std::move(translated), std::move(specialization), push_data_cursor));
const auto& permutation = entry->second.permutations.back();
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
permutation.program.bindings.AdvancePushData(push_data_cursor);
std::printf("Num compiled %u shaders\n", ++num_compiled);
return handle;
return permutation.handle;
}
explicit ProgramCache(vk::Device device): device(device) {
key_scratch.reserve(MaxStaticKeyWords);
lookup_key.static_state.reserve(MaxStaticKeyWords);
}
~ProgramCache() {
for (const auto& [key, entry]: programs) {
(void)key;
for (const auto& permutation: entry.permutations) {
device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
}
std::unordered_map<SourceKey, std::vector<Permutation>, SourceKeyHash> programs;
std::vector<uint32_t> key_scratch;
vk::Device device;
uint32_t num_compiled = 0;
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
ProgramKey lookup_key;
vk::Device device;
uint32_t num_compiled = 0;
uint64_t next_shader_id = 0;
};
PipelineCache::PipelineCache(GraphicContext& graphics)
@@ -181,12 +401,6 @@ PipelineCache::~PipelineCache() {
};
destroy(m_graphics_pipelines);
destroy(m_compute_pipelines);
for (const auto& [key, permutations]: m_program_cache->programs) {
(void)key;
for (const auto& permutation: permutations) {
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
if (m_driver_cache != nullptr) {
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
}
@@ -212,7 +426,7 @@ void PipelineCache::InitializeDriverCache() {
return;
}
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
const auto path = Common::PathToString(m_driver_cache_path);
const bool cache_exists = Common::File::IsFileExisting(m_driver_cache_path);
if (cache_exists) {
@@ -224,7 +438,7 @@ void PipelineCache::InitializeDriverCache() {
if (cache_exists) {
Common::File file(m_driver_cache_path, Common::File::Mode::Read);
const auto file_size = file.IsInvalid() ? 0 : file.Size();
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
if (file_size >= signature.size() + sizeof(uint64_t) &&
file_size <= std::numeric_limits<uint32_t>::max()) {
std::string cached_signature(signature.size(), '\0');
@@ -236,7 +450,8 @@ void PipelineCache::InitializeDriverCache() {
file.Read(cached_signature.data(), static_cast<uint32_t>(cached_signature.size()),
&signature_read);
file.Read(&payload_hash, sizeof(payload_hash), &hash_read);
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()), &payload_read);
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()),
&payload_read);
file.Close();
if (signature_read != cached_signature.size() || hash_read != sizeof(payload_hash) ||
payload_read != initial_data.size() || cached_signature != signature ||
@@ -248,8 +463,7 @@ void PipelineCache::InitializeDriverCache() {
}
} else {
file.Close();
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)",
path);
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)", path);
}
}
@@ -308,7 +522,7 @@ void PipelineCache::Save() {
return;
}
payload.resize(size);
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
const auto payload_hash = XXH3_64bits(payload.data(), payload.size());
prefix.append(reinterpret_cast<const char*>(&payload_hash), sizeof(payload_hash));
if (!Common::File::CreateDirectories(m_driver_cache_path.parent_path())) {
@@ -338,15 +552,20 @@ void PipelineCache::Save() {
m_driver_cache = nullptr;
}
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
const HW::Context& context,
ShaderVertexInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, input_info);
PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
const HW::ShaderRegisters& sh, const HW::Context& context,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
bool pixel_active, ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) {
const auto vertex_params = PrepareProgram(vertex_regs, sh, vertex_info);
ShaderParams pixel_params;
if (pixel_active) {
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
}
if (context.GetClipControl().clip_disable) {
const auto& viewport = context.GetScreenViewport().viewports[0];
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
auto& clip = input_info.clip_space;
auto& clip = vertex_info.clip_space;
clip.scale[0] = viewport.xscale;
clip.scale[1] = viewport.yscale;
clip.offset[0] = viewport.xoffset;
@@ -358,26 +577,23 @@ ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
clip.enabled = true;
}
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
ShaderProgram PipelineCache::GetPixelProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, vertex_info, target_export_mapping, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
uint32_t push_data_cursor = 0;
GraphicsPrograms result;
if (pixel_active) {
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
}
result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor);
return result;
}
ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info) {
input_info.needs_lds_barriers = !m_graphics.compute_wave64_supported;
const auto params = PrepareProgram(regs, sh, input_info);
const auto params = PrepareProgram(regs, sh, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
uint32_t push_data_cursor = 0;
return m_program_cache->Get(params, input_info, push_data_cursor);
}
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
@@ -401,8 +617,7 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
Common::LockGuard lock(m_mutex);
auto& ctx = command.GetRegisters();
const HW::BlendColor& bclr = ctx.GetBlendColor();
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
color_mask[i] =
(colors[i].image_id ? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
@@ -506,11 +721,6 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
static_params.blend_enable[i] = bc.enable;
static_params.blend_bypass[i] = rt.info.blend_bypass;
}
static_params.blend_color_red = bclr.red;
static_params.blend_color_green = bclr.green;
static_params.blend_color_blue = bclr.blue;
static_params.blend_color_alpha = bclr.alpha;
NormalizeStaticParamsForDynamicState(static_params);
GraphicsPipelineKey key {};
@@ -518,6 +728,30 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
key.vs_shader_id = p.vs_shader_id;
key.ps_shader_id = p.ps_shader_id;
key.static_params = static_params;
EXIT_IF(vs_input_info.buffers_num < 0 ||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX ||
vs_input_info.resources_num < 0 ||
vs_input_info.resources_num > ShaderVertexInputInfo::RES_MAX);
key.vertex_input.binding_count = static_cast<uint8_t>(vs_input_info.buffers_num);
key.vertex_input.attribute_count = static_cast<uint8_t>(vs_input_info.resources_num);
uint32_t attributes_num = 0;
for (int binding = 0; binding < vs_input_info.buffers_num; binding++) {
const auto& buffer = vs_input_info.buffers[binding];
EXIT_IF(buffer.attr_num < 0 || buffer.attr_num > ShaderVertexInputBuffer::ATTR_MAX);
attributes_num += static_cast<uint32_t>(buffer.attr_num);
EXIT_IF(attributes_num > static_cast<uint32_t>(vs_input_info.resources_num));
key.vertex_input.bindings[binding] = {.stride = buffer.stride,
.instance = buffer.fetch_index != 0};
for (int attribute = 0; attribute < buffer.attr_num; attribute++) {
const auto index = buffer.attr_indices[attribute];
EXIT_IF(index < 0 || index >= vs_input_info.resources_num);
key.vertex_input.attributes[index] = {
.offset = buffer.attr_offsets[attribute],
.binding = static_cast<uint8_t>(binding),
};
}
}
EXIT_IF(attributes_num != static_cast<uint32_t>(vs_input_info.resources_num));
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
return *iter->second;
@@ -528,16 +762,16 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
if (ps_active) {
ShaderDbgDumpInputInfo(*ps_input_info);
}
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64
" module=%p\n",
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64 " module=%p\n",
vs_id, static_cast<void*>(vertex_program.module), ps_id,
static_cast<void*>(pixel_program.module));
}
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
ps_input_info, pixel_program.module, static_params, m_driver_cache);
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info,
vertex_program.module, ps_input_info, pixel_program.module,
static_params, m_driver_cache);
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
@@ -65,10 +65,6 @@ struct PipelineStaticParameters {
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_bypass[RENDER_COLOR_ATTACHMENTS_MAX] = {};
float blend_color_red = 0.0f;
float blend_color_green = 0.0f;
float blend_color_blue = 0.0f;
float blend_color_alpha = 0.0f;
bool operator==(const PipelineStaticParameters& other) const noexcept;
};
@@ -85,7 +81,7 @@ static_assert(sizeof(PipelineStaticParameters) ==
sizeof(bool) + sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
@@ -96,13 +92,31 @@ struct PipelineRenderingState {
bool operator==(const PipelineRenderingState&) const = default;
};
struct PipelineVertexInputState {
struct Binding {
uint32_t stride = 0;
bool instance = false;
bool operator==(const Binding&) const = default;
};
struct Attribute {
uint32_t offset = 0;
uint8_t binding = 0;
bool operator==(const Attribute&) const = default;
};
std::array<Binding, ShaderVertexInputInfo::RES_MAX> bindings {};
std::array<Attribute, ShaderVertexInputInfo::RES_MAX> attributes {};
uint8_t binding_count = 0;
uint8_t attribute_count = 0;
bool operator==(const PipelineVertexInputState&) const = default;
};
struct ShaderProgram {
uint64_t id = 0;
vk::ShaderModule module = nullptr;
explicit operator bool() const {
return id != 0 && module != nullptr;
}
explicit operator bool() const { return id != 0 && module != nullptr; }
};
class PipelineCache {
@@ -128,25 +142,26 @@ public:
uint64_t cs_shader_id = 0;
};
ShaderProgram GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
const HW::Context& context,
ShaderVertexInputInfo& input_info);
ShaderProgram
GetPixelProgram(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info);
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
struct GraphicsPrograms {
ShaderProgram vertex;
ShaderProgram pixel;
};
GraphicsPipeline& CreateGraphicsPipeline(
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program);
GraphicsPrograms GetGraphicsPrograms(
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
const HW::ShaderRegisters& sh, const HW::Context& context,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
bool pixel_active, ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info);
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
GraphicsPipeline&
CreateGraphicsPipeline(std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info,
vk::PrimitiveTopology topology, bool primitive_restart_enable,
const ShaderProgram& vertex_program, const ShaderProgram& pixel_program);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program);
@@ -157,11 +172,13 @@ private:
PipelineRenderingState rendering;
uint64_t vs_shader_id = 0;
uint64_t ps_shader_id = 0;
PipelineVertexInputState vertex_input;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
static_params == other.static_params;
}
};
@@ -202,6 +219,16 @@ private:
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::Mix(hash, key.vs_shader_id);
PipelineKeyHash::Mix(hash, key.ps_shader_id);
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].stride);
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].instance);
}
PipelineKeyHash::Mix(hash, key.vertex_input.attribute_count);
for (uint32_t i = 0; i < key.vertex_input.attribute_count; i++) {
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].offset);
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].binding);
}
PipelineKeyHash::MixStaticParams(hash, key.static_params);
return hash;
}
@@ -215,7 +242,7 @@ private:
}
};
GraphicContext& m_graphics;
GraphicContext& m_graphics;
std::unique_ptr<ProgramCache> m_program_cache;
vk::PipelineCache m_driver_cache = nullptr;
std::filesystem::path m_driver_cache_path;
@@ -232,13 +259,13 @@ private:
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache);
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, vk::ShaderModule vertex_module,
const ShaderPixelInputInfo* ps_input_info, vk::ShaderModule pixel_module,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
} // namespace Libs::Graphics
@@ -69,7 +69,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
}
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
CollectShaderBufferWrites(const ShaderRecompiler::IR::CompiledShaderInfo& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources) {
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
std::vector<ShaderBufferWriteRange> writes;
@@ -97,7 +97,7 @@ CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
EXIT_IF(!runtime);
return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty();
return !CollectShaderBufferWrites(*runtime.program, runtime.resources).empty();
}
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
@@ -23,7 +23,7 @@ VulkanMemoryBarrier MakeShaderWriteHazardDependency();
VulkanMemoryBarrier MakeShaderWriteDependency();
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
CollectShaderBufferWrites(const ShaderRecompiler::IR::CompiledShaderInfo& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources);
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
@@ -390,7 +390,7 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
}
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::CompiledShaderInfo& program,
vk::ShaderStageFlagBits stage) {
for (const auto& binding: program.bindings.descriptors) {
descriptor_bindings.push_back(
@@ -421,10 +421,10 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache) {
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, vk::ShaderModule vertex_module,
const ShaderPixelInputInfo* ps_input_info, vk::ShaderModule pixel_module,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) {
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == nullptr));
@@ -505,135 +505,131 @@ void CreatePipelineInternal(
shader_stages[shader_stage_count++] = frag_shader_stage_info;
}
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX];
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX];
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX] {};
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX] {};
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
const auto& b = vs_input_info.buffers[bi];
input_desc[bi].binding = bi;
input_desc[bi].stride = b.stride;
input_desc[bi].inputRate =
(b.fetch_index == 0 ? vk::VertexInputRate::eVertex : vk::VertexInputRate::eInstance);
for (uint32_t binding = 0; binding < vertex_input.binding_count; binding++) {
input_desc[binding].binding = binding;
input_desc[binding].stride = vertex_input.bindings[binding].stride;
input_desc[binding].inputRate = vertex_input.bindings[binding].instance
? vk::VertexInputRate::eInstance
: vk::VertexInputRate::eVertex;
}
for (uint32_t index = 0; index < vertex_input.attribute_count; index++) {
input_attr[index].binding = vertex_input.attributes[index].binding;
input_attr[index].location = index;
input_attr[index].offset = vertex_input.attributes[index].offset;
for (int ai = 0; ai < b.attr_num; ai++) {
auto index = b.attr_indices[ai];
input_attr[index].binding = bi;
input_attr[index].location = index;
input_attr[index].offset = b.attr_offsets[ai];
uint32_t attr_size = 4;
const auto registers_num = vs_input_info.resources_dst[index].registers_num;
const auto compiled_components =
vs_input_info.stage.program->info.vertex_fetch_components[index];
const auto used_components =
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
static_cast<uint32_t>(used_components));
uint32_t attr_size = 4;
auto registers_num = vs_input_info.resources_dst[index].registers_num;
const auto compiled_components =
vs_input_info.stage.program->info.vertex_fetch_components[index];
auto used_components =
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
static_cast<uint32_t>(used_components));
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
"src_fmt=%u dst=v%u regs=%u"
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
log_id, index, input_attr[index].binding, input_attr[index].offset,
input_desc[bi].stride, static_cast<int>(input_attr[index].format),
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
static_cast<uint32_t>(registers_num),
static_cast<uint32_t>(used_components), attr_size,
static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
}
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
"src_fmt=%u dst=v%u regs=%u"
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
log_id, index, input_attr[index].binding, input_attr[index].offset,
input_desc[input_attr[index].binding].stride,
static_cast<int>(input_attr[index].format),
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
static_cast<uint32_t>(registers_num), static_cast<uint32_t>(used_components),
attr_size, static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
}
}
if (vs_input_info.resources[index].OutOfBounds() != 0) {
static bool logged = false;
if (!logged) {
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8
"\n",
vs_input_info.resources[index].OutOfBounds());
logged = true;
}
if (vs_input_info.resources[index].OutOfBounds() != 0) {
static bool logged = false;
if (!logged) {
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8 "\n",
vs_input_info.resources[index].OutOfBounds());
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](
uint32_t swizzle, uint32_t expected) {
static bool logged = false;
if (!logged) {
LOGF(
"VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
" (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
", expected=0x%03" PRIx32 ")\n",
static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
logged = true;
}
};
switch (registers_num) {
case 1: {
auto swizzle = vs_input_info.resources[index].DstSelX();
if (swizzle != DstSel(4)) {
log_unsupported_vertex_swizzle(swizzle, DstSel(4));
}
break;
}
case 2: {
auto swizzle = vs_input_info.resources[index].DstSelXY();
auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 3: {
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
auto expected = DstSel(4, 5, 6);
switch (attr_size) {
case 1: expected = DstSel(4, 0, 0); break;
case 2: expected = DstSel(4, 5, 0); break;
default: break;
}
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 4: {
auto swizzle = vs_input_info.resources[index].DstSelXYZW();
auto expected = DstSel(4, 5, 6, 7);
bool supported = false;
switch (attr_size) {
case 1:
expected = DstSel(4, 0, 0, 1);
supported = (swizzle == expected);
break;
case 2:
expected = DstSel(4, 5, 0, 1);
supported = (swizzle == expected);
break;
case 3:
expected = DstSel(4, 5, 6, 1);
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
break;
default:
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
swizzle == DstSel(4, 5, 6, 0));
break;
}
if (!supported) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
default: EXIT("invalid registers_num");
auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](uint32_t swizzle,
uint32_t expected) {
static bool logged = false;
if (!logged) {
LOGF("VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
" (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
", expected=0x%03" PRIx32 ")\n",
static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
logged = true;
}
};
switch (registers_num) {
case 1: {
auto swizzle = vs_input_info.resources[index].DstSelX();
if (swizzle != DstSel(4)) {
log_unsupported_vertex_swizzle(swizzle, DstSel(4));
}
break;
}
case 2: {
auto swizzle = vs_input_info.resources[index].DstSelXY();
auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 3: {
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
auto expected = DstSel(4, 5, 6);
switch (attr_size) {
case 1: expected = DstSel(4, 0, 0); break;
case 2: expected = DstSel(4, 5, 0); break;
default: break;
}
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 4: {
auto swizzle = vs_input_info.resources[index].DstSelXYZW();
auto expected = DstSel(4, 5, 6, 7);
bool supported = false;
switch (attr_size) {
case 1:
expected = DstSel(4, 0, 0, 1);
supported = (swizzle == expected);
break;
case 2:
expected = DstSel(4, 5, 0, 1);
supported = (swizzle == expected);
break;
case 3:
expected = DstSel(4, 5, 6, 1);
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
break;
default:
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
swizzle == DstSel(4, 5, 6, 0));
break;
}
if (!supported) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
default: EXIT("invalid registers_num");
}
}
@@ -641,9 +637,9 @@ void CreatePipelineInternal(
vertex_input_info.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
vertex_input_info.pNext = nullptr;
vertex_input_info.flags = {};
vertex_input_info.vertexBindingDescriptionCount = vs_input_info.buffers_num;
vertex_input_info.vertexBindingDescriptionCount = vertex_input.binding_count;
vertex_input_info.pVertexBindingDescriptions = input_desc;
vertex_input_info.vertexAttributeDescriptionCount = vs_input_info.resources_num;
vertex_input_info.vertexAttributeDescriptionCount = vertex_input.attribute_count;
vertex_input_info.pVertexAttributeDescriptions = input_attr;
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
@@ -792,15 +788,11 @@ void CreatePipelineInternal(
#else
color_blending.pNext = &color_write;
#endif
color_blending.flags = {};
color_blending.logicOpEnable = VK_FALSE;
color_blending.logicOp = vk::LogicOp::eCopy;
color_blending.attachmentCount = static_params.color_count;
color_blending.pAttachments = color_blend_attachment;
color_blending.blendConstants[0] = static_params.blend_color_red;
color_blending.blendConstants[1] = static_params.blend_color_green;
color_blending.blendConstants[2] = static_params.blend_color_blue;
color_blending.blendConstants[3] = static_params.blend_color_alpha;
color_blending.flags = {};
color_blending.logicOpEnable = VK_FALSE;
color_blending.logicOp = vk::LogicOp::eCopy;
color_blending.attachmentCount = static_params.color_count;
color_blending.pAttachments = color_blend_attachment;
EXIT_IF(!vs_input_info.stage);
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
@@ -814,8 +806,8 @@ void CreatePipelineInternal(
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
const vk::PushConstantRange push_constants {
GraphicsStages, 0, ShaderRecompiler::IR::NativePushConstantSize};
const vk::PushConstantRange push_constants {GraphicsStages, 0,
ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
@@ -877,6 +869,7 @@ void CreatePipelineInternal(
vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
vk::DynamicState::eBlendConstants,
#if !defined(__APPLE__)
// Keep last so depth-only pipelines can omit this dynamic state.
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
+29 -9
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;
@@ -190,9 +211,8 @@ private:
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
std::vector<uint32_t> m_image_occurrences;
std::array<uint32_t, ShaderRecompiler::IR::NativePushConstantSize / sizeof(uint32_t)>
m_push_constants {};
friend class CommandProcessor;
friend struct RenderExecutorTestAccess;
};
@@ -46,7 +46,7 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
const auto& resources = input.stage.resources;
if (resources.buffers.size() != program.info.buffers.size()) {
EXIT("compute runtime buffer count does not match shader metadata\n");
}
@@ -81,7 +81,7 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
uint64_t& resolved_size) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
const auto& resources = input.stage.resources;
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
!program.info.images.empty() || !program.info.samplers.empty() || program.info.uses_dma ||
!resources.images.empty() || !resources.samplers.empty()) {
@@ -224,7 +224,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
const bool large_workgroup =
(input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512);
const auto& program = *input_info.stage.program;
const auto& resources = *input_info.stage.resources;
const auto& resources = input_info.stage.resources;
if (TryConsumeComputeMetaClear(input_info, buffer)) {
ResetBindings();
return;
@@ -249,7 +249,10 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
thread_group_z, mode, input_info.threads_num[0], input_info.threads_num[1],
input_info.threads_num[2], program.info.buffers.size(), program.info.images.size(),
sampled_images, program.info.images.size() - sampled_images,
program.info.samplers.size(), program.bindings.push_constant_size);
program.info.samplers.size(),
program.bindings.UsesPushData()
? static_cast<uint32_t>(sizeof(ShaderRecompiler::IR::PushData))
: 0u);
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& buffer = program.info.buffers[i];
const auto r = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
+103 -89
View File
@@ -52,7 +52,7 @@ int32_t ResolveVertexOffset(uint32_t index_offset, const ShaderVertexInputInfo&
EXIT_IF(!vs_input_info.stage);
const auto& program = *vs_input_info.stage.program;
const auto& resources = *vs_input_info.stage.resources;
const auto& resources = vs_input_info.stage.resources;
if (program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
const auto index =
static_cast<uint32_t>(program.info.vertex_offset_sgpr) - program.user_data_base;
@@ -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 {};
@@ -364,6 +386,9 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
line_width = 1.0f;
}
vk_buffer.setLineWidth(line_width);
const auto& blend = ctx.GetBlendColor();
const std::array blend_constants {blend.red, blend.green, blend.blue, blend.alpha};
vk_buffer.setBlendConstants(blend_constants.data());
const auto& mode = ctx.GetModeControl();
const auto& poly_offset = ctx.GetPolyOffset();
@@ -495,15 +520,13 @@ struct DrawRenderState {
RenderState rendering;
ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info;
ShaderProgram vertex_program;
ShaderProgram pixel_program;
PipelineCache::GraphicsPrograms programs;
};
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;
};
@@ -745,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 {
@@ -878,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,
@@ -991,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;
}
@@ -1008,30 +1032,20 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, bool
const auto& pixel_shader_info = sh_ctx.GetPs();
const auto& shader_regs = ctx.GetShaderRegisters();
state.vertex_program = {};
state.pixel_program = {};
state.ps_input_info = {};
state.programs = {};
state.ps_input_info = {};
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
target_export_mapping {};
for (uint32_t i = 0; i < state.color_count; i++) {
target_export_mapping[state.color_info[i].target_slot] = state.color_info[i].export_mapping;
}
if (log_phases) {
LogDrawPhase(draw.name, "GetVertexProgram");
}
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
state.vertex_program = pipeline_cache.GetVertexProgram(vertex_shader_info, shader_regs, ctx,
state.vs_input_info);
if (!state.ps_active) {
return;
}
if (log_phases) {
LogDrawPhase(draw.name, "GetPixelProgram");
LogDrawPhase(draw.name, "GetGraphicsPrograms");
}
state.pixel_program =
pipeline_cache.GetPixelProgram(pixel_shader_info, shader_regs, state.vs_input_info,
target_export_mapping, state.ps_input_info);
state.programs = pipeline_cache.GetGraphicsPrograms(
vertex_shader_info, pixel_shader_info, shader_regs, ctx, target_export_mapping,
state.ps_active, state.vs_input_info, state.ps_input_info);
}
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, CommandBuffer& buffer,
@@ -1110,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);
@@ -1132,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:
@@ -1144,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;
@@ -1185,7 +1199,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.vertex_program, state.pixel_program);
state.programs.vertex, state.programs.pixel);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1247,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;
}
@@ -1289,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);
@@ -1312,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 =
@@ -1357,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);
@@ -1379,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;
}
@@ -1417,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;
@@ -1452,7 +1460,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
if (rect_list && state.vs_input_info.buffers_num == 0 &&
state.vs_input_info.param_export_mask == 0 && state.ps_input_info.input_num != 0) {
state.vs_input_info.stage.program->param_export_mask == 0 &&
state.ps_input_info.input_num != 0) {
if (graphics_debug_dump_enabled()) {
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
"ps_inputs=%u ps=0x%016" PRIx64 " es=0x%016" PRIx64 " gs=0x%016" PRIx64 "\n",
@@ -1467,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();
@@ -34,9 +34,9 @@ const char* GetDumpLabel(const CompileOptions& options) {
return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler";
}
std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) {
std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) {
std::string dump = "CFG:\n";
dump += CFG::GraphToString(cfg);
dump += cfg;
dump += "\nIR:\n";
dump += fmt::format("mode={} scratch_dwords={}\n",
ir.dispatcher_fallback ? "dispatcher" : "structured", ir.scratch_dwords);
@@ -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);
}
@@ -464,7 +479,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
} // namespace
CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& options) {
TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOptions& options) {
if (code.empty()) {
EXIT("shader recompiler input is empty\n");
}
@@ -615,33 +630,46 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
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;
}
const IR::SrtRuntime runtime {
.user_data = options.user_data,
.shader_base = reinterpret_cast<uint64_t>(code.data()),
.read_memory = options.read_memory,
.userdata = options.read_memory_data,
.read_specialization_memory = options.read_specialization_memory,
};
IR::ResourceSnapshot resources;
if (!IR::MaterializeResources(ir, runtime, resources)) {
EXIT("shader resource materialization failed: stage=%s hash=0x%016" PRIx64 "\n",
StageName(options.stage), options.shader_hash);
TranslateResult result;
result.program = std::move(ir);
if (options.dump_ir) {
result.decoded_dump = std::move(decoded_dump);
result.cfg_dump = CFG::GraphToString(cfg);
}
return result;
}
CompileResult CompileProgram(TranslateResult translated, const CompileOptions& options,
const IR::ResourceSpecialization& specialization,
uint32_t push_data_start_dword) {
const auto emit_begin = std::chrono::steady_clock::now();
auto& ir = translated.program;
IR::ApplyResourceSpecialization(ir, specialization);
const ShaderVertexInputInfo* vertex = nullptr;
const ShaderPixelInputInfo* pixel = nullptr;
const ShaderComputeInputInfo* compute = nullptr;
switch (options.stage) {
case ShaderType::Vertex: vertex = options.input_info.vertex; break;
case ShaderType::Pixel: pixel = options.input_info.pixel; break;
case ShaderType::Compute: compute = options.input_info.compute; break;
default: EXIT("invalid shader stage\n");
}
IR::SpecializeResources(ir, resources);
IR::ShaderInfoOptions info_options;
info_options.vertex = vertex;
info_options.pixel = pixel;
info_options.compute = compute;
IR::CollectShaderInfo(ir, info_options);
IR::AllocateBindings(ir, options.push_constant_offset);
IR::AllocateBindings(ir, push_data_start_dword);
Spirv::AnalyzeProgramRequirements(ir);
std::string ir_dump;
if (options.dump_ir) {
ir_dump = MakeIrDump(cfg, ir);
ir_dump = MakeIrDump(translated.cfg_dump, ir);
if (options.early_dump) {
LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
}
@@ -649,17 +677,19 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
auto spirv = Spirv::EmitProgram(ir, resources, options.input_info);
auto spirv = Spirv::EmitProgram(ir, options.input_info);
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
" elapsed_ms=%" PRIu64 "\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
static_cast<uint64_t>(spirv.size()), phase_ms());
static_cast<uint64_t>(spirv.size()),
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - emit_begin)
.count()));
CompileResult result;
result.spirv = std::move(spirv);
result.program = std::move(ir);
result.resources = std::move(resources);
result.spirv = std::move(spirv);
result.program = std::move(ir);
if (options.dump_ir) {
result.decoded_dump = std::move(decoded_dump);
result.decoded_dump = std::move(translated.decoded_dump);
result.ir_dump = std::move(ir_dump);
}
return result;
@@ -17,27 +17,32 @@ struct CompileOptions {
uint32_t user_data_base = 0;
uint32_t scratch_dwords = 0;
uint64_t shader_hash = 0;
uint32_t push_constant_offset = 0;
bool dump_ir = true;
bool early_dump = false;
const char* dump_label = nullptr;
std::span<const uint32_t> user_data;
IR::SrtMemoryReader read_memory = nullptr;
IR::SrtMemoryReader read_specialization_memory = nullptr;
void* read_memory_data = nullptr;
ShaderStageInputInfo input_info;
};
struct CompileResult {
std::vector<uint32_t> spirv;
std::string decoded_dump;
std::string ir_dump;
IR::Program program;
IR::ResourceSnapshot resources;
struct TranslateResult {
IR::Program program;
std::string decoded_dump;
std::string cfg_dump;
};
[[nodiscard]] CompileResult Recompile(std::span<const uint32_t> code,
const CompileOptions& options);
struct CompileResult {
std::vector<uint32_t> spirv;
std::string decoded_dump;
std::string ir_dump;
IR::Program program;
};
[[nodiscard]] TranslateResult TranslateProgram(std::span<const uint32_t> code,
const CompileOptions& options);
[[nodiscard]] CompileResult CompileProgram(TranslateResult translated,
const CompileOptions& options,
const IR::ResourceSpecialization& specialization,
uint32_t push_data_start_dword = 0);
} // namespace Libs::Graphics::ShaderRecompiler
@@ -81,13 +81,13 @@ void ValidateNativeProgram(const IR::Program& program) {
const bool uses_flattened_runtime =
!program.srt_reads.empty() ||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
return image.indirect_mapping_capacity != 0u;
return image.indirect_search_iterations != 0u;
});
if (uses_flattened_runtime) {
Expect(Kind::FlattenedSrt);
}
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
Expect(Kind::UserData);
if (program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData()) {
Expect(Kind::ShaderData);
}
std::array<bool, KindCount> seen {};
@@ -104,21 +104,19 @@ void ValidateNativeProgram(const IR::Program& program) {
Fail(program, "native shader plan is missing a required descriptor group");
}
}
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
if (program.bindings.push_constant_offset % sizeof(uint32_t) != 0 ||
program.bindings.push_constant_offset + program.bindings.push_constant_size >
IR::NativePushConstantSize ||
const auto has_shader_data_storage = present[static_cast<size_t>(Kind::ShaderData)];
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
if ((program.bindings.UsesPushData() &&
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
program.bindings.memory_offset_count != program.info.buffers.size() ||
(!has_user_storage &&
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
(has_user_storage && program.bindings.push_constant_size != 0) ||
has_shader_data_storage != (shader_data_dwords != 0 && !program.bindings.UsesPushData()) ||
!std::is_sorted(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) ||
std::adjacent_find(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) !=
program.bindings.user_data_registers.end()) {
Fail(program, "native user-data layout is inconsistent");
Fail(program, "native shader-data layout is inconsistent");
}
const auto planning_only_handle = [&](const IR::Inst& handle) {
@@ -191,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;
@@ -302,7 +304,6 @@ void AnalyzeProgramRequirements(IR::Program& program) {
}
std::vector<uint32_t> EmitProgram(const IR::Program& program,
const IR::ResourceSnapshot& resources,
ShaderStageInputInfo input_info) {
using namespace Emitter;
@@ -315,15 +316,9 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
!program.spirv_requirements.has_value()) {
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
}
if (!IR::ValidateResourceSnapshot(program, resources)) {
Fail(program, "resource snapshot is inconsistent with the native shader plan");
}
if (!IR::ValidateResourceSpecialization(program, resources)) {
Fail(program, "resource specialization is inconsistent with the native shader plan");
}
ValidateNativeProgram(program);
IR::ValidateProgram(program, true);
EmitterState state(program, resources, input_info);
EmitterState state(program, input_info);
state.stage = program.stage;
state.wave_size = program.wave_size;
state.inputs.reserve(program.info.inputs.size());
@@ -3,7 +3,7 @@
#include "common/common.h"
#include "common/stringUtils.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <vector>
@@ -12,7 +12,6 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
void AnalyzeProgramRequirements(IR::Program& program);
std::vector<uint32_t> EmitProgram(const IR::Program& program,
const IR::ResourceSnapshot& resources,
ShaderStageInputInfo input_info);
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
@@ -158,7 +158,8 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
}
const auto& info = ImageDimensionInfoFor(image.dimension);
return state.builder.Type(OpTypeImage,
{ImageScalarType(state, image.numeric_class), info.spirv_dimension, 0,
{ImageScalarType(state, image.numeric_class), info.spirv_dimension,
image.depth_compare ? 1u : 0u,
info.arrayed, info.multisampled, sampled, format});
}
@@ -576,6 +576,7 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
switch (opcode) {
case IR::ValueOpcode::ImageAtomicSwap32: return OpAtomicExchange;
case IR::ValueOpcode::ImageAtomicIAdd32: return OpAtomicIAdd;
case IR::ValueOpcode::ImageAtomicUMin32: return OpAtomicUMin;
case IR::ValueOpcode::ImageAtomicUMax32: return OpAtomicUMax;
@@ -806,7 +807,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return true;
}
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
if (state.flattened_srt_variable == 0 || image.indirect_mapping_capacity == 0u ||
if (state.flattened_srt_variable == 0 || image.indirect_search_iterations == 0u ||
image.indirect_resources.size() < 2u) {
ctx.Fail(inst, "has no indirect image runtime mapping");
return true;
@@ -824,7 +825,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
auto low = ConstantU32(state, 0u);
auto high = LoadMapping(mapping);
auto selected = ConstantU32(state, 0u);
for (uint32_t iteration = 0; iteration < std::bit_width(image.indirect_mapping_capacity);
for (uint32_t iteration = 0; iteration < image.indirect_search_iterations;
iteration++) {
const auto active = Binary(state, OpULessThan, TypeBool(state), low, high);
const auto mid =
@@ -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,
@@ -341,19 +343,18 @@ constexpr std::array<ImageDimensionInfo, 7> ImageDimensions {{
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension);
struct EmitterState {
EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_,
ShaderStageInputInfo input_info_)
: program(program_), resources(resources_), input_info(input_info_),
EmitterState(const IR::Program& program_, ShaderStageInputInfo input_info_)
: program(program_), input_info(input_info_),
requirements(*program_.spirv_requirements) {}
Builder builder;
const IR::Program& program;
const IR::ResourceSnapshot& resources;
ShaderStageInputInfo input_info;
const IR::SpirvRequirements& requirements;
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;
@@ -361,7 +362,7 @@ struct EmitterState {
uint32_t gds_variable = 0;
uint32_t gds_length = 0;
uint32_t push_constant_variable = 0;
uint32_t vsharp_storage_variable = 0;
uint32_t shader_data_storage_variable = 0;
uint32_t flattened_srt_variable = 0;
uint32_t lds_variable = 0;
uint32_t scratch_variable = 0;
@@ -392,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);
@@ -405,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);
@@ -620,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);
@@ -634,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) {
@@ -4,6 +4,7 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
if (state.push_constant_variable != 0) {
dword_index += state.program.bindings.push_data_start_dword;
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, TypePushConstantElementPointer(state), pointer,
@@ -12,11 +13,11 @@ uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
return value;
}
if (state.vsharp_storage_variable != 0) {
if (state.shader_data_storage_variable != 0) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.vsharp_storage_variable, ConstantU32(state, 0),
state.shader_data_storage_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
return value;
@@ -80,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) {
@@ -112,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));
}
@@ -151,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));
}
@@ -119,8 +142,7 @@ uint32_t TypeU32ElementPointer(EmitterState& state, uint32_t storage_class) {
namespace {
uint32_t PushConstantArrayType(EmitterState& state) {
const auto count =
ConstantU32(state, state.program.bindings.push_constant_size / sizeof(uint32_t));
const auto count = ConstantU32(state, IR::PushData::DwordCount);
return state.builder.DecoratedType(OpTypeArray, {TypeU32(state), count},
{{OpDecorate, {DecorationArrayStride, sizeof(uint32_t)}}});
}
@@ -128,7 +150,7 @@ uint32_t PushConstantArrayType(EmitterState& state) {
uint32_t PushConstantBlockType(EmitterState& state) {
return state.builder.DecoratedType(
OpTypeStruct, {PushConstantArrayType(state)},
{{OpMemberDecorate, {0, DecorationOffset, state.program.bindings.push_constant_offset}},
{{OpMemberDecorate, {0, DecorationOffset, 0}},
{OpDecorate, {DecorationBlock}}});
}
@@ -163,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(
@@ -173,14 +202,14 @@ void DefineDescriptorVariables(EmitterState& state) {
state.fault_buffer_variable = state.builder.DefineGlobalVariable(
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
}
if (state.program.bindings.push_constant_size != 0) {
if (state.program.bindings.UsesPushData()) {
const auto pointer_type =
TypePointer(state, StorageClassPushConstant, PushConstantBlockType(state));
state.push_constant_variable =
state.builder.DefineGlobalVariable(pointer_type, StorageClassPushConstant);
}
if (DescriptorBinding(state, IR::DescriptorBindingKind::UserData) != nullptr) {
state.vsharp_storage_variable = state.builder.DefineGlobalVariable(
if (DescriptorBinding(state, IR::DescriptorBindingKind::ShaderData) != nullptr) {
state.shader_data_storage_variable = state.builder.DefineGlobalVariable(
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
}
if (DescriptorBinding(state, IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
@@ -571,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);
@@ -604,9 +641,9 @@ void AddVsharpAnnotationsAndNames(EmitterState& state) {
state.builder.AddName(PushConstantBlockType(state), "BufferResource");
state.builder.AddName(state.push_constant_variable, "vsharp");
}
if (state.vsharp_storage_variable != 0) {
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
IR::DescriptorBindingKind::UserData);
if (state.shader_data_storage_variable != 0) {
DecorateDescriptor(state, state.shader_data_storage_variable, "shader_data",
IR::DescriptorBindingKind::ShaderData);
}
}
@@ -628,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);
}
@@ -202,6 +202,7 @@ constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
};
constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = {
{0x0fu, "image_atomic_swap", Opcode::IMAGE_ATOMIC_SWAP},
{0x11u, "image_atomic_add", Opcode::IMAGE_ATOMIC_ADD},
{0x15u, "image_atomic_umin", Opcode::IMAGE_ATOMIC_UMIN},
{0x17u, "image_atomic_umax", Opcode::IMAGE_ATOMIC_UMAX},
@@ -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[] = {
@@ -535,6 +535,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_STORE:
case Opcode::IMAGE_STORE_MIP:
case Opcode::IMAGE_ATOMIC_SWAP:
case Opcode::IMAGE_ATOMIC_ADD:
case Opcode::IMAGE_ATOMIC_UMIN:
case Opcode::IMAGE_ATOMIC_UMAX:
@@ -591,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:
@@ -599,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,
@@ -538,6 +541,7 @@ enum class Opcode {
IMAGE_LOAD_MIP,
IMAGE_STORE,
IMAGE_STORE_MIP,
IMAGE_ATOMIC_SWAP,
IMAGE_ATOMIC_ADD,
IMAGE_ATOMIC_UMIN,
IMAGE_ATOMIC_UMAX,
@@ -223,7 +223,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
if (decoded.family == Decoder::Family::MIMG) {
const bool store_or_atomic = decoded.opcode == Decoder::Opcode::IMAGE_STORE ||
decoded.opcode == Decoder::Opcode::IMAGE_STORE_MIP ||
(decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_ADD &&
(decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_SWAP &&
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_XOR);
if (store_or_atomic) {
return index == 0u ? decoded.dst : decoded.src0;
@@ -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:
@@ -981,6 +1001,8 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::DS_WRXCHG_RTN_B32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicSwap32, true);
case Decoder::Opcode::IMAGE_ATOMIC_SWAP:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicSwap32);
case Decoder::Opcode::IMAGE_ATOMIC_ADD:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicIAdd32);
case Decoder::Opcode::IMAGE_ATOMIC_UMIN:
@@ -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 {
+36 -3
View File
@@ -54,7 +54,7 @@ bool IsRuntimeRead(ValueOpcode opcode) {
AddressOpcodeInfoOf(opcode).access == AddressAccess::Read;
}
bool EquivalentValue(const Program& program, Value left, Value right,
bool EquivalentValue(const ResourcePlan& program, Value left, Value right,
std::vector<std::pair<const Inst*, const Inst*>>& visited) {
left = left.Resolve();
right = right.Resolve();
@@ -97,6 +97,20 @@ bool EquivalentValue(const Program& program, Value left, Value right,
} // namespace
ResourcePlan::~ResourcePlan() {
for (auto& inst: value_storage) {
inst.Invalidate();
}
}
ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
if (this != &other) {
this->~ResourcePlan();
new (this) ResourcePlan(std::move(other));
}
return *this;
}
Program::~Program() {
// Values may cross block boundaries. Detach all arguments before any block starts destroying
// its instruction storage so reverse-use links always point to live definitions.
@@ -115,12 +129,31 @@ Program& Program::operator=(Program&& other) noexcept {
return *this;
}
bool EquivalentValue(const Program& program, Value left, Value right) {
CompiledShaderInfo Program::TakeCompiledInfo() && {
CompiledShaderInfo result {
.stage = stage,
.shader_hash = shader_hash,
.wave_size = wave_size,
.user_data_base = user_data_base,
.user_data_count = user_data_count,
.scratch_dwords = scratch_dwords,
.info = std::move(info),
.bindings = std::move(bindings),
};
for (const auto& output: result.info.outputs) {
if (output.kind == StageOutputKind::Parameter && output.index < 32) {
result.param_export_mask |= 1u << output.index;
}
}
return result;
}
bool EquivalentValue(const ResourcePlan& program, Value left, Value right) {
std::vector<std::pair<const Inst*, const Inst*>> visited;
return EquivalentValue(program, left, right, visited);
}
Value ResolveInvariantPhi(const Program& program, Value value) {
Value ResolveInvariantPhi(const ResourcePlan& program, Value value) {
value = value.Resolve();
const auto* root = value.TryInstruction();
if (root == nullptr || root->GetOpcode() != ValueOpcode::Phi) {
@@ -0,0 +1,29 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCESNAPSHOT_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCESNAPSHOT_H_
#include <array>
#include <cstdint>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR {
struct DescriptorValue {
std::array<uint32_t, 8> dwords = {};
uint32_t dword_count = 0;
bool operator==(const DescriptorValue& other) const {
return dword_count == other.dword_count && dwords == other.dwords;
}
};
struct ResourceSnapshot {
std::vector<DescriptorValue> buffers;
std::vector<DescriptorValue> images;
std::vector<DescriptorValue> samplers;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
};
} // namespace Libs::Graphics::ShaderRecompiler::IR
#endif // EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCESNAPSHOT_H_
+104 -47
View File
@@ -8,11 +8,13 @@
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/ir/Block.h"
#include "graphics/shader/recompiler/ir/ResourceSnapshot.h"
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.h"
#include "graphics/shader/shader.h"
#include <array>
#include <bit>
#include <list>
#include <memory>
#include <optional>
#include <string_view>
@@ -87,15 +89,6 @@ struct ExportInfo {
bool operator==(const ExportInfo& other) const = default;
};
struct DescriptorValue {
std::array<uint32_t, 8> dwords = {};
uint32_t dword_count = 0;
bool operator==(const DescriptorValue& other) const {
return dword_count == other.dword_count && dwords == other.dwords;
}
};
struct BufferResource {
static constexpr uint32_t NoImageAlias = UINT32_MAX;
@@ -139,7 +132,7 @@ struct ImageResource {
bool r128 = false;
uint32_t indirect_root = NoIndirectImage;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_mapping_capacity = 0;
uint32_t indirect_search_iterations = 0;
std::vector<uint32_t> indirect_resources;
bool operator==(const ImageResource& other) const = default;
@@ -149,6 +142,7 @@ struct SamplerResource {
uint32_t source = 0;
uint32_t first_use_pc = 0;
bool force_point_filtering = false;
bool depth_compare = false;
bool operator==(const SamplerResource& other) const = default;
};
@@ -259,9 +253,10 @@ struct StageOutput {
bool operator==(const StageOutput& other) const = default;
};
inline constexpr uint32_t FirstImageBinding = 1u;
inline constexpr uint32_t FirstStorageImageBinding = 22u;
inline constexpr uint32_t ImageBindingCount = 36u;
inline constexpr uint32_t FirstImageBinding = 1u;
inline constexpr uint32_t FirstDepthComparisonImageBinding = 22u;
inline constexpr uint32_t FirstStorageImageBinding = 29u;
inline constexpr uint32_t ImageBindingCount = 43u;
enum class DescriptorBindingKind : uint32_t {
Buffers = 0u,
@@ -270,14 +265,28 @@ enum class DescriptorBindingKind : uint32_t {
BdaPagetable,
FaultBuffer,
FlattenedSrt,
UserData,
ShaderData,
Count,
};
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 37u);
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 43u);
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 44u);
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 50u);
constexpr uint32_t NativePushConstantSize = 128;
struct PushData {
static constexpr uint32_t DwordCount = 32;
static constexpr uint32_t NoStart = UINT32_MAX;
std::array<uint32_t, DwordCount> dwords {};
[[nodiscard]] static constexpr bool CanFit(uint32_t start, uint32_t size) {
return size != 0 && start <= DwordCount && size <= DwordCount - start;
}
[[nodiscard]] static constexpr uint32_t StartFor(uint32_t cursor, uint32_t size) {
return CanFit(cursor, size) ? cursor : NoStart;
}
};
static_assert(sizeof(PushData) == 128);
constexpr uint32_t NativePushConstantSize = sizeof(PushData);
[[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) {
return static_cast<uint32_t>(kind) +
@@ -305,9 +314,10 @@ DescriptorBindingForImage(const ImageResource& image) {
constexpr uint32_t SampledFloatBinding = 1u;
constexpr uint32_t SampledUintBinding = 8u;
constexpr uint32_t SampledSintBinding = 15u;
constexpr uint32_t StorageFloatBinding = 22u;
constexpr uint32_t StorageUintBinding = 27u;
constexpr uint32_t AtomicUintBinding = 32u;
constexpr uint32_t SampledDepthBinding = FirstDepthComparisonImageBinding;
constexpr uint32_t StorageFloatBinding = FirstStorageImageBinding;
constexpr uint32_t StorageUintBinding = 34u;
constexpr uint32_t AtomicUintBinding = 39u;
uint32_t base = 0;
bool sampled = false;
@@ -316,12 +326,19 @@ DescriptorBindingForImage(const ImageResource& image) {
return std::nullopt;
}
sampled = true;
switch (image.numeric_class) {
case Prospero::TextureNumericClass::Float: base = SampledFloatBinding; break;
case Prospero::TextureNumericClass::Uint: base = SampledUintBinding; break;
case Prospero::TextureNumericClass::Sint: base = SampledSintBinding; break;
case Prospero::TextureNumericClass::Unsupported: return std::nullopt;
default: return std::nullopt;
if (image.depth_compare) {
if (image.numeric_class != Prospero::TextureNumericClass::Float) {
return std::nullopt;
}
base = SampledDepthBinding;
} else {
switch (image.numeric_class) {
case Prospero::TextureNumericClass::Float: base = SampledFloatBinding; break;
case Prospero::TextureNumericClass::Uint: base = SampledUintBinding; break;
case Prospero::TextureNumericClass::Sint: base = SampledSintBinding; break;
case Prospero::TextureNumericClass::Unsupported: return std::nullopt;
default: return std::nullopt;
}
}
} else if (image.resource_class == ImageResourceClass::Storage) {
if (image.atomic) {
@@ -375,8 +392,7 @@ struct DescriptorBinding {
};
struct BindingLayout {
uint32_t push_constant_offset = 0;
uint32_t push_constant_size = 0;
uint32_t push_data_start_dword = PushData::NoStart;
uint32_t memory_offset_dword = 0;
uint32_t memory_offset_count = 0;
std::vector<uint32_t> user_data_registers;
@@ -385,6 +401,14 @@ struct BindingLayout {
[[nodiscard]] uint32_t ShaderDataDwords() const {
return memory_offset_dword + (memory_offset_count + 3u) / 4u;
}
[[nodiscard]] bool UsesPushData() const {
return push_data_start_dword != PushData::NoStart;
}
void AdvancePushData(uint32_t& cursor) const {
if (UsesPushData()) {
cursor = push_data_start_dword + ShaderDataDwords();
}
}
bool operator==(const BindingLayout& other) const = default;
};
@@ -403,6 +427,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;
@@ -418,6 +443,7 @@ struct SpirvRequirements {
bool function_lds = false;
bool function_scratch = false;
bool pixel_valid_mask = false;
bool buffer_int64_atomics = false;
};
struct BlockInfo {
@@ -454,7 +480,47 @@ struct SrtRead {
bool operator==(const SrtRead& other) const = default;
};
struct Program {
// Stable shader metadata consumed by the renderer after native IR has been discarded.
struct CompiledShaderInfo {
ShaderType stage = ShaderType::Unknown;
uint64_t shader_hash = 0;
uint32_t wave_size = 64;
uint32_t user_data_base = 0;
uint32_t user_data_count = 64;
uint32_t scratch_dwords = 0;
uint32_t param_export_mask = 0;
ShaderInfo info;
BindingLayout bindings;
};
// Immutable runtime resource analysis retained by the shader cache. It owns only the native
// value graph reachable from descriptors/SRT reads, rather than the translated shader CFG.
struct ResourcePlan {
ResourcePlan() = default;
~ResourcePlan();
ResourcePlan(const ResourcePlan&) = delete;
ResourcePlan& operator=(const ResourcePlan&) = delete;
ResourcePlan(ResourcePlan&&) noexcept = default;
ResourcePlan& operator=(ResourcePlan&& other) noexcept;
ShaderType stage = ShaderType::Unknown;
uint64_t shader_hash = 0;
uint32_t user_data_base = 0;
uint32_t user_data_count = 64;
std::list<Inst> value_storage;
std::vector<MemoryInfo> memory_info;
std::vector<DescriptorSource> descriptor_sources;
std::vector<uint32_t> materialization_sources;
std::vector<SrtRead> srt_reads;
std::vector<uint8_t> clean_flat_slots;
bool requires_specialization_memory = false;
bool srt_plan_complete = false;
bool resource_tracking_complete = false;
ShaderInfo info;
};
struct Program: ResourcePlan {
Program() = default;
~Program();
@@ -462,31 +528,22 @@ struct Program {
Program& operator=(const Program&) = delete;
Program(Program&&) noexcept = default;
Program& operator=(Program&& other) noexcept;
CompiledShaderInfo TakeCompiledInfo() &&;
ShaderType stage = ShaderType::Unknown;
uint64_t shader_hash = 0;
uint32_t wave_size = 64;
uint32_t user_data_base = 0;
uint32_t user_data_count = 64;
uint32_t scratch_dwords = 0;
std::vector<std::unique_ptr<Block>> block_storage;
BlockList blocks;
uint32_t wave_size = 64;
uint32_t scratch_dwords = 0;
bool dispatcher_fallback = false;
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
std::string fallback_reason;
std::vector<std::unique_ptr<Block>> block_storage;
BlockList blocks;
std::vector<BlockInfo> block_info;
std::vector<BlockInfo> block_info;
// Decoded MIMG/VMEM metadata carries details such as RDNA2 NSA address registers and
// storage-image swizzles. Typed memory instructions carry a dense index into these shader-local
// tables until those fields are consumed by emission.
std::vector<MemoryInfo> memory_info;
std::vector<ExportInfo> export_info;
std::vector<DescriptorSource> descriptor_sources;
std::vector<SrtRead> srt_reads;
std::vector<Value> dynamic_reads;
bool srt_plan_complete = false;
ShaderInfo info;
bool resource_tracking_complete = false;
bool shader_info_complete = false;
bool shader_info_complete = false;
BindingLayout bindings;
bool binding_layout_complete = false;
@@ -497,8 +554,8 @@ std::string ProgramToString(const Program& program);
void ValidateProgram(const Program& program, bool require_ssa);
void ResolveControlFlowIdentities(Program& program);
bool EquivalentValue(const Program& program, Value left, Value right);
Value ResolveInvariantPhi(const Program& program, Value value);
bool EquivalentValue(const ResourcePlan& program, Value left, Value right);
Value ResolveInvariantPhi(const ResourcePlan& program, Value value);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -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:
@@ -204,6 +209,7 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) {
return {ImageAccess::Read, ImageResourceClass::Sampled, true};
case ValueOpcode::ImageWrite:
return {ImageAccess::Write, ImageResourceClass::Storage, false};
case ValueOpcode::ImageAtomicSwap32:
case ValueOpcode::ImageAtomicIAdd32:
case ValueOpcode::ImageAtomicUMin32:
case ValueOpcode::ImageAtomicUMax32:
@@ -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)
@@ -268,6 +271,7 @@ VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicIAdd32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMin32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1)
@@ -69,24 +69,18 @@ bool UsesGds(const Program& program) {
} // namespace
void AllocateBindings(Program& program, uint32_t push_constant_offset) {
void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
if (!program.shader_info_complete || program.binding_layout_complete) {
EXIT("shader binding layout failed: %s", !program.shader_info_complete
? "shader info is not ready"
: "binding layout already allocated");
}
if (push_constant_offset > NativePushConstantSize ||
push_constant_offset % sizeof(uint32_t) != 0) {
EXIT("shader binding layout failed: push-constant offset %u exceeds the Vulkan minimum "
"guarantee or is unaligned",
push_constant_offset);
}
BindingLayout next;
next.push_constant_offset = push_constant_offset;
next.user_data_registers = CollectUserData(program);
next.memory_offset_dword = static_cast<uint32_t>(next.user_data_registers.size());
next.memory_offset_count = static_cast<uint32_t>(program.info.buffers.size());
next.push_data_start_dword =
PushData::StartFor(push_data_start_dword, next.ShaderDataDwords());
if (!program.info.buffers.empty()) {
std::vector<uint32_t> resources(program.info.buffers.size());
@@ -139,17 +133,14 @@ void AllocateBindings(Program& program, uint32_t push_constant_offset) {
const bool uses_flattened_runtime =
!program.srt_reads.empty() ||
std::ranges::any_of(program.info.images, [](const ImageResource& image) {
return image.indirect_mapping_capacity != 0u;
return image.indirect_search_iterations != 0u;
});
if (uses_flattened_runtime) {
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
}
const auto push_dwords = (NativePushConstantSize - push_constant_offset) / sizeof(uint32_t);
if (next.ShaderDataDwords() <= push_dwords) {
next.push_constant_size = next.ShaderDataDwords() * sizeof(uint32_t);
} else {
AddBinding(next, DescriptorBindingKind::UserData);
if (next.ShaderDataDwords() != 0 && !next.UsesPushData()) {
AddBinding(next, DescriptorBindingKind::ShaderData);
}
program.bindings = std::move(next);
@@ -5,7 +5,7 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
void AllocateBindings(Program& program, uint32_t push_constant_offset);
void AllocateBindings(Program& program, uint32_t push_data_start_dword = 0);
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
@@ -7,6 +7,8 @@
#include <algorithm>
#include <array>
#include <bit>
#include <cstdio>
#include <cstring>
#include <fmt/format.h>
#include <functional>
@@ -19,9 +21,22 @@ namespace {
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
constexpr uint64_t MaxIndirectImageProbes = 65536u;
[[noreturn]] void SpecializationFail(std::string_view message) {
EXIT("shader resource specialization failed: %s", std::string(message).c_str());
std::abort();
struct IndirectImage {
uint32_t resource = 0;
std::vector<uint32_t> keys;
std::vector<uint32_t> candidates;
std::vector<DescriptorValue> descriptors;
};
struct MaterializedSnapshot {
ResourceSnapshot resources;
std::vector<IndirectImage> indirect_images;
};
bool SpecializationFail(std::string_view message) {
std::fprintf(stderr, "shader resource specialization failed: %.*s\n",
static_cast<int>(message.size()), message.data());
return false;
}
Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor,
@@ -94,6 +109,11 @@ bool RequiresPointSampler(const ImageResource& image) {
image.conversion_format != Prospero::BufferFormat::kInvalid;
}
bool RequiresPointSampler(const ResourceSpecialization::Image& image) {
return image.numeric_class == Prospero::TextureNumericClass::Sint ||
image.conversion_format != Prospero::BufferFormat::kInvalid;
}
bool DescriptorIsCube(const DescriptorValue& descriptor) {
return static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu) ==
Prospero::ImageType::kCube;
@@ -116,14 +136,14 @@ bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResou
return true;
}
const DescriptorSource* Source(const Program& program, uint32_t source) {
const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
if (source >= program.descriptor_sources.size()) {
return nullptr;
}
return &program.descriptor_sources[source];
}
void MarkCleanFlatSlots(const Program& program, const DescriptorSource* source,
void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* source,
std::span<uint8_t> slots) {
if (source == nullptr) {
return;
@@ -186,8 +206,8 @@ bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynam
bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
const DescriptorValue& material_value,
const DescriptorValue& heap_value, const SrtRuntime& runtime,
ResourceSnapshot::IndirectImage& result) {
const DescriptorValue& heap_value, bool r128,
const SrtRuntime& runtime, IndirectImage& result) {
ShaderBufferResource material;
ShaderBufferResource heap;
if (!DecodeBufferDescriptor(material_value, material) ||
@@ -212,6 +232,8 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
std::vector<uint32_t> keys {0u};
std::unordered_set<uint32_t> seen {0u};
keys.reserve(static_cast<size_t>(probe_count) + 1u);
seen.reserve(static_cast<size_t>(probe_count) + 1u);
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
uint32_t key = 0;
if (!ReadScalarBufferWord(material, static_cast<uint32_t>(offset), 0u, runtime, key)) {
@@ -225,9 +247,11 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
}
}
ResourceSnapshot::IndirectImage next;
next.capacity = static_cast<uint32_t>(probe_count + 1u);
next.keys = std::move(keys);
IndirectImage next;
next.keys = std::move(keys);
next.candidates.reserve(next.keys.size());
next.descriptors.reserve(
std::min(next.keys.size(), static_cast<size_t>(ShaderInfo::MaxImages)));
for (const auto key: next.keys) {
DescriptorValue candidate;
candidate.dword_count = 8u;
@@ -238,11 +262,14 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
return false;
}
}
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate)) {
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, r128)) {
candidate.dwords.fill(0);
}
const auto found = std::ranges::find(next.descriptors, candidate);
if (found == next.descriptors.end()) {
if (next.descriptors.size() >= ShaderInfo::MaxImages) {
return false;
}
next.descriptors.push_back(candidate);
next.candidates.push_back(static_cast<uint32_t>(next.descriptors.size() - 1u));
} else {
@@ -253,339 +280,122 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
return true;
}
size_t FlattenedRuntimeDwords(const Program& program) {
size_t size = program.srt_reads.size();
for (uint32_t resource = 0; resource < program.info.images.size(); resource++) {
const auto& image = program.info.images[resource];
if (image.indirect_root == resource) {
size = std::max(size, static_cast<size_t>(image.indirect_mapping_offset) + 1u +
static_cast<size_t>(image.indirect_mapping_capacity) * 2u);
}
}
return size;
}
} // namespace
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot) {
if (!program.resource_tracking_complete) {
return false;
}
if (snapshot.buffers.size() != program.info.buffers.size() ||
snapshot.images.size() != program.info.images.size() ||
snapshot.samplers.size() != program.info.samplers.size()) {
return false;
}
if (snapshot.flattened_srt.size() != FlattenedRuntimeDwords(program)) {
return false;
}
if (program.binding_layout_complete) {
for (const auto reg: program.bindings.user_data_registers) {
if (reg < program.user_data_base ||
reg - program.user_data_base >= snapshot.user_data.size()) {
return false;
}
}
}
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto alias = program.info.buffers[i].image_alias;
if (alias != BufferResource::NoImageAlias && alias >= program.info.images.size()) {
return false;
}
}
const auto CheckWidth = [&](const auto& values, uint32_t width) {
for (uint32_t i = 0; i < values.size(); i++) {
if (values[i].dword_count != width) {
return false;
}
}
return true;
};
std::unordered_set<uint32_t> indirect_resources;
for (const auto& table: snapshot.indirect_images) {
const auto* source = table.resource < program.info.images.size()
? Source(program, program.info.images[table.resource].source)
: nullptr;
if (source == nullptr || !source->indirect_image.has_value() ||
program.info.images[table.resource].indirect_root != ImageResource::NoIndirectImage ||
table.keys.empty() || table.keys.size() != table.candidates.size() ||
table.capacity < table.keys.size() || table.descriptors.empty() ||
!indirect_resources.insert(table.resource).second) {
return false;
}
for (const auto candidate: table.candidates) {
if (candidate >= table.descriptors.size()) {
return false;
}
}
if (snapshot.images[table.resource] != table.descriptors[table.candidates[0]]) {
return false;
}
}
return CheckWidth(snapshot.buffers, 4) && CheckWidth(snapshot.images, 8) &&
CheckWidth(snapshot.samplers, 4);
}
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot) {
if (!ValidateResourceSnapshot(program, snapshot)) {
return false;
}
if (!snapshot.indirect_images.empty()) {
return false;
}
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& buffer = program.info.buffers[i];
ShaderBufferResource descriptor;
if (!DecodeBufferDescriptor(snapshot.buffers[i], descriptor)) {
return false;
}
if (buffer.packed_stride != descriptor.PackedStride() ||
buffer.descriptor_format != descriptor.Format() ||
buffer.descriptor_swizzle != descriptor.DstSelXYZW()) {
return false;
}
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto& image = program.info.images[i];
const auto& descriptor = snapshot.images[i];
const auto mip_count = StorageMipCount(image, descriptor);
if (mip_count == 0u || mip_count != image.mip_count) {
return false;
}
if (NullImageDescriptor(descriptor)) {
if (image.indirect_root != ImageResource::NoIndirectImage) {
const auto root = image.indirect_root;
if (root >= program.info.images.size() ||
program.info.images[root].resource_class != image.resource_class ||
program.info.images[root].numeric_class != image.numeric_class ||
program.info.images[root].dimension != image.dimension ||
program.info.images[root].mip_mode != image.mip_mode ||
program.info.images[root].mip_count != image.mip_count ||
program.info.images[root].conversion_format != image.conversion_format ||
program.info.images[root].shader_swizzle != image.shader_swizzle ||
program.info.images[root].cube != image.cube) {
return false;
}
continue;
}
const auto canonical_numeric = image.atomic ? Prospero::TextureNumericClass::Uint
: Prospero::TextureNumericClass::Float;
if (image.dimension != Decoder::ImageDimension::Dim2D || image.cube ||
image.resource_class == ImageResourceClass::None ||
image.numeric_class != canonical_numeric) {
return false;
}
continue;
}
const auto dimension = DescriptorDimension(descriptor, image.dimension);
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension ||
DescriptorIsCube(descriptor) != image.cube) {
return false;
}
if (image.resource_class == ImageResourceClass::None) {
return false;
}
const bool storage = image.resource_class == ImageResourceClass::Storage;
const auto format =
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
return false;
}
const auto conversion_format = ImageConversionFormat(format);
if (image.conversion_format != conversion_format) {
return false;
}
if ((storage || conversion_format != Prospero::BufferFormat::kInvalid) &&
image.shader_swizzle != DescriptorImageSwizzle(descriptor)) {
return false;
}
auto numeric_class = Prospero::SampledTextureNumericClass(format);
if (storage) {
const bool raw_sint = format == Prospero::BufferFormat::k32SInt && image.written &&
!image.read && !image.atomic;
if ((!raw_sint && numeric_class == Prospero::TextureNumericClass::Sint) ||
numeric_class == Prospero::TextureNumericClass::Unsupported) {
return false;
}
if (raw_sint) {
numeric_class = Prospero::TextureNumericClass::Uint;
}
} else if (image.depth_compare && numeric_class != Prospero::TextureNumericClass::Float) {
return false;
}
if (image.numeric_class != numeric_class) {
return false;
}
}
return true;
}
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
ResourceSnapshot& snapshot) {
static bool MaterializeSnapshot(const ResourcePlan& program, const SrtRuntime& runtime,
MaterializedSnapshot& snapshot) {
if (!program.resource_tracking_complete) {
return false;
}
std::vector<DescriptorSourceRequest> requests;
std::vector<uint8_t> clean_flat_slots(program.srt_reads.size());
requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u +
program.info.samplers.size());
for (const auto& buffer: program.info.buffers) {
requests.push_back({buffer.source});
}
for (const auto& image: program.info.images) {
const auto* source = Source(program, image.source);
if (source != nullptr && source->indirect_image.has_value()) {
if (runtime.read_specialization_memory == nullptr) {
return false;
}
MarkCleanFlatSlots(program, Source(program, source->indirect_image->material_source),
clean_flat_slots);
MarkCleanFlatSlots(program, Source(program, source->indirect_image->heap_source),
clean_flat_slots);
} else {
requests.push_back({image.source});
}
}
for (const auto& sampler: program.info.samplers) {
requests.push_back({sampler.source});
if (program.requires_specialization_memory && runtime.read_specialization_memory == nullptr) {
return false;
}
std::vector<DescriptorValue> values;
std::vector<uint32_t> flattened_srt;
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt,
clean_flat_slots)) {
if (!EvaluateRuntimeSources(program, program.materialization_sources, runtime, values,
flattened_srt, program.clean_flat_slots)) {
return false;
}
ResourceSnapshot next;
auto cursor = values.begin();
auto& next = snapshot.resources;
auto cursor = values.begin();
next.buffers.assign(cursor, cursor + program.info.buffers.size());
cursor += program.info.buffers.size();
for (auto& descriptor: next.buffers) {
ShaderBufferResource buffer;
if (DecodeBufferDescriptor(descriptor, buffer) && buffer.Type() != 0) {
descriptor.dwords.fill(0);
}
}
next.flattened_srt = std::move(flattened_srt);
next.flattened_srt.resize(FlattenedRuntimeDwords(program));
next.images.resize(program.info.images.size());
std::vector<uint8_t> image_written(program.info.images.size());
for (uint32_t image_index = 0; image_index < program.info.images.size(); image_index++) {
const auto& image = program.info.images[image_index];
const auto* source = Source(program, image.source);
if (source != nullptr && source->indirect_image.has_value()) {
if (image.indirect_root != ImageResource::NoIndirectImage &&
image.indirect_root != image_index) {
continue;
}
const std::array requests {
DescriptorSourceRequest {source->indirect_image->material_source},
DescriptorSourceRequest {source->indirect_image->heap_source}};
SrtRuntime clean_runtime = runtime;
clean_runtime.read_memory = runtime.read_specialization_memory;
const std::array requests {source->indirect_image->material_source,
source->indirect_image->heap_source};
SrtRuntime clean_runtime = runtime;
clean_runtime.read_memory = runtime.read_specialization_memory;
std::vector<DescriptorValue> tables;
if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables)) {
return false;
}
const auto& material = tables[0];
const auto& heap = tables[1];
ResourceSnapshot::IndirectImage table;
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime,
table)) {
const auto& material = tables[0];
const auto& heap = tables[1];
IndirectImage table;
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, image.r128,
runtime, table)) {
return false;
}
if (image.indirect_root == ImageResource::NoIndirectImage) {
next.images[image_index] = table.descriptors[table.candidates[0]];
image_written[image_index] = 1u;
if (table.descriptors.size() > 1u) {
table.resource = image_index;
next.indirect_images.push_back(std::move(table));
}
continue;
}
if (table.keys.size() > image.indirect_mapping_capacity ||
table.descriptors.size() > image.indirect_resources.size()) {
return false;
}
for (uint32_t candidate = 0; candidate < image.indirect_resources.size(); candidate++) {
const auto resource = image.indirect_resources[candidate];
if (resource >= program.info.images.size() ||
program.info.images[resource].indirect_root != image_index) {
return false;
}
next.images[resource] =
table.descriptors[candidate < table.descriptors.size() ? candidate : 0u];
image_written[resource] = 1u;
}
std::vector<uint32_t> order(table.keys.size());
std::iota(order.begin(), order.end(), 0u);
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
const auto mapping = image.indirect_mapping_offset;
next.flattened_srt[mapping] = static_cast<uint32_t>(table.keys.size());
for (uint32_t entry = 0; entry < order.size(); entry++) {
const auto source = order[entry];
const auto offset = mapping + 1u + entry * 2u;
next.flattened_srt[offset] = table.keys[source];
next.flattened_srt[offset + 1] = table.candidates[source];
next.images[image_index] = table.descriptors[table.candidates[0]];
if (table.descriptors.size() > 1u) {
table.resource = image_index;
snapshot.indirect_images.push_back(std::move(table));
}
} else {
auto descriptor = *cursor++;
if (!ValidImageDescriptor(descriptor, image.r128)) {
descriptor.dwords.fill(0);
}
next.images[image_index] = descriptor;
image_written[image_index] = 1u;
next.images[image_index] = descriptor;
}
}
if (std::ranges::find(image_written, uint8_t {0}) != image_written.end()) {
return false;
}
next.samplers.assign(cursor, cursor + program.info.samplers.size());
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
if (!ValidateResourceSnapshot(program, next)) {
return false;
}
snapshot = std::move(next);
return true;
}
void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
if (!program.resource_tracking_complete || program.shader_info_complete ||
program.binding_layout_complete) {
return SpecializationFail(!program.resource_tracking_complete
? "shader resources were not tracked"
: "resource specialization is too late");
}
if (!ValidateResourceSnapshot(program, snapshot)) {
return SpecializationFail("invalid resource snapshot");
}
struct SamplerPlan {
std::array<uint32_t, ShaderInfo::MaxSamplers> point_sampler {};
uint32_t sampler_count = 0;
};
auto next = program.info;
auto next_snapshot = snapshot;
template <typename Images>
bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan);
static bool BuildResourceSpecialization(const ResourcePlan& program, MaterializedSnapshot snapshot,
ResourceSnapshot& specialized_snapshot,
ResourceSpecialization& specialization) {
auto next_snapshot = std::move(snapshot.resources);
ResourceSpecialization next_specialization;
next_specialization.buffers.reserve(program.info.buffers.size());
size_t image_count = program.info.images.size();
size_t mapping_words = 0;
for (const auto& table: snapshot.indirect_images) {
if (table.resource >= next.images.size() || table.descriptors.size() < 2u ||
next.images.size() + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
if (table.resource >= program.info.images.size() || table.descriptors.size() < 2u ||
image_count + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
return SpecializationFail(
"indirect image candidates exceed the dense image resource limit");
}
const auto root_image = next.images[table.resource];
std::vector<uint32_t> resources(table.descriptors.size());
resources[0] = table.resource;
image_count += table.descriptors.size() - 1u;
mapping_words += 1u + table.keys.size() * 2u;
}
next_snapshot.images.reserve(image_count);
next_snapshot.flattened_srt.reserve(next_snapshot.flattened_srt.size() + mapping_words);
next_specialization.images.reserve(image_count);
for (const auto& image: program.info.images) {
next_specialization.images.push_back({
.numeric_class = image.numeric_class,
.dimension = image.dimension,
.mip_count = image.mip_count,
.conversion_format = image.conversion_format,
.shader_swizzle = image.shader_swizzle,
.indirect_root = image.indirect_root,
.indirect_mapping_offset = image.indirect_mapping_offset,
.indirect_search_iterations = image.indirect_search_iterations,
.cube = image.cube,
});
}
for (const auto& table: snapshot.indirect_images) {
const auto root_image = next_specialization.images[table.resource];
for (uint32_t candidate = 1; candidate < table.descriptors.size(); candidate++) {
resources[candidate] = static_cast<uint32_t>(next.images.size());
auto image = root_image;
image.indirect_root = table.resource;
next.images.push_back(std::move(image));
auto image = root_image;
image.indirect_root = table.resource;
next_specialization.images.push_back(image);
next_snapshot.images.push_back(table.descriptors[candidate]);
}
auto& root = next.images[table.resource];
root.indirect_root = table.resource;
root.indirect_mapping_offset = static_cast<uint32_t>(next_snapshot.flattened_srt.size());
root.indirect_mapping_capacity = table.capacity;
root.indirect_resources = std::move(resources);
auto& root = next_specialization.images[table.resource];
root.indirect_root = table.resource;
root.indirect_mapping_offset = static_cast<uint32_t>(next_snapshot.flattened_srt.size());
root.indirect_search_iterations = std::bit_width(table.keys.size());
next_snapshot.flattened_srt.resize(next_snapshot.flattened_srt.size() + 1u +
static_cast<size_t>(table.capacity) * 2u);
table.keys.size() * 2u);
std::vector<uint32_t> order(table.keys.size());
std::iota(order.begin(), order.end(), 0u);
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
@@ -599,36 +409,58 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
}
next_snapshot.images[table.resource] = table.descriptors[0];
}
next_snapshot.indirect_images.clear();
for (uint32_t i = 0; i < next.buffers.size(); i++) {
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
auto& descriptor_value = next_snapshot.buffers[i];
ShaderBufferResource descriptor;
if (!DecodeBufferDescriptor(next_snapshot.buffers[i], descriptor)) {
if (!DecodeBufferDescriptor(descriptor_value, descriptor)) {
return SpecializationFail(fmt::format("buffer descriptor {} has invalid width", i));
}
next.buffers[i].packed_stride = descriptor.PackedStride();
next.buffers[i].descriptor_format = descriptor.Format();
next.buffers[i].descriptor_swizzle = descriptor.DstSelXYZW();
if (descriptor.Type() != 0) {
descriptor_value.dwords.fill(0);
descriptor = {};
}
auto packed_stride = descriptor.PackedStride();
const auto stride = packed_stride & 0x3fffu;
const bool swizzle = stride != 0u && ((packed_stride >> 14u) & 1u) != 0u;
if (stride == 0u) {
packed_stride &= ~((1u << 14u) | (3u << 16u));
} else if (!swizzle) {
packed_stride &= ~(3u << 16u);
}
next_specialization.buffers.push_back({
.packed_stride = packed_stride,
.descriptor_format = program.info.buffers[i].formatted
? descriptor.Format()
: Prospero::BufferFormat::kInvalid,
.descriptor_swizzle =
program.info.buffers[i].formatted ? descriptor.DstSelXYZW() : DstSel(4, 5, 6, 7),
});
}
for (uint32_t i = 0; i < next.images.size(); i++) {
for (uint32_t i = 0; i < next_specialization.images.size(); i++) {
const auto& descriptor = next_snapshot.images[i];
auto& image = next.images[i];
if (image.resource_class == ImageResourceClass::None ||
(image.atomic && image.resource_class != ImageResourceClass::Storage)) {
auto& image = next_specialization.images[i];
const auto base_index = i < program.info.images.size() ? i : image.indirect_root;
if (base_index >= program.info.images.size()) {
return SpecializationFail(fmt::format("image resource {} has an invalid root", i));
}
const auto& base = program.info.images[base_index];
if (base.resource_class == ImageResourceClass::None ||
(base.atomic && base.resource_class != ImageResourceClass::Storage)) {
return SpecializationFail(fmt::format("image resource {} has an invalid class", i));
}
image.mip_count = StorageMipCount(image, descriptor);
image.mip_count = StorageMipCount(base, descriptor);
if (image.mip_count == 0u) {
return SpecializationFail(
fmt::format("storage image descriptor {} has an invalid mip range", i));
}
if (NullImageDescriptor(descriptor)) {
image.numeric_class = image.atomic ? Prospero::TextureNumericClass::Uint
: Prospero::TextureNumericClass::Float;
image.numeric_class = base.atomic ? Prospero::TextureNumericClass::Uint
: Prospero::TextureNumericClass::Float;
image.dimension = Decoder::ImageDimension::Dim2D;
image.cube = false;
continue;
}
const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension);
const auto descriptor_dimension = DescriptorDimension(descriptor, base.dimension);
if (descriptor_dimension == Decoder::ImageDimension::Unknown) {
return SpecializationFail(fmt::format(
"image descriptor {} has unsupported type {}: {:08x},{:08x},{:08x},{:08x},"
@@ -641,19 +473,19 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
image.cube = DescriptorIsCube(descriptor);
const auto format =
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
if (base.atomic && format != Prospero::BufferFormat::k32UInt) {
return SpecializationFail(
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
}
const bool storage = image.resource_class == ImageResourceClass::Storage;
const bool storage = base.resource_class == ImageResourceClass::Storage;
image.conversion_format = ImageConversionFormat(format);
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
}
const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt &&
image.written && !image.read && !image.atomic;
image.numeric_class = Prospero::SampledTextureNumericClass(format);
base.written && !base.read && !base.atomic;
image.numeric_class = Prospero::SampledTextureNumericClass(format);
if (storage) {
if ((!raw_sint_storage && image.numeric_class == Prospero::TextureNumericClass::Sint) ||
image.numeric_class == Prospero::TextureNumericClass::Unsupported) {
@@ -665,46 +497,49 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
image.numeric_class = Prospero::TextureNumericClass::Uint;
}
} else if (image.numeric_class == Prospero::TextureNumericClass::Unsupported ||
(image.depth_compare &&
(base.depth_compare &&
image.numeric_class != Prospero::TextureNumericClass::Float)) {
return SpecializationFail(
fmt::format("sampled image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
}
}
for (uint32_t root_index = 0; root_index < next.images.size(); root_index++) {
auto& root = next.images[root_index];
for (uint32_t root_index = 0; root_index < next_specialization.images.size(); root_index++) {
auto& root = next_specialization.images[root_index];
if (root.indirect_root != root_index) {
continue;
}
if (root.indirect_mapping_capacity == 0u || root.indirect_resources.size() < 2u ||
const auto key_count = root.indirect_mapping_offset < next_snapshot.flattened_srt.size()
? next_snapshot.flattened_srt[root.indirect_mapping_offset]
: 0u;
if (root.indirect_search_iterations == 0u || key_count < 2u ||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
static_cast<size_t>(root.indirect_mapping_capacity) * 2u >
static_cast<size_t>(key_count) * 2u >
next_snapshot.flattened_srt.size()) {
return SpecializationFail("indirect image specialization has an invalid key mapping");
}
uint32_t exemplar = ImageResource::NoIndirectImage;
for (const auto resource: root.indirect_resources) {
if (resource >= next.images.size() ||
next.images[resource].indirect_root != root_index) {
return SpecializationFail("indirect image specialization has an invalid candidate");
uint32_t exemplar = ImageResource::NoIndirectImage;
uint32_t resource_count = 0;
for (uint32_t resource = 0; resource < next_specialization.images.size(); resource++) {
if (next_specialization.images[resource].indirect_root != root_index) {
continue;
}
if (!NullImageDescriptor(next_snapshot.images[resource])) {
resource_count++;
if (exemplar == ImageResource::NoIndirectImage &&
!NullImageDescriptor(next_snapshot.images[resource])) {
exemplar = resource;
break;
}
}
if (exemplar == ImageResource::NoIndirectImage) {
if (resource_count < 2u || exemplar == ImageResource::NoIndirectImage) {
return SpecializationFail("indirect image specialization has no typed candidate");
}
const auto& image_class = next.images[exemplar];
for (uint32_t candidate = 0; candidate < next.images.size(); candidate++) {
auto& image = next.images[candidate];
const auto& image_class = next_specialization.images[exemplar];
for (uint32_t candidate = 0; candidate < next_specialization.images.size(); candidate++) {
auto& image = next_specialization.images[candidate];
if (image.indirect_root != root_index) {
continue;
}
if (NullImageDescriptor(next_snapshot.images[candidate])) {
image.resource_class = image_class.resource_class;
image.numeric_class = image_class.numeric_class;
image.dimension = image_class.dimension;
image.mip_count = image_class.mip_count;
@@ -712,65 +547,225 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
image.shader_swizzle = image_class.shader_swizzle;
image.cube = image_class.cube;
}
if (image.resource_class != image_class.resource_class ||
image.numeric_class != image_class.numeric_class ||
if (image.numeric_class != image_class.numeric_class ||
image.dimension != image_class.dimension ||
image.mip_mode != image_class.mip_mode ||
image.mip_count != image_class.mip_count ||
image.conversion_format != image_class.conversion_format ||
image.shader_swizzle != image_class.shader_swizzle ||
image.depth_compare != image_class.depth_compare ||
image.cube != image_class.cube) {
return SpecializationFail(
fmt::format("indirect image table at pc 0x{:08x} has incompatible candidates",
root.first_use_pc));
program.info.images[root_index].first_use_pc));
}
}
}
auto memory_info = program.memory_info;
SamplerPlan sampler_plan;
if (!BuildSamplerPlan(program.info, next_specialization.images, sampler_plan)) {
return SpecializationFail("specialized sampler layout exceeds its resource limit");
}
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
const auto target = sampler_plan.point_sampler[index];
if (target != UINT32_MAX && target >= program.info.samplers.size()) {
next_snapshot.samplers.push_back(next_snapshot.samplers[index]);
}
}
specialization = std::move(next_specialization);
specialized_snapshot = std::move(next_snapshot);
return true;
}
struct SamplerUsage {
bool native = false;
bool point_only = false;
};
std::vector<SamplerUsage> sampler_usage(next.samplers.size());
for (const auto& pair: next.sampled_pairs) {
if (pair.image >= next.images.size() || pair.sampler >= next.samplers.size()) {
return SpecializationFail(
fmt::format("sampled resource pair at pc 0x{:08x} is invalid", pair.first_use_pc));
template <typename Images>
bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan) {
if (base.samplers.size() > plan.point_sampler.size()) {
return false;
}
std::array<uint8_t, ShaderInfo::MaxSamplers> usage {};
plan.point_sampler.fill(UINT32_MAX);
plan.sampler_count = static_cast<uint32_t>(base.samplers.size());
for (const auto& pair: base.sampled_pairs) {
if (pair.image >= images.size() || pair.sampler >= base.samplers.size()) {
return false;
}
auto& usage = sampler_usage[pair.sampler];
if (RequiresPointSampler(next.images[pair.image])) {
usage.point_only = true;
usage[pair.sampler] |= RequiresPointSampler(images[pair.image]) ? 2u : 1u;
}
for (uint32_t index = 0; index < base.samplers.size(); index++) {
if ((usage[index] & 2u) == 0u) {
continue;
}
if ((usage[index] & 1u) == 0u) {
plan.point_sampler[index] = index;
} else {
usage.native = true;
if (plan.sampler_count >= ShaderInfo::MaxSamplers) {
return false;
}
plan.point_sampler[index] = plan.sampler_count++;
}
}
std::vector<uint32_t> point_sampler(next.samplers.size(), UINT32_MAX);
for (uint32_t i = 0; i < sampler_usage.size(); i++) {
if (!sampler_usage[i].point_only) {
return true;
}
ResourcePlan ExtractResourcePlan(const Program& program) {
ResourcePlan plan;
plan.stage = program.stage;
plan.shader_hash = program.shader_hash;
plan.user_data_base = program.user_data_base;
plan.user_data_count = program.user_data_count;
plan.info = program.info;
plan.memory_info = program.memory_info;
plan.srt_plan_complete = program.srt_plan_complete;
plan.resource_tracking_complete = program.resource_tracking_complete;
std::unordered_map<const Inst*, Inst*> cloned;
std::function<Value(Value)> Clone = [&](Value value) -> Value {
value = value.Resolve();
const auto* source = value.TryInstruction();
if (source == nullptr) {
return value;
}
if (source->GetOpcode() == ValueOpcode::Phi) {
const auto invariant = ResolveInvariantPhi(program, value);
if (!invariant.IsEmpty() && invariant != value) {
return Clone(invariant);
}
}
if (const auto found = cloned.find(source); found != cloned.end()) {
return Value(found->second);
}
auto& target =
plan.value_storage.emplace_back(source->GetOpcode(), source->Flags<uint64_t>());
cloned.emplace(source, &target);
if (source->GetOpcode() == ValueOpcode::Phi) {
for (size_t index = 0; index < source->NumArgs(); index++) {
target.AddPhiOperand(nullptr, Clone(source->Arg(index)));
}
} else {
for (size_t index = 0; index < source->NumArgs(); index++) {
target.SetArg(index, Clone(source->Arg(index)));
}
}
return Value(&target);
};
plan.descriptor_sources.reserve(program.descriptor_sources.size());
for (const auto& source: program.descriptor_sources) {
auto& target = plan.descriptor_sources.emplace_back();
target.dword_count = source.dword_count;
target.indirect_image = source.indirect_image;
for (uint32_t dword = 0; dword < source.dword_count; dword++) {
target.dwords[dword] = Clone(source.dwords[dword]);
}
}
plan.srt_reads.reserve(program.srt_reads.size());
for (const auto& read: program.srt_reads) {
plan.srt_reads.push_back({Clone(read.value), read.flat_offset});
}
plan.materialization_sources.reserve(plan.info.buffers.size() + plan.info.images.size() +
plan.info.samplers.size());
for (const auto& buffer: plan.info.buffers) {
plan.materialization_sources.push_back(buffer.source);
}
for (const auto& image: plan.info.images) {
const auto* source = Source(plan, image.source);
if (source != nullptr && source->indirect_image.has_value()) {
plan.requires_specialization_memory = true;
} else {
plan.materialization_sources.push_back(image.source);
}
}
for (const auto& sampler: plan.info.samplers) {
plan.materialization_sources.push_back(sampler.source);
}
plan.clean_flat_slots.resize(plan.srt_reads.size());
for (const auto& image: plan.info.images) {
const auto* source = Source(plan, image.source);
if (source == nullptr || !source->indirect_image.has_value()) {
continue;
}
if (!sampler_usage[i].native) {
next.samplers[i].force_point_filtering = true;
point_sampler[i] = i;
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->material_source),
plan.clean_flat_slots);
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->heap_source),
plan.clean_flat_slots);
}
return plan;
}
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
ResourceSnapshot& snapshot, ResourceSpecialization& specialization) {
MaterializedSnapshot materialized;
if (!MaterializeSnapshot(program, runtime, materialized)) {
return false;
}
return BuildResourceSpecialization(program, std::move(materialized), snapshot, specialization);
}
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete ||
program.binding_layout_complete);
EXIT_IF(program.info.buffers.size() != specialization.buffers.size() ||
program.info.images.size() > specialization.images.size());
auto buffers = program.info.buffers;
for (size_t index = 0; index < buffers.size(); index++) {
buffers[index].packed_stride = specialization.buffers[index].packed_stride;
buffers[index].descriptor_format = specialization.buffers[index].descriptor_format;
buffers[index].descriptor_swizzle = specialization.buffers[index].descriptor_swizzle;
}
auto images = program.info.images;
images.reserve(specialization.images.size());
for (uint32_t index = 0; index < specialization.images.size(); index++) {
const auto& source = specialization.images[index];
if (index >= images.size()) {
EXIT_IF(source.indirect_root >= program.info.images.size());
images.push_back(program.info.images[source.indirect_root]);
}
auto& image = images[index];
image.numeric_class = source.numeric_class;
image.dimension = source.dimension;
image.mip_count = source.mip_count;
image.conversion_format = source.conversion_format;
image.shader_swizzle = source.shader_swizzle;
image.indirect_root = source.indirect_root;
image.indirect_mapping_offset = source.indirect_mapping_offset;
image.indirect_search_iterations = source.indirect_search_iterations;
image.cube = source.cube;
image.indirect_resources.clear();
}
for (uint32_t index = 0; index < images.size(); index++) {
const auto root = images[index].indirect_root;
if (root != ImageResource::NoIndirectImage) {
EXIT_IF(root >= images.size());
images[root].indirect_resources.push_back(index);
}
}
SamplerPlan sampler_plan;
EXIT_IF(!BuildSamplerPlan(program.info, images, sampler_plan));
auto samplers = program.info.samplers;
auto sampled_pairs = program.info.sampled_pairs;
samplers.reserve(sampler_plan.sampler_count);
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
const auto target = sampler_plan.point_sampler[index];
if (target == UINT32_MAX) {
continue;
}
if (next.samplers.size() >= ShaderInfo::MaxSamplers) {
return SpecializationFail(
"point-filter sampler variants exceed the dense sampler resource limit");
}
point_sampler[i] = static_cast<uint32_t>(next.samplers.size());
auto sampler = next.samplers[i];
sampler.force_point_filtering = true;
next.samplers.push_back(sampler);
next_snapshot.samplers.push_back(next_snapshot.samplers[i]);
}
for (auto& pair: next.sampled_pairs) {
if (RequiresPointSampler(next.images[pair.image])) {
pair.sampler = point_sampler[pair.sampler];
if (target == index) {
samplers[index].force_point_filtering = true;
} else {
EXIT_IF(target != samplers.size());
auto sampler = samplers[index];
sampler.force_point_filtering = true;
samplers.push_back(std::move(sampler));
}
}
for (auto& pair: sampled_pairs) {
if (RequiresPointSampler(images[pair.image])) {
EXIT_IF(sampler_plan.point_sampler[pair.sampler] == UINT32_MAX);
pair.sampler = sampler_plan.point_sampler[pair.sampler];
}
samplers[pair.sampler].depth_compare |= images[pair.image].depth_compare;
}
auto memory_info = program.memory_info;
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
const auto image_opcode = ImageOpcodeInfoOf(inst.GetOpcode());
@@ -778,31 +773,24 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
continue;
}
const auto index = inst.Flags<MemoryFlags>().index;
if (index >= memory_info.size()) {
return SpecializationFail(
fmt::format("typed image instruction has invalid memory metadata {}", index));
}
EXIT_IF(index >= memory_info.size());
auto& memory = memory_info[index];
if (memory.resource >= next.images.size()) {
return SpecializationFail(fmt::format(
"typed image instruction has invalid resource {}", memory.resource));
}
const auto& image = next.images[memory.resource];
EXIT_IF(memory.resource >= images.size());
const auto& image = images[memory.resource];
if (image_opcode.needs_sampler && RequiresPointSampler(image) &&
memory.sampler < point_sampler.size()) {
memory.sampler = point_sampler[memory.sampler];
}
if (image.indirect_root == memory.resource &&
inst.GetOpcode() != ValueOpcode::ImageSampleRaw) {
return SpecializationFail(
fmt::format("indirect image table at pc 0x{:08x} is used by unsupported {}",
inst.Flags<MemoryFlags>().pc, ValueOpcodeName(inst.GetOpcode())));
memory.sampler < program.info.samplers.size()) {
EXIT_IF(sampler_plan.point_sampler[memory.sampler] == UINT32_MAX);
memory.sampler = sampler_plan.point_sampler[memory.sampler];
}
EXIT_IF(image.indirect_root == memory.resource &&
inst.GetOpcode() != ValueOpcode::ImageSampleRaw);
}
}
program.info = std::move(next);
program.memory_info = std::move(memory_info);
snapshot = std::move(next_snapshot);
program.info.buffers = std::move(buffers);
program.info.images = std::move(images);
program.info.samplers = std::move(samplers);
program.info.sampled_pairs = std::move(sampled_pairs);
program.memory_info = std::move(memory_info);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -5,34 +5,46 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
struct ResourceSnapshot {
struct IndirectImage {
uint32_t resource = 0;
uint32_t capacity = 0;
std::vector<uint32_t> keys;
std::vector<uint32_t> candidates;
std::vector<DescriptorValue> descriptors;
// Canonical module-affecting resource state. Runtime addresses and descriptor payloads remain in
// ResourceSnapshot and therefore do not create shader permutations.
struct ResourceSpecialization {
struct Buffer {
uint32_t packed_stride = 0;
Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid;
uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7);
bool operator==(const Buffer&) const = default;
};
std::vector<DescriptorValue> buffers;
std::vector<DescriptorValue> images;
std::vector<DescriptorValue> samplers;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
std::vector<IndirectImage> indirect_images;
struct Image {
Prospero::TextureNumericClass numeric_class = Prospero::TextureNumericClass::Unsupported;
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
uint32_t mip_count = 1;
Prospero::BufferFormat conversion_format = Prospero::BufferFormat::kInvalid;
uint32_t shader_swizzle = ShaderImageIdentitySwizzle;
uint32_t indirect_root = ImageResource::NoIndirectImage;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_search_iterations = 0;
bool cube = false;
bool operator==(const Image&) const = default;
};
std::vector<Buffer> buffers;
std::vector<Image> images;
bool operator==(const ResourceSpecialization&) const = default;
};
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot);
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot);
// Extracts the descriptor/SRT value graph before resource specialization. The returned plan owns
// its values and is independent of the translated shader CFG.
ResourcePlan ExtractResourcePlan(const Program& program);
// Resolves the immutable dense resource topology against one runtime user-data/SRT snapshot.
// On failure the destination is unchanged.
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
ResourceSnapshot& snapshot);
// Resolves and specializes the immutable resource plan in one transaction. On failure both
// destinations are unchanged.
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
ResourceSnapshot& snapshot, ResourceSpecialization& specialization);
// Applies runtime descriptor shape/format facts to a copied dense topology before layout and
// emission. Invalid specializations are fatal.
void SpecializeResources(Program& program, ResourceSnapshot& snapshot);
// Applies an already-derived specialization to native IR before layout and emission.
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -464,6 +464,16 @@ private:
DescriptorSource descriptor;
MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc);
uint32_t bad_dword = 0;
if (expected == ValueOpcode::GetImageResource) {
for (; bad_dword < descriptor.dword_count; bad_dword++) {
const auto* value = descriptor.dwords[bad_dword].Resolve().TryInstruction();
if (value != nullptr && value->GetOpcode() == ValueOpcode::ReadConstBuffer) {
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
ValueOpcodeName(expected), bad_dword));
}
}
bad_dword = 0;
}
if (!ValidateSource(descriptor, bad_dword)) {
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
ValueOpcodeName(expected), bad_dword));
@@ -5,6 +5,7 @@
#include <algorithm>
#include <bit>
#include <cmath>
#include <cstring>
#include <fmt/format.h>
#include <unordered_map>
@@ -25,7 +26,7 @@ const char* StageName(ShaderType stage) {
}
}
std::string Diagnostic(const Program& program, uint32_t pc, const std::string& message) {
std::string Diagnostic(const ResourcePlan& program, uint32_t pc, const std::string& message) {
return fmt::format("shader SRT: hash=0x{:016x} stage={} pc=0x{:08x} {}", program.shader_hash,
StageName(program.stage), pc, message);
}
@@ -50,7 +51,7 @@ bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
return true;
}
bool IsRawRead(const Program& values, const Inst& inst) {
bool IsRawRead(const ResourcePlan& values, const Inst& inst) {
const auto op = inst.GetOpcode();
if (op != ValueOpcode::LoadAddressU32 && op != ValueOpcode::ReadConstBuffer) {
return false;
@@ -74,8 +75,17 @@ bool IsDescriptorHandle(ValueOpcode opcode) {
}
}
bool IsRuntimeIntegerOp(ValueOpcode op) {
bool IsRuntimeSelect(ValueOpcode op) {
return op == ValueOpcode::SelectU1 || op == ValueOpcode::SelectU32 ||
op == ValueOpcode::SelectF32;
}
bool IsRuntimeUniformOp(ValueOpcode op) {
switch (op) {
case ValueOpcode::BitCastU32F32:
case ValueOpcode::BitCastF32U32:
case ValueOpcode::ConvertU32F32:
case ValueOpcode::ConvertF32U32:
case ValueOpcode::CompositeConstructU64:
case ValueOpcode::CompositeExtractU64:
case ValueOpcode::CompositeConstructU32x2:
@@ -104,6 +114,7 @@ bool IsRuntimeIntegerOp(ValueOpcode op) {
case ValueOpcode::BitwiseNot32:
case ValueOpcode::SelectU1:
case ValueOpcode::SelectU32:
case ValueOpcode::SelectF32:
case ValueOpcode::ULessThan32:
case ValueOpcode::IEqual32:
case ValueOpcode::UGreaterThan32:
@@ -111,14 +122,20 @@ bool IsRuntimeIntegerOp(ValueOpcode op) {
case ValueOpcode::LogicalOr:
case ValueOpcode::LogicalAnd:
case ValueOpcode::LogicalXor:
case ValueOpcode::LogicalNot: return true;
case ValueOpcode::LogicalNot:
case ValueOpcode::FPOrdLessThanEqual32:
case ValueOpcode::FPOrdGreaterThanEqual32:
case ValueOpcode::FPIsNan32:
case ValueOpcode::FPMul32:
case ValueOpcode::FPTrunc32: return true;
default: return false;
}
}
class RuntimeValidator {
public:
explicit RuntimeValidator(const Program& program): m_program(program) {}
explicit RuntimeValidator(const ResourcePlan& program, Value active_mask = {})
: m_program(program), m_active_mask(active_mask.Resolve()) {}
bool Run(Value value) { return Validate(value); }
@@ -132,7 +149,8 @@ private:
case Type::U8:
case Type::U16:
case Type::U32:
case Type::U64: return true;
case Type::U64:
case Type::F32: return true;
default: return false;
}
}
@@ -144,6 +162,10 @@ private:
return valid;
};
const auto op = inst->GetOpcode();
if (!m_active_mask.IsEmpty() && IsRuntimeSelect(op) && inst->NumArgs() == 3 &&
inst->Arg(0).Resolve() == m_active_mask) {
return finish(Validate(inst->Arg(1)));
}
if (op == ValueOpcode::UndefU1 || op == ValueOpcode::UndefU8 ||
op == ValueOpcode::UndefU16 || op == ValueOpcode::UndefU32 ||
op == ValueOpcode::UndefU64 || op == ValueOpcode::Void) {
@@ -173,6 +195,13 @@ private:
}
return finish(Validate(invariant));
}
if (op == ValueOpcode::ReadFirstLane) {
if (inst->NumArgs() != 2 || inst->Arg(0).GetType() != Type::U32 ||
inst->Arg(1).GetType() != Type::U1) {
return finish(false);
}
return finish(RuntimeValidator(m_program, inst->Arg(1)).Run(inst->Arg(0)));
}
if (op == ValueOpcode::GetSrtResource) {
if (inst->NumArgs() != 0) {
return finish(false);
@@ -222,7 +251,7 @@ private:
return finish(false);
}
} else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer &&
op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op)) {
op != ValueOpcode::LoadAddressU32 && !IsRuntimeUniformOp(op)) {
return finish(false);
}
for (size_t index = 0; index < inst->NumArgs(); index++) {
@@ -233,7 +262,8 @@ private:
return finish(true);
}
const Program& m_program;
const ResourcePlan& m_program;
Value m_active_mask;
std::unordered_set<const Inst*> m_visiting;
};
@@ -385,10 +415,11 @@ private:
class Evaluator {
public:
Evaluator(const Program& program, const SrtRuntime& runtime,
std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr)
Evaluator(const ResourcePlan& program, const SrtRuntime& runtime,
std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr,
Value active_mask = {})
: m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots),
m_clean_evaluator(clean_evaluator) {}
m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()) {}
bool Evaluate(Value value, uint32_t& result) {
uint64_t wide = 0;
@@ -400,6 +431,12 @@ public:
}
private:
static float Float32(uint64_t bits) {
return std::bit_cast<float>(static_cast<uint32_t>(bits));
}
static uint64_t Float32Bits(float value) { return std::bit_cast<uint32_t>(value); }
bool EvaluateWide(Value value, uint64_t& result) {
value = value.Resolve();
if (value.IsImmediate()) {
@@ -409,6 +446,7 @@ private:
case Type::U16: result = value.U16(); return true;
case Type::U32: result = value.U32(); return true;
case Type::U64: result = value.U64(); return true;
case Type::F32: result = Float32Bits(value.F32Value()); return true;
default: return false;
}
}
@@ -416,6 +454,15 @@ private:
if (inst == nullptr) {
return false;
}
if (!m_reserved) {
m_cache.reserve(m_program.value_storage.size());
m_visiting.reserve(m_program.value_storage.size());
m_reserved = true;
}
if (!m_active_mask.IsEmpty() && IsRuntimeSelect(inst->GetOpcode()) &&
inst->NumArgs() == 3 && inst->Arg(0).Resolve() == m_active_mask) {
return EvaluateWide(inst->Arg(1), result);
}
if (const auto found = m_cache.find(inst); found != m_cache.end()) {
result = found->second;
return true;
@@ -560,6 +607,13 @@ private:
}
case ValueOpcode::GetShaderBase: result = m_runtime.shader_base; return true;
case ValueOpcode::Phi: return EvaluatePhi(inst, result);
case ValueOpcode::ReadFirstLane: {
Evaluator active(m_program, m_runtime, m_clean_flat_slots, m_clean_evaluator,
inst.Arg(1));
return active.EvaluateWide(inst.Arg(0), result);
}
case ValueOpcode::BitCastU32F32:
case ValueOpcode::BitCastF32U32: return Arg(inst, 0, result);
case ValueOpcode::CompositeExtractU64:
case ValueOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result);
case ValueOpcode::CompositeConstructU64:
@@ -630,6 +684,53 @@ private:
return true;
}
return false;
case ValueOpcode::ConvertF32U32:
if (Arg(inst, 0, a)) {
result = Float32Bits(static_cast<float>(static_cast<uint32_t>(a)));
return true;
}
return false;
case ValueOpcode::ConvertU32F32:
if (Arg(inst, 0, a)) {
const auto value = Float32(a);
if (!std::isfinite(value) || value < 0.0f ||
static_cast<double>(value) > UINT32_MAX) {
return false;
}
result = static_cast<uint32_t>(value);
return true;
}
return false;
case ValueOpcode::FPMul32:
if (binary()) {
result = Float32Bits(Float32(a) * Float32(b));
return true;
}
return false;
case ValueOpcode::FPTrunc32:
if (Arg(inst, 0, a)) {
result = Float32Bits(std::trunc(Float32(a)));
return true;
}
return false;
case ValueOpcode::FPIsNan32:
if (Arg(inst, 0, a)) {
result = std::isnan(Float32(a));
return true;
}
return false;
case ValueOpcode::FPOrdLessThanEqual32:
if (binary()) {
result = Float32(a) <= Float32(b);
return true;
}
return false;
case ValueOpcode::FPOrdGreaterThanEqual32:
if (binary()) {
result = Float32(a) >= Float32(b);
return true;
}
return false;
case ValueOpcode::BitwiseAnd32:
if (binary()) {
result = static_cast<uint32_t>(a & b);
@@ -753,6 +854,7 @@ private:
}
case ValueOpcode::SelectU32:
case ValueOpcode::SelectU1:
case ValueOpcode::SelectF32:
if (ternary()) {
result = a != 0u ? b : c;
return true;
@@ -816,23 +918,24 @@ private:
return false;
}
const Program& m_program;
const ResourcePlan& m_program;
const SrtRuntime& m_runtime;
std::span<const uint8_t> m_clean_flat_slots;
Evaluator* m_clean_evaluator = nullptr;
Value m_active_mask;
std::unordered_map<const Inst*, uint64_t> m_cache;
std::vector<const Inst*> m_visiting;
bool m_reserved = false;
};
const DescriptorSource* Source(const Program& program, uint32_t source) {
const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
if (source >= program.descriptor_sources.size()) {
return nullptr;
}
return &program.descriptor_sources[source];
}
bool EvaluateRuntimeSourcesImpl(const Program& program,
std::span<const DescriptorSourceRequest> requests,
bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uint32_t> sources,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, bool evaluate_flat,
std::span<const uint8_t> clean_flat_slots) {
@@ -848,9 +951,9 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
Evaluator clean_evaluator(program, clean_runtime);
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
std::vector<DescriptorValue> evaluated;
evaluated.reserve(requests.size());
for (const auto& request: requests) {
const auto* source = Source(program, request.source);
evaluated.reserve(sources.size());
for (const auto source_index: sources) {
const auto* source = Source(program, source_index);
if (source == nullptr) {
return false;
}
@@ -885,7 +988,7 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
} // namespace
bool ValidateRuntimeValue(const Program& program, Value value) {
bool ValidateRuntimeValue(const ResourcePlan& program, Value value) {
return RuntimeValidator(program).Run(value);
}
@@ -898,34 +1001,32 @@ void BuildSrtPlan(Program& program) {
program.srt_plan_complete = true;
}
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
DescriptorValue& result) {
const DescriptorSourceRequest request {source};
std::vector<DescriptorValue> results;
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results)) {
bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
const SrtRuntime& runtime, DescriptorValue& result) {
std::vector<DescriptorValue> results;
if (!EvaluateDescriptorSources(program, std::span {&source, 1}, runtime, results)) {
return false;
}
result = results.front();
return true;
}
bool EvaluateDescriptorSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results) {
std::vector<uint32_t> ignored;
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {});
return EvaluateRuntimeSourcesImpl(program, sources, runtime, results, ignored, false, {});
}
bool EvaluateRuntimeSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat,
std::span<const uint8_t> clean_flat_slots) {
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true,
return EvaluateRuntimeSourcesImpl(program, sources, runtime, results, flat, true,
clean_flat_slots);
}
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat) {
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
std::vector<uint32_t>& flat) {
std::vector<DescriptorValue> ignored;
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {});
}
@@ -19,32 +19,27 @@ struct SrtRuntime {
SrtMemoryReader read_specialization_memory = nullptr;
};
struct DescriptorSourceRequest {
uint32_t source = 0;
};
// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots;
// dynamic offsets remain explicit and are never assigned a fake slot.
void BuildSrtPlan(Program& program);
bool ValidateRuntimeValue(const Program& program, Value value);
bool ValidateRuntimeValue(const ResourcePlan& program, Value value);
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
DescriptorValue& result);
bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
const SrtRuntime& runtime, DescriptorValue& result);
// Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by
// several descriptors are memoized once across the batch.
bool EvaluateDescriptorSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results);
// Evaluates descriptor sources and the flattened immediate SRT with one memoized scalar walk.
// On failure neither destination is changed.
bool EvaluateRuntimeSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots);
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat);
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
std::vector<uint32_t>& flat);
} // namespace Libs::Graphics::ShaderRecompiler::IR
+1 -1
View File
@@ -48,7 +48,7 @@ std::vector<Parameter> GetParameters(const ShaderVertexInputInfo& vertex_info,
std::vector<Parameter> parameters;
for (const auto input: active_inputs) {
const auto input_location = ShaderPixelParameterMappedLocation(*pixel_info, input);
if ((vertex_info.param_export_mask & (1u << input_location)) != 0) {
if ((vertex_info.stage.program->param_export_mask & (1u << input_location)) != 0) {
parameters.push_back({input_location,
ShaderPixelParameterLocation(*pixel_info, active_inputs, input),
ShaderPixelParameterIsFlat(*pixel_info, input)});
+7 -369
View File
@@ -3,7 +3,6 @@
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/magicEnum.h"
#include "common/profiler.h"
@@ -12,23 +11,15 @@
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shaderCompiler.h"
#include "graphics/shader/shaderVertexMetadata.h"
#include "kernel/memory.h"
#include "libs/errno.h"
#include "spirv-tools/libspirv.h"
#include "spirv-tools/libspirv.hpp"
#include "spirv-tools/optimizer.hpp"
#include <algorithm>
#include <atomic>
#include <bit>
#include <cstdio>
#include <filesystem>
#include <fmt/format.h>
#include <memory>
#include <mutex>
#include <span>
@@ -49,15 +40,6 @@
namespace Libs::Graphics {
namespace {
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
return value != nullptr &&
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
}
} // namespace
struct ShaderBinaryInfo {
uint8_t signature[7];
uint8_t version;
@@ -107,62 +89,6 @@ static ShaderMappedData ShaderGetMappedData(uint64_t addr, const char* label) {
EXIT("%s shader=0x%016" PRIx64 " is missing from ShaderMap\n", label, addr);
}
static bool SpirvDisassemble(const uint32_t* src_binary, size_t src_binary_size,
std::string* dst_disassembly) {
if (dst_disassembly != nullptr) {
spvtools::SpirvTools core(SPV_ENV_VULKAN_1_2);
std::string disassembly;
if (!core.Disassemble(src_binary, src_binary_size, &disassembly,
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COMMENT) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR))) {
*dst_disassembly = disassembly.c_str();
LOGF("Disassemble failed\n");
return false;
}
*dst_disassembly = disassembly.c_str();
}
return true;
}
static bool SpirvValidateBinary(const char* label, uint64_t shader_hash,
const std::vector<uint32_t>& spirv) {
if (!Config::ShaderValidationEnabled()) {
return true;
}
spvtools::SpirvTools core(SPV_ENV_VULKAN_1_3);
std::string messages;
core.SetMessageConsumer([&messages](spv_message_level_t /*level*/, const char* /*source*/,
const spv_position_t& position, const char* message) {
messages += fmt::format("{}: {} ({}) {}\n", static_cast<int>(position.line),
static_cast<int>(position.column), static_cast<int>(position.index),
message);
});
if (core.Validate(spirv)) {
return true;
}
std::string disassembly;
SpirvDisassemble(spirv.data(), spirv.size(), &disassembly);
LOGF_COLOR(Log::Color::BrightRed, "%s SPIR-V validation failed hash=0x%016" PRIx64 ":\n%s",
label, shader_hash, messages.c_str());
LOGF("%s\n", disassembly.c_str());
return false;
}
static void ExitShaderRecompilerFailure(const char* label, uint64_t shader_hash,
const char* reason) {
EXIT("%s failed hash=0x%016" PRIx64 ": %s\n", label, shader_hash,
reason != nullptr ? reason : "");
}
static const ShaderBinaryInfo* GetBinaryInfo(const uint32_t* code) {
EXIT_IF(code == nullptr);
@@ -695,7 +621,6 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
info = {};
info.export_count = static_cast<int>(sh.GetExportCount());
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0);
@@ -746,18 +671,12 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
static void ShaderGetStaticInputInfoPS(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vs_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
const ShaderMappedData& data, ShaderPixelInputInfo& ps_info) {
KYTY_PROFILER_FUNCTION();
ps_info = {};
ps_info.scratch_size_dwords = data.scratch_size_dwords;
ps_info.push_constant_offset =
vs_info.stage.program != nullptr
? vs_info.stage.program->bindings.push_constant_offset +
vs_info.stage.program->bindings.push_constant_size
: 0;
// SPI_PS_IN_CONTROL.NUM_INTERP occupies bits 5:0. Keep the remaining control
// flags in the hardware state and extract only the input count here.
@@ -801,7 +720,6 @@ static void ShaderGetStaticInputInfoPS(
? target_export_mapping[i]
: Prospero::ColorComponentMapping {};
}
ps_info.mrt_output_mask = 0;
}
static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
@@ -828,73 +746,12 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
info.workgroup_register = regs.cs_regs.user_sgpr;
}
static void ApplyVertexOutputs(ShaderVertexInputInfo& info,
const ShaderRecompiler::IR::Program& program) {
info.export_count = 0;
info.param_export_mask = 0;
for (const auto& output: program.info.outputs) {
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Parameter && output.index < 32) {
info.param_export_mask |= 1u << output.index;
info.export_count = std::max(info.export_count, static_cast<int>(output.index + 1));
}
}
}
static void ApplyPixelOutputs(ShaderPixelInputInfo& info,
const ShaderRecompiler::IR::Program& program) {
info.mrt_output_mask = 0;
for (const auto& output: program.info.outputs) {
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt && output.index < 8) {
info.mrt_output_mask |= 1u << output.index;
}
}
}
static bool LogPermutationMismatch(const ShaderRecompiler::IR::Program* program,
const char* stage) {
static std::atomic<uint32_t> log_count {0};
if (Config::GraphicsDebugDumpEnabled() &&
log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
LOGF("ShaderProgramCache native runtime mismatch %s shader=0x%016" PRIx64 "\n",
stage, program != nullptr ? program->shader_hash : 0);
}
return false;
}
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderVertexInputInfo& info) {
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
ReadShaderGuestMemory)) {
return LogPermutationMismatch(program.get(), "VS");
}
ApplyVertexOutputs(info, *program);
return true;
}
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderPixelInputInfo& info) {
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
ReadShaderGuestMemory)) {
return LogPermutationMismatch(program.get(), "PS");
}
ApplyPixelOutputs(info, *program);
return true;
}
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderComputeInputInfo& info) {
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
ReadShaderGuestMemory)) {
return LogPermutationMismatch(program.get(), "CS");
}
return true;
}
void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) {
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX ||
info.buffers_num < 0 || info.buffers_num > ShaderVertexInputInfo::RES_MAX);
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX);
key.clear();
key.push_back(static_cast<uint32_t>(info.fetch_embedded));
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
key.push_back(info.resources_num);
key.push_back(info.scratch_size_dwords);
key.push_back(info.pa_cl_vs_out_cntl);
@@ -917,6 +774,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
key.push_back(destination.register_start);
key.push_back(destination.registers_num);
key.push_back(destination.fetch_index);
key.push_back(static_cast<uint32_t>(destination.attr_id));
key.push_back(resource.Stride());
key.push_back(static_cast<uint32_t>(resource.SwizzleEnabled()));
key.push_back(resource.DstSelX());
@@ -927,28 +785,11 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
key.push_back(resource.OutOfBounds());
key.push_back(static_cast<uint32_t>(resource.AddTid()));
}
key.push_back(info.buffers_num);
int attributes_num = 0;
for (int i = 0; i < info.buffers_num; i++) {
const auto& buffer = info.buffers[i];
EXIT_IF(buffer.attr_num < 0 || buffer.attr_num > ShaderVertexInputBuffer::ATTR_MAX);
attributes_num += buffer.attr_num;
EXIT_IF(attributes_num > ShaderVertexInputInfo::RES_MAX);
key.push_back(buffer.attr_num);
key.push_back(buffer.stride);
key.push_back(buffer.fetch_index);
for (int j = 0; j < buffer.attr_num; j++) {
key.push_back(buffer.attr_indices[j]);
key.push_back(buffer.attr_offsets[j]);
}
}
}
void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>& key) {
EXIT_IF(info.input_num > std::size(info.interpolator_settings));
key.clear();
key.push_back(info.push_constant_offset);
key.push_back(info.scratch_size_dwords);
key.push_back(info.input_num);
key.push_back(info.ps_system_input_base);
@@ -1006,11 +847,10 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::ShaderRe
ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vs_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& ps_info) {
const auto data = ShaderGetMappedData(regs.ps_regs.data_addr, "ShaderGetInputInfoPS():");
ShaderGetStaticInputInfoPS(regs, sh, vs_info, target_export_mapping, data, ps_info);
ShaderGetStaticInputInfoPS(regs, sh, target_export_mapping, data, ps_info);
return GetShaderParams(
regs.ps_regs.data_addr, "ShaderRecompiler PS", GetDeclaredShaderHash(regs.ps_regs.data_addr),
std::span<const uint32_t>(regs.ps_user_sgpr.value, regs.ps_regs.rsrc2.user_sgpr), data);
@@ -1032,11 +872,8 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
LOGF("ShaderDbgDumpInputInfo()\n");
LOGF("\t fetch_external = %s\n"
"\t fetch_embedded = %s\n"
"\t param_export_mask = 0x%08" PRIx32 "\n"
"\t export_count = %d\n",
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false",
info.param_export_mask, info.export_count);
"\t fetch_embedded = %s\n",
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false");
for (int i = 0; i < info.resources_num; i++) {
LOGF("\t input %d\n", i);
@@ -1134,205 +971,6 @@ void ShaderDbgDumpInputInfo(const ShaderComputeInputInfo& info) {
info.group_id[1] ? "true" : "false", info.group_id[2] ? "true" : "false");
}
static bool ShaderRecompilerTextDumpEnabled() {
// Graphics debug dump already writes SPIR-V binaries to _Shaders. Keep the very large
// disassembly/IR text behind the shader-log switch so file logging cannot stall boot.
return Config::GetShaderLogDirection() != Config::ShaderLogDirection::Silent;
}
static void DumpShaderRecompilerSpirv(const char* type, uint64_t shader_hash,
const std::vector<uint32_t>& bin) {
if (!Config::GraphicsDebugDumpEnabled()) {
return;
}
static std::atomic_int id = 0;
const auto base_name = Config::GetShaderLogFolder() /
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
Common::File::CreateDirectories(base_name.parent_path());
Common::File spv_file;
auto spv_name = base_name;
spv_name += ".spv";
spv_file.Create(spv_name);
if (spv_file.IsInvalid()) {
auto spv_name_text = Common::PathToString(spv_name);
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", spv_name_text.c_str());
} else {
spv_file.Write(bin.data(), bin.size() * 4);
spv_file.Close();
}
return;
std::string text;
if (!SpirvDisassemble(bin.data(), bin.size(), &text)) {
auto spv_name_text = Common::PathToString(spv_name);
LOGF_COLOR(Log::Color::BrightRed, "SpirvDisassemble() failed for %s\n",
spv_name_text.c_str());
return;
}
Common::File asm_file;
auto asm_name = base_name;
asm_name += ".spvasm";
asm_file.Create(asm_name);
if (asm_file.IsInvalid()) {
auto asm_name_text = Common::PathToString(asm_name);
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", asm_name_text.c_str());
} else {
asm_file.Printf("%s", text.c_str());
asm_file.Close();
}
}
static void DumpShaderRecompilerOriginal(const char* type, uint64_t shader_hash,
std::span<const uint32_t> code,
const std::string& decoded_dump) {
if (!Config::GraphicsDebugDumpEnabled()) {
return;
}
EXIT_IF(code.empty());
static std::atomic_int id = 0;
const auto base_name = Config::GetShaderLogFolder() / "original" /
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
Common::File::CreateDirectories(base_name.parent_path());
Common::File bin_file;
auto bin_name = base_name;
bin_name += ".bin";
bin_file.Create(bin_name);
if (bin_file.IsInvalid()) {
auto bin_name_text = Common::PathToString(bin_name);
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", bin_name_text.c_str());
} else {
bin_file.Write(code.data(), code.size_bytes());
bin_file.Close();
}
if (decoded_dump.empty()) {
return;
}
Common::File text_file;
auto text_name = base_name;
text_name += ".rdna2";
text_file.Create(text_name);
if (text_file.IsInvalid()) {
auto text_name_text = Common::PathToString(text_name);
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", text_name_text.c_str());
} else {
text_file.Printf("%s", decoded_dump.c_str());
text_file.Close();
}
}
static const char* ShaderStageName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "vs";
case ShaderType::Pixel: return "ps";
case ShaderType::Compute: return "cs";
default: EXIT("invalid shader stage\n");
}
}
static const char* ShaderStageLabel(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "ShaderRecompiler VS";
case ShaderType::Pixel: return "ShaderRecompiler PS";
case ShaderType::Compute: return "ShaderRecompiler CS";
default: EXIT("invalid shader stage\n");
}
}
static ShaderRecompiler::CompileOptions MakeCompileOptions(const ShaderParams& params,
ShaderType stage) {
ShaderRecompiler::CompileOptions options;
options.stage = stage;
options.shader_hash = params.hash;
options.user_data = params.user_data;
options.read_specialization_memory = ReadShaderGuestMemory;
options.dump_ir = ShaderRecompilerTextDumpEnabled();
options.early_dump = options.dump_ir;
options.dump_label = ShaderStageLabel(stage);
return options;
}
static vk::ShaderModule CompileModule(vk::Device device, const ShaderParams& params,
ShaderRecompiler::CompileOptions options,
ShaderStageRuntime& stage) {
const auto* stage_name = ShaderStageName(options.stage);
const auto* label = ShaderStageLabel(options.stage);
auto result = ShaderRecompiler::Recompile(params.code, options);
DumpShaderRecompilerOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
if (!SpirvValidateBinary(label, options.shader_hash, result.spirv)) {
DumpShaderRecompilerSpirv(stage_name, options.shader_hash, result.spirv);
ExitShaderRecompilerFailure(label, options.shader_hash, "SPIR-V validation failed");
}
DumpShaderRecompilerSpirv(stage_name, options.shader_hash, result.spirv);
stage.program =
std::make_shared<const ShaderRecompiler::IR::Program>(std::move(result.program));
stage.resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>(std::move(result.resources));
vk::ShaderModuleCreateInfo create_info {};
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
create_info.pCode = result.spirv.data();
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
"create recompiled shader module");
EXIT_IF(module == nullptr);
if (options.dump_ir) {
if (!options.early_dump) {
LOGF("%s decoded RDNA2:\n%s", label, result.decoded_dump.c_str());
LOGF("%s IR:\n%s", label, result.ir_dump.c_str());
}
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", label,
static_cast<uint64_t>(result.spirv.size()), options.wave_size);
}
return module;
}
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderVertexInputInfo& input_info) {
KYTY_PROFILER_FUNCTION(profiler::colors::Amber300);
auto options = MakeCompileOptions(params, ShaderType::Vertex);
options.user_data_base = 8;
options.scratch_dwords = input_info.scratch_size_dwords;
options.input_info.vertex = &input_info;
const auto module = CompileModule(device, params, options, input_info.stage);
ApplyVertexOutputs(input_info, *input_info.stage.program);
return module;
}
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderPixelInputInfo& input_info) {
KYTY_PROFILER_FUNCTION(profiler::colors::Blue300);
auto options = MakeCompileOptions(params, ShaderType::Pixel);
options.scratch_dwords = input_info.scratch_size_dwords;
options.push_constant_offset = input_info.push_constant_offset;
options.input_info.pixel = &input_info;
const auto module = CompileModule(device, params, options, input_info.stage);
ApplyPixelOutputs(input_info, *input_info.stage.program);
return module;
}
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderComputeInputInfo& input_info) {
KYTY_PROFILER_FUNCTION(profiler::colors::CyanA700);
auto options = MakeCompileOptions(params, ShaderType::Compute);
options.scratch_dwords = input_info.scratch_size_dwords;
options.wave_size = input_info.wave_size;
options.input_info.compute = &input_info;
return CompileModule(device, params, options, input_info.stage);
}
bool ShaderAddressValid(uint64_t addr) {
return reinterpret_cast<const uint32_t*>(addr) != nullptr;
}
+5 -21
View File
@@ -4,10 +4,10 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/shader/recompiler/ir/ResourceSnapshot.h"
#include "graphics/shader/shaderBindings.h"
#include <array>
#include <memory>
#include <span>
#include <string>
#include <string_view>
@@ -36,30 +36,18 @@ struct ShaderRegisters;
enum class ShaderType { Unknown, Vertex, Pixel, Fetch, Compute };
namespace ShaderRecompiler::IR {
struct Program;
struct ResourceSnapshot;
struct CompiledShaderInfo;
} // namespace ShaderRecompiler::IR
struct ShaderStageRuntime {
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> resources;
const ShaderRecompiler::IR::CompiledShaderInfo* program = nullptr;
ShaderRecompiler::IR::ResourceSnapshot resources;
[[nodiscard]] explicit operator bool() const {
return program != nullptr && resources != nullptr;
return program != nullptr;
}
};
using ShaderSpecializationMemoryReader = bool (*)(void* userdata, uint64_t address,
uint32_t* value);
// Resolves an immutable native shader plan against current user data. The prior stage is preserved
// if any ReadConst, snapshot, or specialization check fails.
bool ShaderMaterializeStageRuntime(
std::shared_ptr<const ShaderRecompiler::IR::Program> program,
std::span<const uint32_t> user_data, uint64_t shader_base, ShaderStageRuntime& stage,
ShaderSpecializationMemoryReader read_specialization_memory = nullptr,
void* read_memory_data = nullptr);
constexpr uint32_t DstSel(uint32_t x, uint32_t y = 0, uint32_t z = 0, uint32_t w = 0) {
return x | (y << 3u) | (z << 6u) | (w << 9u);
}
@@ -87,9 +75,7 @@ struct ShaderVertexInputInfo {
int fetch_attrib_reg = 0;
int fetch_buffer_reg = 0;
int buffers_num = 0;
int export_count = 0;
uint32_t scratch_size_dwords = 0;
uint32_t param_export_mask = 0;
uint32_t pa_cl_vs_out_cntl = 0;
ShaderClipSpaceTransform clip_space;
bool fetch_external = false;
@@ -119,8 +105,6 @@ struct ShaderPixelInputInfo {
uint32_t ps_perspective_center_vgpr = UINT32_MAX;
uint8_t target_output_mode[8] = {};
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint32_t mrt_output_mask = 0;
uint32_t push_constant_offset = 0;
uint32_t scratch_size_dwords = 0;
bool ps_pos_x = false;
bool ps_pos_y = false;
-17
View File
@@ -1,10 +1,8 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shader.h"
#include <memory>
#include <span>
#include <vector>
@@ -28,26 +26,11 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::ShaderRe
ShaderVertexInputInfo& input_info);
ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info);
ShaderParams PrepareProgram(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderVertexInputInfo& input_info);
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderPixelInputInfo& input_info);
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderComputeInputInfo& input_info);
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderVertexInputInfo& input_info);
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderPixelInputInfo& input_info);
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderComputeInputInfo& input_info);
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_ */
@@ -1,32 +0,0 @@
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include <utility>
namespace Libs::Graphics {
bool ShaderMaterializeStageRuntime(std::shared_ptr<const ShaderRecompiler::IR::Program> program,
std::span<const uint32_t> user_data, uint64_t shader_base,
ShaderStageRuntime& stage,
ShaderSpecializationMemoryReader read_specialization_memory,
void* read_memory_data) {
if (program == nullptr) {
return false;
}
ShaderRecompiler::IR::SrtRuntime runtime;
runtime.user_data = user_data;
runtime.shader_base = shader_base;
runtime.read_specialization_memory = read_specialization_memory;
runtime.userdata = read_memory_data;
ShaderRecompiler::IR::ResourceSnapshot snapshot;
if (!ShaderRecompiler::IR::MaterializeResources(*program, runtime, snapshot) ||
!ShaderRecompiler::IR::ValidateResourceSpecialization(*program, snapshot)) {
return false;
}
auto resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>(std::move(snapshot));
stage = {std::move(program), std::move(resources)};
return true;
}
} // namespace Libs::Graphics
+15
View File
@@ -2573,6 +2573,11 @@ int KYTY_SYSV_ABI PthreadRwlockRdlock(PthreadRwlock* rwlock) {
int KYTY_SYSV_ABI PthreadRwlockTimedrdlock(PthreadRwlock* rwlock, KernelUseconds usec) {
PRINT_NAME();
auto* pthread_static_objects = g_pthread_context->GetPthreadStaticObjects();
rwlock = static_cast<PthreadRwlock*>(
pthread_static_objects->CreateObject(rwlock, PthreadStaticObject::Type::Rwlock));
if (rwlock == nullptr) {
return KERNEL_ERROR_EINVAL;
}
@@ -2585,6 +2590,11 @@ int KYTY_SYSV_ABI PthreadRwlockTimedrdlock(PthreadRwlock* rwlock, KernelUseconds
int KYTY_SYSV_ABI PthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds usec) {
PRINT_NAME();
auto* pthread_static_objects = g_pthread_context->GetPthreadStaticObjects();
rwlock = static_cast<PthreadRwlock*>(
pthread_static_objects->CreateObject(rwlock, PthreadStaticObject::Type::Rwlock));
if (rwlock == nullptr) {
return KERNEL_ERROR_EINVAL;
}
@@ -2597,6 +2607,11 @@ int KYTY_SYSV_ABI PthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds
int KYTY_SYSV_ABI PthreadRwlockTryrdlock(PthreadRwlock* rwlock) {
PRINT_NAME();
auto* pthread_static_objects = g_pthread_context->GetPthreadStaticObjects();
rwlock = static_cast<PthreadRwlock*>(
pthread_static_objects->CreateObject(rwlock, PthreadStaticObject::Type::Rwlock));
if (rwlock == nullptr) {
return KERNEL_ERROR_EINVAL;
}
+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;
+28
View File
@@ -739,6 +739,32 @@ static const JsonValue* KYTY_SYSV_ABI JsonValueIndexUInt(const JsonValue* self,
return (index < impl->size() ? (*impl)[static_cast<size_t>(index)] : JsonStaticNullValue());
}
static JsonValue* KYTY_SYSV_ABI JsonValueReferValueKey(JsonValue* self, const JsonString* key) {
PRINT_NAME();
if (self == nullptr || self->type != JsonValueTypeObject) {
return nullptr;
}
const auto* impl = JsonObjectImpl(self->object);
const auto it = impl->find(*JsonStringImpl(key));
return (it != impl->end() ? it->second : nullptr);
}
static void KYTY_SYSV_ABI JsonValueToString(const JsonValue* self, JsonString* dst) {
PRINT_NAME();
auto* impl = JsonStringImpl(dst);
if (impl == nullptr) {
return;
}
if (self != nullptr && self->type == JsonValueTypeString) {
*impl = *JsonStringImpl(self->string);
} else {
impl->clear();
JsonSerializeValue(self, impl);
}
}
static int32_t KYTY_SYSV_ABI JsonValueSerialize(JsonValue* self, JsonString* dst) {
PRINT_NAME();
@@ -971,6 +997,8 @@ LIB_DEFINE(InitNet_1_Json2) {
LIB_FUNC("urOpESTBZmo", LibJson2::JsonObjectAssign);
LIB_FUNC("zTwZdI8AZ5Y", LibJson2::JsonValueGetBoolean);
LIB_FUNC("R7FDWtcN6f8", LibJson2::JsonValueSerialize);
LIB_FUNC("wLsJlmgEIaI", LibJson2::JsonValueReferValueKey);
LIB_FUNC("Ncel8t2Rrpc", LibJson2::JsonValueToString);
LIB_FUNC("oH8aBmLU+fc", LibJson2::JsonObjectClear);
LIB_FUNC("bAM9Qwofus0", LibJson2::JsonArrayBack);
LIB_FUNC("UeuWT+yNdCQ", LibJson2::JsonValueBoolCtor);
+3
View File
@@ -3227,6 +3227,9 @@ LIB_DEFINE(InitLibKernel_1_Pthread) {
LIB_FUNC("mqdNorrB+gI", LibKernel::PthreadRwlockWrlock);
LIB_FUNC("sIlRvQqsN2Y", LibKernel::PthreadRwlockWrlock);
LIB_FUNC("bIHoZCTomsI", LibKernel::PthreadRwlockTrywrlock);
LIB_FUNC("XD3mDeybCnk", LibKernel::PthreadRwlockTryrdlock);
LIB_FUNC("iPtZRWICjrM", LibKernel::PthreadRwlockTimedrdlock);
LIB_FUNC("adh--6nIqTk", LibKernel::PthreadRwlockTimedwrlock);
LIB_FUNC("i2ifZ3fS2fo", LibKernel::PthreadRwlockattrDestroy);
LIB_FUNC("yOfGg-I1ZII", LibKernel::PthreadRwlockattrInit);
LIB_FUNC("qsdmgXjqSgk", LibKernel::PthreadRwlockattrDestroy);
+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,
+233
View File
@@ -0,0 +1,233 @@
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include <cstdio>
#include <cstdlib>
#include <memory>
namespace {
void Check(bool value, const char *text) {
if (!value) {
std::fprintf(stderr, "ResourceMaterializationTests: failed: %s\n", text);
std::abort();
}
}
bool RejectSpecializationRead(void *userdata, uint64_t, uint32_t *) {
++*static_cast<uint32_t *>(userdata);
return false;
}
Libs::Graphics::ShaderRecompiler::IR::Block &
AddValueBlock(Libs::Graphics::ShaderRecompiler::IR::Program &program) {
using namespace Libs::Graphics::ShaderRecompiler::IR;
auto block = std::make_unique<Block>();
auto *result = block.get();
program.blocks.push_back(result);
program.block_info.push_back({.id = 0});
program.block_storage.push_back(std::move(block));
return *result;
}
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address) {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
auto &value_block = AddValueBlock(program);
MemoryInfo memory;
memory.kind = ResourceKind::ScalarAddress;
memory.planning_only = true;
program.memory_info.push_back(memory);
const auto low = Value(static_cast<uint32_t>(address));
const auto high = Value(static_cast<uint32_t>(address >> 32u));
auto &handle =
value_block.AppendNewInst(ValueOpcode::GetAddressResource, {low, high});
auto &raw = value_block.AppendNewInst(
ValueOpcode::LoadAddressU32,
{Value(&handle), Value(0u), Value(0u), Value(true)});
raw.SetFlags(MemoryFlags{.index = 0, .pc = 0x40});
program.srt_reads.push_back({Value(&raw), 0});
auto &srt = value_block.AppendNewInst(ValueOpcode::GetSrtResource);
auto &flat = value_block.AppendNewInst(ValueOpcode::ReadConst,
{Value(&srt), Value(0u)});
DescriptorSource source;
source.dwords[0] = Value(&flat);
source.dwords[1] = Value(0u);
source.dword_count = 2;
program.descriptor_sources.push_back(source);
return ExtractResourcePlan(program);
}
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan UnbasedFlatPlan() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
AddValueBlock(program);
program.info.uses_dma = true;
return ExtractResourcePlan(program);
}
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan UserDataBufferPlan() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
auto &value_block = AddValueBlock(program);
auto &user_data = value_block.AppendNewInst(
ValueOpcode::GetUserData, {Value(static_cast<ScalarReg>(0))});
DescriptorSource source;
source.dwords[0] = Value(&user_data);
source.dwords[1] = Value(0u);
source.dwords[2] = Value(0u);
source.dwords[3] = Value(0u);
source.dword_count = 4;
program.descriptor_sources.push_back(source);
program.info.buffers.push_back({.source = 0});
return ExtractResourcePlan(program);
}
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan MixedSamplerPlan() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
AddValueBlock(program);
const auto AddSource = [&program](uint32_t dword_count, uint32_t first) {
DescriptorSource source;
source.dword_count = dword_count;
source.dwords[0] = Value(first);
for (uint32_t i = 1; i < dword_count; i++) {
source.dwords[i] = Value(0u);
}
program.descriptor_sources.push_back(source);
return static_cast<uint32_t>(program.descriptor_sources.size() - 1u);
};
const auto image0 = AddSource(8, 0);
const auto image1 = AddSource(8, 0);
const auto sampler0 = AddSource(4, 0x11111111u);
const auto sampler1 = AddSource(4, 0x22222222u);
program.info.images.push_back(
{.source = image0,
.resource_class = ImageResourceClass::Sampled,
.numeric_class = Libs::Graphics::Prospero::TextureNumericClass::Float,
.dimension =
Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D});
program.info.images.push_back(
{.source = image1,
.resource_class = ImageResourceClass::Sampled,
.numeric_class = Libs::Graphics::Prospero::TextureNumericClass::Float,
.dimension =
Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D,
.conversion_format =
Libs::Graphics::Prospero::BufferFormat::k8_8_8_8UNorm});
program.info.samplers.push_back({.source = sampler0});
program.info.samplers.push_back({.source = sampler1});
program.info.sampled_pairs.push_back({.image = 0, .sampler = 0});
program.info.sampled_pairs.push_back({.image = 0, .sampler = 1});
program.info.sampled_pairs.push_back({.image = 1, .sampler = 1});
return ExtractResourcePlan(program);
}
void TestMappedSrtUsesDirectReaderByDefault() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
const uint32_t dword = 0x12345678;
auto plan = SrtPlan(reinterpret_cast<uint64_t>(&dword));
uint32_t specialization_reads = 0;
const SrtRuntime runtime{.userdata = &specialization_reads,
.read_specialization_memory =
RejectSpecializationRead};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
Check(MaterializeResources(plan, runtime, snapshot, specialization),
"mapped SRT stage materialization failed");
Check(specialization_reads == 0,
"ordinary SRT read used the specialization reader");
Check(snapshot.flattened_srt.size() == 1 &&
snapshot.flattened_srt[0] == dword,
"cache rematerialization did not use the direct reader by default");
}
void TestUnbasedFlatCacheHitMaterializes() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
auto plan = UnbasedFlatPlan();
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
Check(MaterializeResources(plan, {}, snapshot, specialization),
"unbased FLAT stage materialization failed");
Check(snapshot.buffers.empty() && snapshot.images.empty(),
"unbased FLAT plan produced unexpected descriptors");
}
void TestFailedMaterializationPreservesPriorStage() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
auto plan = UserDataBufferPlan();
ResourceSnapshot snapshot;
snapshot.user_data.push_back(0xfeedbeefu);
ResourceSpecialization specialization;
specialization.buffers.push_back({.packed_stride = 7});
Check(!MaterializeResources(plan, {}, snapshot, specialization),
"missing runtime user data did not reject the cached stage");
Check(snapshot.user_data == std::vector<uint32_t>{0xfeedbeefu} &&
specialization.buffers.size() == 1 &&
specialization.buffers[0].packed_stride == 7,
"failed cache materialization changed its destinations");
}
void TestMixedSamplerDuplicatesTheCorrectSnapshot() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
auto plan = MixedSamplerPlan();
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
Check(MaterializeResources(plan, {}, snapshot, specialization),
"mixed sampler materialization failed");
Check(snapshot.samplers.size() == 3,
"mixed sampler materialization appended unrelated samplers");
Check(snapshot.samplers[2] == snapshot.samplers[1] &&
snapshot.samplers[2] != snapshot.samplers[0],
"point sampler variant duplicated the wrong runtime descriptor");
}
} // namespace
namespace Common {
int DbgExitHandler(const char *, int, std::string_view) { std::abort(); }
int DbgExitHandler(const char *, int, fmt::text_style, std::string_view) {
std::abort();
}
int DbgExitIfHandler(const char *, const char *, int) { return 1; }
void DbgExit(int) { std::abort(); }
} // namespace Common
int main() {
TestMappedSrtUsesDirectReaderByDefault();
TestUnbasedFlatCacheHitMaterializes();
TestFailedMaterializationPreservesPriorStage();
TestMixedSamplerDuplicatesTheCorrectSnapshot();
std::puts("ResourceMaterializationTests: all cases passed");
return 0;
}
// Keep this focused standalone target self-contained by amalgamating its small
// typed-IR implementation set.
#include "graphics/shader/recompiler/ir/Block.cpp"
#include "graphics/shader/recompiler/ir/Program.cpp"
#include "graphics/shader/recompiler/ir/Type.cpp"
#include "graphics/shader/recompiler/ir/Value.cpp"
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
+203 -130
View File
@@ -8,6 +8,7 @@
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include <array>
#include <bit>
#include <cstring>
#include <iostream>
#include <limits>
@@ -29,6 +30,14 @@ void Check(bool condition, const char *message) {
}
}
bool SameResourceSnapshot(const ResourceSnapshot &lhs,
const ResourceSnapshot &rhs) {
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
lhs.samplers == rhs.samplers &&
lhs.flattened_srt == rhs.flattened_srt &&
lhs.user_data == rhs.user_data;
}
template <typename F>
void CheckFatal(F &&function, std::string_view expected, const char *message) {
try {
@@ -250,6 +259,7 @@ MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0,
void TestInvariantIndirectImageMaterialization() {
auto fixture = MakeIndirectImageFixture(false);
fixture->PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture->program);
EliminateDeadCode(fixture->program.blocks);
ValidateProgram(fixture->program, true);
@@ -298,24 +308,20 @@ void TestInvariantIndirectImageMaterialization() {
.userdata = &memory,
.read_specialization_memory = ReadLinearTestMemory};
ResourceSnapshot snapshot;
const auto same_snapshot = [](const ResourceSnapshot &lhs,
const ResourceSnapshot &rhs) {
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
lhs.samplers == rhs.samplers &&
lhs.flattened_srt == rhs.flattened_srt &&
lhs.user_data == rhs.user_data &&
lhs.indirect_images.empty() == rhs.indirect_images.empty();
};
Check(MaterializeResources(fixture->program, runtime, snapshot) &&
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot, specialization) &&
snapshot.images.size() == 1 &&
std::equal(image_descriptor.begin(), image_descriptor.end(),
snapshot.images[0].dwords.begin()),
"invariant indirect image table did not materialize");
const auto prior_snapshot = snapshot;
const auto prior_specialization = specialization;
memory.fail_address = 0x1004u;
Check(!MaterializeResources(fixture->program, runtime, snapshot) &&
same_snapshot(snapshot, prior_snapshot),
Check(!MaterializeResources(resource_plan, runtime, snapshot,
specialization) &&
SameResourceSnapshot(snapshot, prior_snapshot) &&
specialization == prior_specialization,
"rejected planning memory read mutated the snapshot");
memory.fail_address = UINT64_MAX;
@@ -330,8 +336,9 @@ void TestInvariantIndirectImageMaterialization() {
memory.words[(0x2020u - memory.base) / 4u + 3u] =
image_descriptor[3] ^ (1u << 28u);
ResourceSnapshot null_snapshot;
Check(MaterializeResources(fixture->program, runtime, null_snapshot) &&
null_snapshot.indirect_images.empty() &&
ResourceSpecialization null_specialization;
Check(MaterializeResources(resource_plan, runtime, null_snapshot,
null_specialization) &&
std::ranges::all_of(null_snapshot.images[0].dwords,
[](uint32_t dword) { return dword == 0u; }),
"stale typed null image descriptors were not canonicalized");
@@ -345,19 +352,25 @@ void TestInvariantIndirectImageMaterialization() {
memory.words[(0x2020u - memory.base) / 4u] ^= 1u;
memory.words[(0x1000u - memory.base + 36u) / 4u] = 1u;
ResourceSnapshot dynamic_snapshot;
Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot) &&
dynamic_snapshot.images.size() == 1 &&
dynamic_snapshot.indirect_images.size() == 1 &&
dynamic_snapshot.indirect_images[0].descriptors.size() == 2,
ResourceSpecialization dynamic_specialization;
Check(MaterializeResources(resource_plan, runtime, dynamic_snapshot,
dynamic_specialization) &&
dynamic_snapshot.images.size() == 2 &&
dynamic_specialization.images.size() == 2,
"dynamic indirect image table did not materialize");
SpecializeResources(fixture->program, dynamic_snapshot);
ApplyResourceSpecialization(fixture->program, dynamic_specialization);
Check(fixture->program.info.images.size() == 2 &&
fixture->program.info.images[0].indirect_root == 0 &&
fixture->program.info.images[0].indirect_mapping_capacity != 0 &&
fixture->program.info.images[0].indirect_search_iterations != 0 &&
fixture->program.info.images[0].indirect_resources.size() == 2 &&
dynamic_snapshot.images.size() == 2 &&
dynamic_snapshot.indirect_images.empty(),
dynamic_snapshot.images.size() == 2,
"dynamic indirect image table was not specialized transactionally");
const auto &mapping = dynamic_specialization.images[0];
const auto key_count = dynamic_snapshot.flattened_srt[mapping.indirect_mapping_offset];
Check(mapping.indirect_search_iterations == std::bit_width(key_count) &&
mapping.indirect_mapping_offset + 1u + key_count * 2u ==
dynamic_snapshot.flattened_srt.size(),
"indirect image mapping retained worst-case padding");
for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) {
memory.words[(0x2000u - memory.base) / 4u + dword] =
@@ -368,22 +381,25 @@ void TestInvariantIndirectImageMaterialization() {
memory.words[(0x2000u - memory.base) / 4u] += 0x100u;
memory.words[(0x2020u - memory.base) / 4u] += 0x101u;
ResourceSnapshot rebound_snapshot;
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
ResourceSpecialization rebound_specialization;
Check(MaterializeResources(resource_plan, runtime, rebound_snapshot,
rebound_specialization) &&
rebound_specialization == dynamic_specialization,
"stable indirect key mapping did not accept changed image addresses");
memory.words[(0x2020u - memory.base) / 4u] =
memory.words[(0x2000u - memory.base) / 4u];
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
"runtime indirect key mapping did not accept collapsed candidates");
const auto collapsed_snapshot = rebound_snapshot;
Check(MaterializeResources(resource_plan, runtime, rebound_snapshot,
rebound_specialization) &&
rebound_specialization != dynamic_specialization,
"collapsed indirect candidates did not select a new specialization");
const auto collapsed_specialization = rebound_specialization;
ResourceSnapshot capacity_snapshot;
ResourceSpecialization capacity_specialization;
for (const uint32_t records : {1u, 3u}) {
user_data[2] = records;
Check(
MaterializeResources(fixture->program, runtime, capacity_snapshot) &&
ValidateResourceSpecialization(fixture->program, capacity_snapshot),
"runtime indirect key mapping rejected a fitting material-table size");
Check(MaterializeResources(resource_plan, runtime, capacity_snapshot,
capacity_specialization),
"runtime indirect key mapping rejected a valid material-table size");
}
user_data[2] = 2u;
memory.words[(0x2020u - memory.base) / 4u] =
@@ -395,13 +411,14 @@ void TestInvariantIndirectImageMaterialization() {
image_descriptor[dword];
}
memory.words[(0x1000u - memory.base + 68u) / 4u] = 2u;
Check(
!MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
same_snapshot(rebound_snapshot, collapsed_snapshot),
"larger indirect candidate topology reused or mutated a cached snapshot");
Check(MaterializeResources(resource_plan, runtime, rebound_snapshot,
rebound_specialization) &&
rebound_specialization != collapsed_specialization,
"larger indirect candidate topology reused the old specialization");
auto memory_backed = MakeIndirectImageFixture(false, 0u, true);
memory_backed->PlanAndTrack();
auto memory_backed_plan = ExtractResourcePlan(memory_backed->program);
EliminateDeadCode(memory_backed->program.blocks);
std::array<uint32_t, 11> memory_backed_user_data{0x1000u, 224u << 16u, 2u, 0u,
0x2000u, 16u << 16u, 4u, 0u,
@@ -413,9 +430,12 @@ void TestInvariantIndirectImageMaterialization() {
.userdata = &memory,
.read_specialization_memory =
ReadLinearTestMemory};
Check(!MaterializeResources(memory_backed->program, memory_backed_runtime,
snapshot) &&
same_snapshot(snapshot, prior_snapshot),
const auto memory_backed_prior_snapshot = snapshot;
const auto memory_backed_prior_specialization = specialization;
Check(!MaterializeResources(memory_backed_plan, memory_backed_runtime,
snapshot, specialization) &&
SameResourceSnapshot(snapshot, memory_backed_prior_snapshot) &&
specialization == memory_backed_prior_specialization,
"rejected indirect table descriptor read mutated the snapshot");
memory.fail_address = UINT64_MAX;
@@ -728,6 +748,7 @@ void TestDynamicStorageMipTracking() {
const auto mip2 = AddStore(12, true, Value(2u));
const auto dynamic = AddStore(16, true, fixture.UserData(8));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
const auto &images = fixture.program.info.images;
Check(images.size() == 2 && images[0].mip_mode == ImageMipMode::None &&
@@ -758,15 +779,23 @@ void TestDynamicStorageMipTracking() {
<< 28u);
descriptor.dwords[5] = 3u << 4u;
descriptor.dword_count = 8;
std::array<uint32_t, 9> user_data{};
std::copy(descriptor.dwords.begin(), descriptor.dwords.end(),
user_data.begin());
user_data[8] = 2u;
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
snapshot.images.assign(images.size(), descriptor);
SpecializeResources(fixture.program, snapshot);
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"dynamic storage resources did not materialize");
ApplyResourceSpecialization(fixture.program, specialization);
Check(fixture.program.info.images[1].mip_count == 3 &&
ValidateResourceSpecialization(fixture.program, snapshot),
snapshot.images.size() == fixture.program.info.images.size(),
"base-1 through last-3 dynamic storage range was not specialized");
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
const auto storage_kind = DescriptorBindingForImage(images[0]);
Check(storage_kind.has_value(), "storage image has no descriptor binding");
const auto *storage_binding =
@@ -775,32 +804,58 @@ void TestDynamicStorageMipTracking() {
storage_binding->resources == std::vector<uint32_t>({0, 1, 1, 1}),
"dynamic storage mip descriptors were not expanded consecutively");
Program null_program;
null_program.resource_tracking_complete = true;
ImageResource null_image;
null_image.resource_class = ImageResourceClass::Storage;
null_image.dimension = Decoder::ImageDimension::Dim2D;
null_image.mip_mode = ImageMipMode::DynamicStorage;
null_image.written = true;
null_program.info.images.push_back(null_image);
Fixture null_fixture;
const auto null_handle = null_fixture.Image(
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u)});
const auto null_address = null_fixture.Emit(
ValueOpcode::MakeImageAddress,
{Value(0u), Value(0u), null_fixture.UserData(0), Value(0u), Value(0u),
Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u)});
MemoryInfo null_memory;
null_memory.kind = ResourceKind::Image;
null_memory.image_dimension = Decoder::ImageDimension::Dim2D;
null_memory.image_address_components = 3u;
null_memory.image_has_mip = true;
const auto null_data = null_fixture.Emit(
ValueOpcode::CompositeConstructU32x4,
{Value(1u), Value(2u), Value(3u), Value(4u)});
null_fixture.Emit(ValueOpcode::ImageWrite,
{null_handle, null_address, null_data, Value(true)},
null_fixture.AddMemory(null_memory, 4));
null_fixture.PlanAndTrack();
auto null_plan = ExtractResourcePlan(null_fixture.program);
ResourceSnapshot null_snapshot;
DescriptorValue null_descriptor{};
null_descriptor.dword_count = 8;
null_snapshot.images.push_back(null_descriptor);
SpecializeResources(null_program, null_snapshot);
Check(null_program.info.images[0].mip_count == 1 &&
ValidateResourceSpecialization(null_program, null_snapshot),
ResourceSpecialization null_specialization;
const std::array<uint32_t, 1> null_user_data{0u};
Check(MaterializeResources(null_plan, {.user_data = null_user_data},
null_snapshot, null_specialization),
"canonical null dynamic storage image did not materialize");
ApplyResourceSpecialization(null_fixture.program, null_specialization);
Check(null_fixture.program.info.images[0].mip_count == 1 &&
null_snapshot.images.size() == 1,
"canonical null dynamic storage image did not retain one descriptor");
snapshot.images[1].dwords[3] =
(snapshot.images[1].dwords[3] & ~(0xfu << 16u)) | (2u << 16u);
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"a changed dynamic storage mip count reused the specialization");
snapshot.images[1].dwords[3] =
(snapshot.images[1].dwords[3] & ~((0xfu << 12u) | (0xfu << 16u))) |
auto changed_user_data = user_data;
changed_user_data[3] =
(changed_user_data[3] & ~(0xfu << 16u)) | (2u << 16u);
ResourceSnapshot changed_snapshot;
ResourceSpecialization changed_specialization;
Check(MaterializeResources(resource_plan, {.user_data = changed_user_data},
changed_snapshot, changed_specialization) &&
changed_specialization != specialization,
"a changed dynamic storage mip count reused the specialization key");
changed_user_data[3] =
(changed_user_data[3] & ~((0xfu << 12u) | (0xfu << 16u))) |
(4u << 12u) | (3u << 16u);
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"an inverted dynamic storage mip range was accepted");
const auto valid_snapshot = changed_snapshot;
const auto valid_specialization = changed_specialization;
Check(!MaterializeResources(resource_plan, {.user_data = changed_user_data},
changed_snapshot, changed_specialization) &&
SameResourceSnapshot(changed_snapshot, valid_snapshot) &&
changed_specialization == valid_specialization,
"an inverted dynamic storage mip range was accepted or mutated output");
}
void TestSrtFlatteningAndRuntimeMemoization() {
@@ -843,7 +898,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
.userdata = &memory};
std::vector<DescriptorValue> descriptors;
std::vector<uint32_t> flat;
const DescriptorSourceRequest request{fixture.program.info.buffers[0].source};
const uint32_t request = fixture.program.info.buffers[0].source;
Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime,
descriptors, flat, {}),
"typed runtime source evaluation failed");
@@ -863,7 +918,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) != nullptr,
"flattened typed SRT reads did not receive a binding");
@@ -906,7 +961,7 @@ void TestDynamicSrtReadRemainsExplicit() {
ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
FindBinding(fixture.program.bindings,
@@ -1023,15 +1078,18 @@ void TestDmaAddressMaterialization() {
{unbased, Value(0u), Value(0u), Value(9u), Value(true)},
fixture.AddMemory(flat, 8));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
Check(fixture.program.info.uses_dma,
"typed address operations did not enable DMA");
std::array<uint32_t, 2> user_data{0x2008u, 0u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
Check(MaterializeResources(fixture.program, runtime, snapshot),
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"DMA shader resources did not materialize");
SpecializeResources(fixture.program, snapshot);
ApplyResourceSpecialization(fixture.program, specialization);
}
void TestDynamicFlatAddressesUseDma() {
@@ -1053,13 +1111,16 @@ void TestDynamicFlatAddressesUseDma() {
fixture.Emit(ValueOpcode::LoadAddressU8, {address, low, high, active},
fixture.AddMemory(flat, 0xa4));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
Check(fixture.program.info.uses_dma,
"exec-masked FLAT address did not enable DMA");
std::array<uint32_t, 3> user_data{0x23456780u, 1u, 1u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
Check(MaterializeResources(fixture.program, runtime, snapshot),
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"exec-masked FLAT shader resources did not materialize");
Fixture mismatch;
@@ -1095,6 +1156,7 @@ void TestBufferSwizzleSpecialization() {
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(memory, 4));
fixture.PlanAndTrack();
auto resource_plan = ExtractResourcePlan(fixture.program);
constexpr auto swizzle = Libs::Graphics::DstSel(4, 5, 0, 1);
std::array<uint32_t, 4> user_data{
@@ -1106,16 +1168,22 @@ void TestBufferSwizzleSpecialization() {
(1u << 24u)};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
Check(MaterializeResources(fixture.program, runtime, snapshot),
ResourceSpecialization specialization;
Check(MaterializeResources(resource_plan, runtime, snapshot,
specialization),
"buffer resources did not materialize");
SpecializeResources(fixture.program, snapshot);
ApplyResourceSpecialization(fixture.program, specialization);
Check(fixture.program.info.buffers[0].descriptor_swizzle == swizzle &&
ValidateResourceSpecialization(fixture.program, snapshot),
specialization.buffers[0].descriptor_swizzle == swizzle,
"buffer destination selectors were not specialized");
snapshot.buffers[0].dwords[3] ^= 1u << 9u;
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"buffer swizzle change did not invalidate specialization");
user_data[3] ^= 1u << 9u;
ResourceSnapshot changed_snapshot;
ResourceSpecialization changed_specialization;
Check(MaterializeResources(resource_plan, runtime, changed_snapshot,
changed_specialization) &&
changed_specialization != specialization,
"buffer swizzle change did not select a new specialization key");
}
void TestShaderInfoAndBindingLayout() {
@@ -1147,15 +1215,14 @@ void TestShaderInfoAndBindingLayout() {
StageInputKind::GlobalInvocationId,
"typed shader values were not reflected in shader info");
AllocateBindings(fixture.program, 0);
AllocateBindings(fixture.program);
Check(FindBinding(fixture.program.bindings, DescriptorBindingKind::Buffers) !=
nullptr &&
FindBinding(fixture.program.bindings, DescriptorBindingKind::Gds) !=
nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::UserData) == nullptr &&
fixture.program.bindings.push_constant_size ==
fixture.program.bindings.ShaderDataDwords() * sizeof(uint32_t),
DescriptorBindingKind::ShaderData) == nullptr &&
fixture.program.bindings.UsesPushData(),
"typed resources were not assigned native bindings");
Check(NativeBinding(ShaderType::Compute, DescriptorBindingKind::Buffers) ==
static_cast<uint32_t>(DescriptorBindingKind::Buffers) &&
@@ -1173,15 +1240,15 @@ void TestShaderInfoAndBindingLayout() {
void TestImageBindingAbi() {
using NumericClass = Libs::Graphics::Prospero::TextureNumericClass;
Check(ImageBindingCount == 36u &&
Check(ImageBindingCount == 43u &&
static_cast<uint32_t>(DescriptorBindingKind::Buffers) == 0u &&
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 37u &&
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 38u &&
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 39u &&
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 40u &&
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 41u &&
static_cast<uint32_t>(DescriptorBindingKind::UserData) == 42u &&
static_cast<uint32_t>(DescriptorBindingKind::Count) == 43u,
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 44u &&
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 45u &&
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 46u &&
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 47u &&
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 48u &&
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 49u &&
static_cast<uint32_t>(DescriptorBindingKind::Count) == 50u,
"native descriptor binding anchors changed");
const std::array sampled_dimensions{
@@ -1204,12 +1271,14 @@ void TestImageBindingAbi() {
uint32_t index = 0;
const auto CheckBinding =
[&](ImageResourceClass resource_class, NumericClass numeric_class,
Decoder::ImageDimension dimension, bool atomic) {
Decoder::ImageDimension dimension, bool atomic,
bool depth_compare = false) {
ImageResource image;
image.resource_class = resource_class;
image.numeric_class = numeric_class;
image.dimension = dimension;
image.atomic = atomic;
image.depth_compare = depth_compare;
const auto kind = DescriptorBindingForImage(image);
Check(kind.has_value() &&
static_cast<uint32_t>(*kind) == FirstImageBinding + index &&
@@ -1229,6 +1298,10 @@ void TestImageBindingAbi() {
false);
}
}
for (const auto dimension : sampled_dimensions) {
CheckBinding(ImageResourceClass::Sampled, NumericClass::Float, dimension,
false, true);
}
for (const auto numeric_class : storage_classes) {
for (const auto dimension : storage_dimensions) {
CheckBinding(ImageResourceClass::Storage, numeric_class, dimension,
@@ -1255,6 +1328,11 @@ void TestImageBindingAbi() {
image.numeric_class = NumericClass::Unsupported;
Check(Invalid(image),
"unsupported sampled class received a descriptor binding");
image.numeric_class = NumericClass::Uint;
image.depth_compare = true;
Check(Invalid(image),
"integer comparison image received a descriptor binding");
image.depth_compare = false;
image.numeric_class = static_cast<NumericClass>(UINT32_MAX);
Check(Invalid(image), "invalid sampled class received a descriptor binding");
image.numeric_class = NumericClass::Float;
@@ -1284,58 +1362,53 @@ void TestGraphicsPushConstantLayout() {
}
fixture.program.shader_info_complete = true;
};
Fixture vertex(ShaderType::Vertex);
AddUserData(vertex, 9);
AllocateBindings(vertex.program, 0);
Check(vertex.program.bindings.push_constant_offset == 0 &&
vertex.program.bindings.push_constant_size == 9 * sizeof(uint32_t),
"vertex shader was not placed at the start of the graphics push bank");
const auto pixel_offset = vertex.program.bindings.push_constant_size;
uint32_t cursor = 0;
Fixture pixel(ShaderType::Pixel);
AddUserData(pixel, 4);
AllocateBindings(pixel.program, pixel_offset);
AllocateBindings(pixel.program, cursor);
Check(
pixel.program.bindings.push_constant_offset == pixel_offset &&
pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) &&
pixel.program.bindings.UsesPushData() &&
pixel.program.bindings.push_data_start_dword == 0 &&
FindBinding(pixel.program.bindings,
DescriptorBindingKind::UserData) == nullptr,
"pixel shader did not follow the vertex data in the graphics push bank");
DescriptorBindingKind::ShaderData) == nullptr,
"pixel shader did not start the shared push-data block");
pixel.program.bindings.AdvancePushData(cursor);
Fixture vertex(ShaderType::Vertex);
AddUserData(vertex, 9);
AllocateBindings(vertex.program, cursor);
Check(vertex.program.bindings.UsesPushData() &&
vertex.program.bindings.push_data_start_dword == 4,
"vertex shader did not follow pixel data in the shared push-data block");
vertex.program.bindings.AdvancePushData(cursor);
Check(cursor == 13, "graphics push-data cursor advanced incorrectly");
Fixture edge(ShaderType::Pixel);
AddUserData(edge, 1);
AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t));
Check(edge.program.bindings.push_constant_size == sizeof(uint32_t),
"last aligned push-constant dword did not fit in the graphics bank");
AddUserData(edge, NativePushConstantSize / sizeof(uint32_t));
AllocateBindings(edge.program);
Check(edge.program.bindings.UsesPushData() &&
FindBinding(edge.program.bindings,
DescriptorBindingKind::ShaderData) == nullptr,
"the full shared push-data block did not fit");
Fixture spill(ShaderType::Pixel);
AddUserData(spill, 32);
AllocateBindings(spill.program, pixel_offset);
AddUserData(spill, 20);
AllocateBindings(spill.program, cursor);
Check(
spill.program.bindings.push_constant_size == 0 &&
!spill.program.bindings.UsesPushData() &&
spill.program.bindings.push_data_start_dword == PushData::NoStart &&
FindBinding(spill.program.bindings,
DescriptorBindingKind::UserData) != nullptr,
"pixel shader overlapping the vertex push data did not spill to storage");
DescriptorBindingKind::ShaderData) != nullptr,
"a stage that exceeded the remaining shared push data did not spill to storage");
const auto spill_layout = spill.program.bindings;
spill.program.bindings.AdvancePushData(cursor);
Check(cursor == 13, "a spilled stage consumed shared push-data space");
Fixture full(ShaderType::Pixel);
AddUserData(full, 1);
AllocateBindings(full.program, NativePushConstantSize);
Check(full.program.bindings.push_constant_size == 0 &&
FindBinding(full.program.bindings,
DescriptorBindingKind::UserData) != nullptr,
"full graphics push bank did not spill pixel user data to storage");
Fixture invalid(ShaderType::Pixel);
AddUserData(invalid, 1);
CheckFatal(
[&] {
AllocateBindings(invalid.program,
NativePushConstantSize + sizeof(uint32_t));
},
"push-constant offset",
"push-constant placement beyond the graphics bank was accepted");
Check(!invalid.program.binding_layout_complete,
"push-constant placement beyond the graphics bank was accepted");
Fixture repeated_spill(ShaderType::Pixel);
AddUserData(repeated_spill, 20);
AllocateBindings(repeated_spill.program, 20);
Check(repeated_spill.program.bindings == spill_layout,
"storage fallback retained an irrelevant attempted push-data position");
}
void TestResourceLimitIsTransactional() {
+79
View File
@@ -319,6 +319,84 @@ void TestRuntime64BitDescriptorOps() {
"64-bit typed descriptor arithmetic evaluation is incorrect");
}
void TestUniformFirstLaneSamplerLod() {
Fixture fixture;
const auto active = fixture.Emit(ValueOpcode::WqmMask, {Value(true)});
const auto stale = fixture.Emit(
ValueOpcode::GetVectorRegister, {Value(static_cast<VectorReg>(0))});
const auto write = [&](Value value, Value previous) {
return fixture.Emit(ValueOpcode::SelectU32, {active, value, previous});
};
const auto user = fixture.Emit(
ValueOpcode::GetUserData, {Value(static_cast<ScalarReg>(8))});
const auto as_float = fixture.Emit(ValueOpcode::ConvertF32U32, {user});
const auto initial = write(
fixture.Emit(ValueOpcode::BitCastU32F32, {as_float}), stale);
const auto initial_float =
fixture.Emit(ValueOpcode::BitCastF32U32, {initial});
const auto scaled = fixture.Emit(ValueOpcode::FPMul32,
{Value::F32(256.0f), initial_float});
const auto scaled_write = write(
fixture.Emit(ValueOpcode::BitCastU32F32, {scaled}), initial);
const auto scaled_float =
fixture.Emit(ValueOpcode::BitCastF32U32, {scaled_write});
const auto nan = fixture.Emit(ValueOpcode::FPIsNan32, {scaled_float});
const auto low = fixture.Emit(ValueOpcode::FPOrdLessThanEqual32,
{scaled_float, Value::F32(0.0f)});
const auto high = fixture.Emit(ValueOpcode::FPOrdGreaterThanEqual32,
{scaled_float, Value::F32(4294967296.0f)});
const auto truncated = fixture.Emit(ValueOpcode::FPTrunc32, {scaled_float});
const auto safe_low = fixture.Emit(
ValueOpcode::SelectF32,
{fixture.Emit(ValueOpcode::LogicalOr, {nan, low}), Value::F32(0.0f),
truncated});
const auto safe = fixture.Emit(
ValueOpcode::SelectF32,
{high, Value::F32(4294967040.0f), safe_low});
const auto converted = fixture.Emit(ValueOpcode::ConvertU32F32, {safe});
const auto saturated = write(
fixture.Emit(ValueOpcode::SelectU32,
{high, Value(UINT32_MAX), converted}),
scaled_write);
const auto lod = fixture.Emit(ValueOpcode::UMin32,
{saturated, Value(0xfffu)});
const auto lod_write = write(lod, saturated);
const auto masked = fixture.Emit(ValueOpcode::BitwiseAnd32,
{lod_write, Value(0xfffu)});
const auto masked_write = write(masked, lod_write);
const auto packed = write(
fixture.Emit(
ValueOpcode::BitwiseOr32,
{fixture.Emit(ValueOpcode::ShiftLeftLogical32,
{masked_write, Value(12u)}),
masked_write}),
masked_write);
const auto first = fixture.Emit(ValueOpcode::ReadFirstLane,
{packed, active});
const auto divergent = fixture.Emit(
ValueOpcode::ReadFirstLane, {stale, active});
fixture.Plan();
Check(ValidateRuntimeValue(fixture.program, first),
"uniform sampler LOD construction was rejected");
Check(!ValidateRuntimeValue(fixture.program, divergent),
"divergent first-lane value was accepted as uniform");
fixture.program.descriptor_sources.push_back(
{.dwords = {first}, .dword_count = 1});
std::array<uint32_t, 7> user_data{};
user_data[6] = 3u;
SrtRuntime runtime{.user_data = user_data};
DescriptorValue result;
Check(EvaluateDescriptorSource(fixture.program, 0, runtime, result) &&
result.dwords[0] == 0x00300300u,
"uniform sampler LOD evaluated incorrectly");
user_data[6] = 20u;
Check(EvaluateDescriptorSource(fixture.program, 0, runtime, result) &&
result.dwords[0] == 0x00ffffffu,
"uniform sampler LOD clamp evaluated incorrectly");
}
void TestConstantBufferBounds() {
Fixture fixture;
const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer);
@@ -475,6 +553,7 @@ int main() {
TestInvariantAndDivergentPhi();
TestControlDependentStandaloneLoadStaysTyped();
TestRuntime64BitDescriptorOps();
TestUniformFirstLaneSamplerLod();
TestConstantBufferBounds();
TestReadLaneElimination();
TestOptimizationPipeline();
File diff suppressed because it is too large. Load diff
-178
View File
@@ -1,178 +0,0 @@
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include <cstdio>
#include <cstdlib>
#include <memory>
namespace {
void Check(bool value, const char *text) {
if (!value) {
std::fprintf(stderr, "ShaderStageRuntimeTests: failed: %s\n", text);
std::abort();
}
}
bool RejectSpecializationRead(void *userdata, uint64_t, uint32_t *) {
++*static_cast<uint32_t *>(userdata);
return false;
}
Libs::Graphics::ShaderRecompiler::IR::Block &
AddValueBlock(Libs::Graphics::ShaderRecompiler::IR::Program &program) {
using namespace Libs::Graphics::ShaderRecompiler::IR;
auto block = std::make_unique<Block>();
auto *result = block.get();
program.blocks.push_back(result);
program.block_info.push_back({.id = 0});
program.block_storage.push_back(std::move(block));
return *result;
}
std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program>
SrtProgram(uint64_t address) {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
auto &value_block = AddValueBlock(program);
MemoryInfo memory;
memory.kind = ResourceKind::ScalarAddress;
memory.planning_only = true;
program.memory_info.push_back(memory);
const auto low = Value(static_cast<uint32_t>(address));
const auto high = Value(static_cast<uint32_t>(address >> 32u));
auto &handle =
value_block.AppendNewInst(ValueOpcode::GetAddressResource, {low, high});
auto &raw = value_block.AppendNewInst(
ValueOpcode::LoadAddressU32,
{Value(&handle), Value(0u), Value(0u), Value(true)});
raw.SetFlags(MemoryFlags{.index = 0, .pc = 0x40});
program.srt_reads.push_back({Value(&raw), 0});
auto &srt = value_block.AppendNewInst(ValueOpcode::GetSrtResource);
auto &flat = value_block.AppendNewInst(ValueOpcode::ReadConst,
{Value(&srt), Value(0u)});
DescriptorSource source;
source.dwords[0] = Value(&flat);
source.dwords[1] = Value(0u);
source.dword_count = 2;
program.descriptor_sources.push_back(source);
return std::make_shared<const Program>(std::move(program));
}
std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program>
UnbasedFlatProgram() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
AddValueBlock(program);
program.info.uses_dma = true;
return std::make_shared<const Program>(std::move(program));
}
std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program>
UserDataBufferProgram() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
auto &value_block = AddValueBlock(program);
auto &user_data = value_block.AppendNewInst(
ValueOpcode::GetUserData, {Value(static_cast<ScalarReg>(0))});
DescriptorSource source;
source.dwords[0] = Value(&user_data);
source.dwords[1] = Value(0u);
source.dwords[2] = Value(0u);
source.dwords[3] = Value(0u);
source.dword_count = 4;
program.descriptor_sources.push_back(source);
program.info.buffers.push_back({.source = 0});
return std::make_shared<const Program>(std::move(program));
}
void TestMappedSrtUsesDirectReaderByDefault() {
using namespace Libs::Graphics;
const uint32_t dword = 0x12345678;
auto cached_program = SrtProgram(reinterpret_cast<uint64_t>(&dword));
ShaderStageRuntime stage;
uint32_t specialization_reads = 0;
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage,
RejectSpecializationRead,
&specialization_reads),
"mapped SRT stage materialization failed");
Check(specialization_reads == 0,
"ordinary SRT read used the specialization reader");
Check(stage.program == cached_program && stage.resources != nullptr,
"cache rematerialization did not publish the mapped stage");
Check(stage.resources->flattened_srt.size() == 1 &&
stage.resources->flattened_srt[0] == dword,
"cache rematerialization did not use the direct reader by default");
}
void TestUnbasedFlatCacheHitMaterializes() {
using namespace Libs::Graphics;
auto cached_program = UnbasedFlatProgram();
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
auto prior_resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
ShaderStageRuntime stage{prior_program, prior_resources};
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage),
"unbased FLAT stage materialization failed");
Check(stage.program == cached_program && stage.resources != prior_resources,
"unbased FLAT cache hit did not publish its BDA stage snapshot");
}
void TestFailedMaterializationPreservesPriorStage() {
using namespace Libs::Graphics;
auto cached_program = UserDataBufferProgram();
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
auto prior_resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
ShaderStageRuntime stage{prior_program, prior_resources};
Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage),
"missing runtime user data did not reject the cached stage");
Check(stage.program == prior_program && stage.resources == prior_resources,
"failed cache materialization replaced the prior stage");
}
} // namespace
namespace Common {
int DbgExitHandler(const char *, int, std::string_view) { std::abort(); }
int DbgExitHandler(const char *, int, fmt::text_style, std::string_view) {
std::abort();
}
int DbgExitIfHandler(const char *, const char *, int) { return 1; }
void DbgExit(int) { std::abort(); }
} // namespace Common
int main() {
TestMappedSrtUsesDirectReaderByDefault();
TestUnbasedFlatCacheHitMaterializes();
TestFailedMaterializationPreservesPriorStage();
std::puts("ShaderStageRuntimeTests: all cases passed");
return 0;
}
// Keep this focused standalone target self-contained by amalgamating its small
// typed-IR implementation set.
#include "graphics/shader/recompiler/ir/Block.cpp"
#include "graphics/shader/recompiler/ir/Program.cpp"
#include "graphics/shader/recompiler/ir/Type.cpp"
#include "graphics/shader/recompiler/ir/Value.cpp"
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
+368 -335
View File
File diff suppressed because it is too large. Load diff