Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9b0b87545 | ||
|
|
040b2b9653 | ||
|
|
eccd6f6cac | ||
|
|
0ce19357f6 | ||
|
|
d762d24f73 | ||
|
|
eb917463ad | ||
|
|
c2189de88f | ||
|
|
2fe5c150bc | ||
|
|
5b8e3b51b0 | ||
|
|
f27a136430 | ||
|
|
fb4d97e8a9 | ||
|
|
5ffcd28ca4 | ||
|
|
b3c8f91a84 | ||
|
|
c78a5a9fdf | ||
|
|
858c18468b | ||
|
|
cd74809086 | ||
|
|
e21c5f37d0 | ||
|
|
2d5023fda2 | ||
|
|
98200fed19 | ||
|
|
c48a8bd6ae | ||
|
|
16ea36eb4b | ||
|
|
8f6f65f4c9 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -59,6 +59,7 @@ int DbgExitHandler(const char* file, int line, fmt::text_style style, std::strin
|
||||
}
|
||||
|
||||
void DbgExit(int status) {
|
||||
Subsystems::EmergencyShutdownActive();
|
||||
std::fflush(nullptr);
|
||||
std::_Exit(status);
|
||||
}
|
||||
|
||||
@@ -29,11 +29,7 @@ void DbgExit(int status);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
||||
#else
|
||||
#define EXIT_HALT() (std::_Exit(321), 1)
|
||||
#endif
|
||||
|
||||
#ifndef KYTY_FINAL
|
||||
#define EXIT_IF(x) \
|
||||
|
||||
@@ -69,7 +69,7 @@ File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
Close();
|
||||
}
|
||||
|
||||
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
||||
|
||||
@@ -73,13 +73,9 @@ public:
|
||||
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
|
||||
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
|
||||
void SetNumInstances(uint32_t num_instances);
|
||||
void DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 0, const void* object_ids = nullptr,
|
||||
uint32_t render_target_slice_offset = 0, int32_t vertex_offset_add = 0,
|
||||
uint32_t first_instance = 0);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count, uint32_t flags);
|
||||
void DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0);
|
||||
void DrawIndex(DrawIndexArgs args);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
|
||||
void DrawIndexAuto(DrawAutoArgs args);
|
||||
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
@@ -149,10 +145,6 @@ private:
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void SuspendPm4();
|
||||
void SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset, uint32_t first_vertex,
|
||||
uint32_t first_instance);
|
||||
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ add_executable(launcher WIN32
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets)
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets kyty_git_version)
|
||||
if(WIN32)
|
||||
target_link_libraries(launcher setupapi)
|
||||
endif()
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>560</width>
|
||||
<height>450</height>
|
||||
<width>760</width>
|
||||
<height>620</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -17,363 +17,278 @@
|
||||
<locale language="English" country="UnitedStates"/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="horizontalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QFrame" name="configuaration_groupbox">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<layout class="QGridLayout" name="sectionsLayout" columnstretch="1,1">
|
||||
<property name="horizontalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_6">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QFrame" name="frame_4">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_vulkan_validation">
|
||||
<property name="toolTip">
|
||||
<string>Enable Vulkan validation layers</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Vulkan validation</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_shader_validation">
|
||||
<property name="toolTip">
|
||||
<string>Validate SPIR-V binary</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Shader validation</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cmd_dump">
|
||||
<property name="toolTip">
|
||||
<string>Dump command buffers</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Command buffer dump</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_renderdoc_capture">
|
||||
<property name="toolTip">
|
||||
<string>Enable RenderDoc capture</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RenderDoc capture</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_fullscreen">
|
||||
<property name="toolTip">
|
||||
<string>Run in borderless desktop fullscreen</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_red_zone_protection">
|
||||
<property name="toolTip">
|
||||
<string>Helps prevent crashes in some games on Windows</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Windows SysV red zone crash protection (Experimental)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_user_name">
|
||||
<property name="text">
|
||||
<string>User name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_user_name">
|
||||
<property name="toolTip">
|
||||
<string>Local user name exposed to games</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_user_id">
|
||||
<property name="text">
|
||||
<string>User ID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_user_id">
|
||||
<property name="toolTip">
|
||||
<string>Local user ID exposed to games</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2147483647</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Screen resolution:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
||||
<property name="toolTip">
|
||||
<string>Window resolution</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_present_mode">
|
||||
<property name="text">
|
||||
<string>Present mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="comboBox_present_mode">
|
||||
<property name="toolTip">
|
||||
<string>Vulkan swapchain presentation mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_vblank_frequency">
|
||||
<property name="text">
|
||||
<string>Vblank frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_vblank_frequency">
|
||||
<property name="toolTip">
|
||||
<string>Virtual display refresh rate used for frame pacing</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>360</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>60</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_console_language">
|
||||
<property name="text">
|
||||
<string>Console language:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="comboBox_console_language">
|
||||
<property name="toolTip">
|
||||
<string>Language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Shader optimization type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_optimization_type">
|
||||
<property name="toolTip">
|
||||
<string>Optimize shaders for code size or performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Shader log direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_log_direction">
|
||||
<property name="toolTip">
|
||||
<string>Dump shaders to file or console window. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Shader log folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
|
||||
<property name="toolTip">
|
||||
<string>Specify directory to dump shaders</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Command buffer dump folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
|
||||
<property name="toolTip">
|
||||
<string>Specify directory to dump command buffers</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Printf direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QComboBox" name="comboBox_printf_direction">
|
||||
<property name="toolTip">
|
||||
<string>Print logs to file or console window. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Printf output file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
|
||||
<property name="toolTip">
|
||||
<string>Specify file to dump logs</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label_27">
|
||||
<property name="text">
|
||||
<string>Profiler direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QComboBox" name="comboBox_profiler_direction">
|
||||
<property name="toolTip">
|
||||
<string>Enable or disable profiler. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="ok_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
<property name="verticalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="user_profile_group">
|
||||
<property name="title">
|
||||
<string>User profile</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="userProfileLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_user_name">
|
||||
<property name="text"><string>User name:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_user_name">
|
||||
<property name="toolTip"><string>Local user name exposed to games</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_user_id">
|
||||
<property name="text"><string>User ID:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_user_id">
|
||||
<property name="toolTip"><string>Local user ID exposed to games</string></property>
|
||||
<property name="maximum"><number>2147483647</number></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_console_language">
|
||||
<property name="text"><string>Console language:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboBox_console_language">
|
||||
<property name="toolTip"><string>Language</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="2">
|
||||
<widget class="QGroupBox" name="graphics_group">
|
||||
<property name="title">
|
||||
<string>Graphics && display</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="graphicsLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_screen_resolution">
|
||||
<property name="text"><string>Screen resolution:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
||||
<property name="toolTip"><string>Window resolution</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_present_mode">
|
||||
<property name="text"><string>Present mode:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboBox_present_mode">
|
||||
<property name="toolTip"><string>Vulkan swapchain presentation mode</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_vblank_frequency">
|
||||
<property name="text"><string>Vblank frequency:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_vblank_frequency">
|
||||
<property name="toolTip"><string>Virtual display refresh rate used for frame pacing</string></property>
|
||||
<property name="minimum"><number>30</number></property>
|
||||
<property name="maximum"><number>360</number></property>
|
||||
<property name="value"><number>60</number></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_shader_optimization">
|
||||
<property name="text"><string>Shader optimization:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_optimization_type">
|
||||
<property name="toolTip"><string>Optimize shaders for code size or performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_fullscreen">
|
||||
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
|
||||
<property name="text"><string>Fullscreen</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="debug_group">
|
||||
<property name="title">
|
||||
<string>Debugging && logs</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="debugLayout" columnstretch="0,1,0,1">
|
||||
<property name="horizontalSpacing"><number>8</number></property>
|
||||
<property name="verticalSpacing"><number>6</number></property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_vulkan_validation">
|
||||
<property name="toolTip"><string>Enable Vulkan validation layers</string></property>
|
||||
<property name="text"><string>Vulkan validation</string></property>
|
||||
<property name="checked"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_shader_validation">
|
||||
<property name="toolTip"><string>Validate SPIR-V binary</string></property>
|
||||
<property name="text"><string>Shader validation</string></property>
|
||||
<property name="checked"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_renderdoc_capture">
|
||||
<property name="toolTip"><string>Enable RenderDoc capture</string></property>
|
||||
<property name="text"><string>RenderDoc capture</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_cmd_dump">
|
||||
<property name="toolTip"><string>Dump command buffers</string></property>
|
||||
<property name="text"><string>Command buffer dump</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_shader_log_direction">
|
||||
<property name="text"><string>Shader logging:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_log_direction">
|
||||
<property name="toolTip"><string>Dump shaders to file or console window. If enabled may decrease emulator performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="label_shader_log_folder">
|
||||
<property name="text"><string>Shader log folder:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
|
||||
<property name="toolTip"><string>Specify directory to dump shaders</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_printf_direction">
|
||||
<property name="text"><string>Printf output:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_printf_direction">
|
||||
<property name="toolTip"><string>Print logs to file or console window. If enabled may decrease emulator performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="label_printf_file">
|
||||
<property name="text"><string>Printf output file:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
|
||||
<property name="toolTip"><string>Specify file to dump logs</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_profiler_direction">
|
||||
<property name="text"><string>Profiler:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="comboBox_profiler_direction">
|
||||
<property name="toolTip"><string>Enable or disable profiler. If enabled may decrease emulator performance</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QLabel" name="label_cmd_dump_folder">
|
||||
<property name="text"><string>Command buffer folder:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
|
||||
<property name="toolTip"><string>Specify directory to dump command buffers</string></property>
|
||||
<property name="clearButtonEnabled"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="compatibility_group">
|
||||
<property name="title">
|
||||
<string>Compatibility</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="compatibilityLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_red_zone_protection">
|
||||
<property name="toolTip"><string>Helps prevent crashes in some games on Windows</string></property>
|
||||
<property name="text"><string>Windows SysV red zone crash protection (Experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="compatibilitySpacer">
|
||||
<property name="orientation"><enum>Qt::Horizontal</enum></property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size><width>40</width><height>20</height></size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="buttonLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="clear_button">
|
||||
<property name="minimumSize"><size><width>80</width><height>25</height></size></property>
|
||||
<property name="text"><string>Reset</string></property>
|
||||
<property name="toolTip"><string>Restore default settings</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="buttonSpacer">
|
||||
<property name="orientation"><enum>Qt::Horizontal</enum></property>
|
||||
<property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="minimumSize"><size><width>80</width><height>25</height></size></property>
|
||||
<property name="text"><string>Cancel</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QPushButton" name="ok_button">
|
||||
<property name="minimumSize"><size><width>80</width><height>25</height></size></property>
|
||||
<property name="text"><string>Save</string></property>
|
||||
<property name="default"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clear_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -392,16 +307,6 @@
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ConfigurationEditDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>496</x>
|
||||
<y>65</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>280</x>
|
||||
<y>180</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -72,9 +72,43 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Version">
|
||||
<layout class="QHBoxLayout" name="versionLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_Version">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="check_updates_link">
|
||||
<property name="text">
|
||||
<string><a href="check">Check for updates</a></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="versionSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="check_updates_on_startup">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string>Check for updates on startup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M8 4h11l5 5v7M24 9h-5V4M16 27H8a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"/>
|
||||
<path d="m15 24 1-4 8-8 4 4-8 8-4 1Z"/>
|
||||
<path d="m22 14 4 4" opacity=".8"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M5 8h3M14 8h13M5 16h13M24 16h3M5 24h6M17 24h10"/>
|
||||
<circle cx="11" cy="8" r="3"/>
|
||||
<circle cx="21" cy="16" r="3"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
@@ -1,9 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M10 10h12c3.6 0 5.8 2.3 6.6 6.8l.9 5.1c.4 2.4-.8 4.1-2.8 4.1-1.4 0-2.5-.7-3.6-2.3L21.3 21H10.7l-1.8 2.7C7.8 25.3 6.7 26 5.3 26c-2 0-3.2-1.7-2.8-4.1l.9-5.1C4.2 12.3 6.4 10 10 10Z"/>
|
||||
<path d="M8 16h6M11 13v6"/>
|
||||
<circle cx="22" cy="15" r="1" fill="#e8f1ff" stroke="none"/>
|
||||
<circle cx="25" cy="18" r="1" fill="#e8f1ff" stroke="none"/>
|
||||
<circle cx="22" cy="15" r="1" fill="#000000" stroke="none"/>
|
||||
<circle cx="25" cy="18" r="1" fill="#000000" stroke="none"/>
|
||||
<path d="M13 7h6M16 4v6" opacity=".8"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 574 B After Width: | Height: | Size: 574 B |
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<g fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M6 9h20M12 9V5h8v4M9 9l1 18h12l1-18"/>
|
||||
<path d="M14 14v8M18 14v8" opacity=".8"/>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 272 B |
@@ -10,6 +10,7 @@ class QByteArray;
|
||||
class QGroupBox;
|
||||
class QListWidget;
|
||||
class QMoveEvent;
|
||||
class QResizeEvent;
|
||||
class QToolButton;
|
||||
class QSettings;
|
||||
class QWidget;
|
||||
@@ -48,6 +49,7 @@ protected:
|
||||
void AddGameDirectoryItem(const QString& dir);
|
||||
|
||||
void moveEvent(QMoveEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
static QByteArray g_last_geometry;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include <memory>
|
||||
@@ -14,7 +15,7 @@ class QTreeWidget;
|
||||
class QWidget;
|
||||
|
||||
class Configuration;
|
||||
class ConfigurationItem: public QTreeWidgetItem {
|
||||
class ConfigurationItem: public QObject, public QTreeWidgetItem {
|
||||
public:
|
||||
explicit ConfigurationItem(std::unique_ptr<Configuration> info, QTreeWidget* parent);
|
||||
~ConfigurationItem() override;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
class ConfigurationItem;
|
||||
class CompatibilityDatabase;
|
||||
class QEvent;
|
||||
class QTreeWidgetItem;
|
||||
class QPoint;
|
||||
class MainDialog;
|
||||
@@ -52,6 +53,9 @@ signals:
|
||||
void Run();
|
||||
void Select();
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
public slots:
|
||||
void WriteSettings();
|
||||
void ReadSettings();
|
||||
@@ -74,6 +78,7 @@ private:
|
||||
void ClearCustomSettings(ConfigurationItem* item);
|
||||
void SelectItem(QTreeWidgetItem* witem);
|
||||
void ApplyCompatibility();
|
||||
void UpdateToolbarIcons();
|
||||
[[nodiscard]] bool HasValidGameDirectory() const;
|
||||
|
||||
ConfigurationItem* m_selected_item = nullptr;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef GAME_LIST_TREE_WIDGET_H
|
||||
#define GAME_LIST_TREE_WIDGET_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
@@ -44,7 +43,9 @@ protected:
|
||||
painter.fillRect(event->rect(), palette().brush(QPalette::Base));
|
||||
} else {
|
||||
painter.drawPixmap(m_scaled_pos, m_scaled);
|
||||
painter.fillRect(event->rect(), QColor(0, 0, 0, 115));
|
||||
auto overlay = palette().color(QPalette::Base);
|
||||
overlay.setAlpha(185);
|
||||
painter.fillRect(event->rect(), overlay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef LAUNCHER_THEME_H
|
||||
#define LAUNCHER_THEME_H
|
||||
|
||||
class QApplication;
|
||||
|
||||
namespace LauncherTheme {
|
||||
|
||||
void Initialize(QApplication& application);
|
||||
|
||||
} // namespace LauncherTheme
|
||||
|
||||
#endif // LAUNCHER_THEME_H
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef UPDATE_CHECKER_H
|
||||
#define UPDATE_CHECKER_H
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
class QByteArray;
|
||||
class QWidget;
|
||||
|
||||
class UpdateChecker final: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UpdateChecker(QWidget* parent);
|
||||
|
||||
[[nodiscard]] static bool IsSupported();
|
||||
void Check(bool manual);
|
||||
|
||||
signals:
|
||||
void CheckingChanged(bool checking);
|
||||
|
||||
private:
|
||||
struct UpdateInfo;
|
||||
|
||||
static UpdateInfo ParseUpdateInfo(const QByteArray& data);
|
||||
void FetchUpdateInfo(const char* url, bool fallback, bool manual);
|
||||
void ShowUpdateResult(const UpdateInfo& info, bool manual);
|
||||
|
||||
QWidget* m_parent = nullptr;
|
||||
QNetworkAccessManager m_network;
|
||||
bool m_checking_updates = false;
|
||||
};
|
||||
|
||||
#endif // UPDATE_CHECKER_H
|
||||
@@ -5,4 +5,7 @@
|
||||
<file alias="input-mapping.svg">icons/input-mapping.svg</file>
|
||||
<file alias="remove-configuration.svg">icons/remove-configuration.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/styles">
|
||||
<file alias="launcher.qss">styles/launcher.qss</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <QSettings>
|
||||
#include <QSpinBox>
|
||||
#include <QStyle>
|
||||
@@ -115,7 +116,10 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
||||
m_ui->lineEdit_printf_file->setEnabled(log == Configuration::LogDirection::File);
|
||||
});
|
||||
|
||||
layout()->setSizeConstraint(QLayout::SetFixedSize);
|
||||
// Keep the controls at a usable minimum while allowing the settings window
|
||||
// and its expanding fields to use any additional space the user gives them.
|
||||
layout()->setSizeConstraint(QLayout::SetMinimumSize);
|
||||
setSizeGripEnabled(true);
|
||||
|
||||
restoreGeometry(g_last_geometry);
|
||||
|
||||
@@ -248,7 +252,8 @@ void ConfigurationEditDialog::SetGameDirectories(const QStringList& dirs) {
|
||||
|
||||
m_game_dirs_group->setVisible(true);
|
||||
update_game_directory_buttons();
|
||||
adjustSize();
|
||||
layout()->activate();
|
||||
resize(size().expandedTo(minimumSizeHint()));
|
||||
}
|
||||
|
||||
QStringList ConfigurationEditDialog::GetGameDirectories() const {
|
||||
@@ -288,6 +293,11 @@ void ConfigurationEditDialog::moveEvent(QMoveEvent* event) {
|
||||
g_last_geometry = saveGeometry();
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::resizeEvent(QResizeEvent* event) {
|
||||
QDialog::resizeEvent(event);
|
||||
g_last_geometry = saveGeometry();
|
||||
}
|
||||
|
||||
static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
||||
info.user_name = ui.lineEdit_user_name->text().trimmed();
|
||||
info.user_id = ui.spinBox_user_id->value();
|
||||
|
||||
@@ -127,10 +127,12 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
|
||||
layout->setSpacing(6);
|
||||
|
||||
m_status_indicator = new QLabel(m_status_widget);
|
||||
m_status_indicator->setObjectName(QStringLiteral("game_status_indicator"));
|
||||
m_status_indicator->setFixedSize(QSize(12, 12));
|
||||
layout->addWidget(m_status_indicator);
|
||||
|
||||
m_status_combo = new NoWheelComboBox(m_status_widget);
|
||||
m_status_combo->setObjectName(QStringLiteral("game_status_editor"));
|
||||
MakeTransparent(m_status_combo);
|
||||
AddStatus(m_status_combo, Configuration::GameStatus::Unknown);
|
||||
AddStatus(m_status_combo, Configuration::GameStatus::MainMenu);
|
||||
@@ -139,26 +141,16 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
|
||||
AddStatus(m_status_combo, Configuration::GameStatus::DoesntBoot);
|
||||
m_status_combo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
m_status_combo->setFixedWidth(125);
|
||||
m_status_combo->setStyleSheet(QStringLiteral(
|
||||
"QComboBox { background: transparent; border: 0; color: #eef4ff; padding-left: 2px; }"
|
||||
"QComboBox:focus { background: rgba(255,255,255,35); border-radius: 3px; }"
|
||||
"QComboBox::drop-down { border: 0; }"
|
||||
"QComboBox QAbstractItemView { background: #181c24; color: #eef4ff; "
|
||||
"selection-background-color: #267bd8; }"));
|
||||
layout->addWidget(m_status_combo);
|
||||
layout->addStretch(1);
|
||||
|
||||
parent->setItemWidget(this, StatusColumn, m_status_widget);
|
||||
|
||||
m_comment_edit = new QLineEdit(parent);
|
||||
m_comment_edit->setObjectName(QStringLiteral("game_comment_editor"));
|
||||
MakeTransparent(m_comment_edit);
|
||||
m_comment_edit->setClearButtonEnabled(true);
|
||||
m_comment_edit->setFrame(false);
|
||||
m_comment_edit->setStyleSheet(QStringLiteral(
|
||||
"QLineEdit { background: transparent; border: 0; color: #eef4ff; padding-left: 2px; "
|
||||
"selection-background-color: #267bd8; }"
|
||||
"QLineEdit:focus { background: rgba(255,255,255,35); "
|
||||
"border: 1px solid rgba(255,255,255,90); border-radius: 3px; }"));
|
||||
parent->setItemWidget(this, CommentsColumn, m_comment_edit);
|
||||
|
||||
Update();
|
||||
@@ -260,8 +252,6 @@ void ConfigurationItem::UpdateIcon() {
|
||||
|
||||
void ConfigurationItem::UpdateStatusIndicator() {
|
||||
m_status_indicator->setStyleSheet(
|
||||
QStringLiteral(
|
||||
"background-color: %1; border: 1px solid rgba(255, 255, 255, 90); border-radius: 6px;")
|
||||
.arg(GetStatusColor(m_info->game_status)));
|
||||
QStringLiteral("background-color: %1;").arg(GetStatusColor(m_info->game_status)));
|
||||
m_status_indicator->setToolTip(GetStatusText(m_info->game_status));
|
||||
}
|
||||
@@ -30,6 +30,8 @@
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
@@ -131,7 +133,7 @@ static QString GetPic0Path(const Configuration& info) {
|
||||
return QFileInfo::exists(path) ? QFileInfo(path).absoluteFilePath() : QString();
|
||||
}
|
||||
|
||||
static void ApplyGameListStyle(Ui::ConfigurationListWidget* ui) {
|
||||
static void ConfigureGameList(Ui::ConfigurationListWidget* ui) {
|
||||
ui->cfgs_list->setAlternatingRowColors(false);
|
||||
ui->cfgs_list->setAllColumnsShowFocus(true);
|
||||
ui->cfgs_list->setIndentation(0);
|
||||
@@ -142,35 +144,6 @@ static void ApplyGameListStyle(Ui::ConfigurationListWidget* ui) {
|
||||
ui->cfgs_list->header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
ui->cfgs_list->header()->setHighlightSections(false);
|
||||
ui->cfgs_list->header()->setStretchLastSection(true);
|
||||
|
||||
ui->cfgs_list->setStyleSheet(QStringLiteral(
|
||||
"QTreeWidget { background: transparent; border: 1px solid rgba(255,255,255,42); "
|
||||
"color: #eef4ff; outline: 0; }"
|
||||
"QTreeWidget::item { background: rgba(8,10,16,92); "
|
||||
"border-bottom: 1px solid rgba(255,255,255,24); padding: 4px; }"
|
||||
"QTreeWidget::item:selected { background: rgba(32,118,210,150); color: #ffffff; }"
|
||||
"QHeaderView::section { background: rgba(22,25,31,235); color: #dce6f6; border: 0; "
|
||||
"border-right: 1px solid rgba(255,255,255,35); border-bottom: 1px solid "
|
||||
"rgba(255,255,255,45); "
|
||||
"padding: 5px 6px; }"
|
||||
"QScrollBar:vertical { background: rgba(12,14,18,150); width: 14px; margin: 0; }"
|
||||
"QScrollBar::handle:vertical { background: rgba(220,230,245,120); min-height: 36px; }"
|
||||
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }"));
|
||||
|
||||
ui->search_line_edit->setStyleSheet(QStringLiteral(
|
||||
"QLineEdit { background: rgba(12,14,18,210); border: 1px solid rgba(255,255,255,42); "
|
||||
"border-radius: 4px; color: #eef4ff; padding: 5px 8px; selection-background-color: "
|
||||
"#267bd8; }"
|
||||
"QLineEdit:focus { border-color: rgba(80,160,255,180); }"));
|
||||
ui->global_settings_button->setStyleSheet(QStringLiteral(
|
||||
"QToolButton { background: rgba(255,255,255,18); border: 1px solid transparent; "
|
||||
"border-radius: 5px; padding: 3px; }"
|
||||
"QToolButton:hover { background: rgba(255,255,255,45); border-color: rgba(255,255,255,70); "
|
||||
"}"
|
||||
"QToolButton:disabled { background: transparent; }"));
|
||||
ui->edit_button->setStyleSheet(ui->global_settings_button->styleSheet());
|
||||
ui->delete_button->setStyleSheet(ui->global_settings_button->styleSheet());
|
||||
ui->input_mapping_button->setStyleSheet(ui->global_settings_button->styleSheet());
|
||||
}
|
||||
|
||||
static void AddSaveDataDir(QStringList* dirs, QSet<QString>* seen, const QString& root,
|
||||
@@ -226,14 +199,11 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
|
||||
m_compatibility = new CompatibilityDatabase(
|
||||
QCoreApplication::arguments().contains(QStringLiteral("--local")), this);
|
||||
m_ui->setupUi(this);
|
||||
ApplyGameListStyle(m_ui);
|
||||
ConfigureGameList(m_ui);
|
||||
|
||||
m_ui->global_settings_button->setIcon(QIcon(QStringLiteral(":/icons/global-settings.svg")));
|
||||
UpdateToolbarIcons();
|
||||
m_ui->global_settings_button->setToolTip(tr("Edit global settings and game folders"));
|
||||
m_ui->input_mapping_button->setIcon(QIcon(QStringLiteral(":/icons/input-mapping.svg")));
|
||||
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
|
||||
m_ui->edit_button->setIcon(QIcon(QStringLiteral(":/icons/edit-configuration.svg")));
|
||||
m_ui->delete_button->setIcon(QIcon(QStringLiteral(":/icons/remove-configuration.svg")));
|
||||
|
||||
m_ui->delete_button->setEnabled(false);
|
||||
m_ui->edit_button->setEnabled(false);
|
||||
@@ -287,6 +257,30 @@ ConfigurationListWidget::~ConfigurationListWidget() {
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::changeEvent(QEvent* event) {
|
||||
QWidget::changeEvent(event);
|
||||
if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::PaletteChange) {
|
||||
UpdateToolbarIcons();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::UpdateToolbarIcons() {
|
||||
const auto color = palette().color(QPalette::Window).lightness() < 128 ? QColor(Qt::white)
|
||||
: QColor(Qt::black);
|
||||
const auto set_icon = [&color](QToolButton* button, const QString& resource) {
|
||||
auto pixmap = QIcon(resource).pixmap(button->iconSize(), button->devicePixelRatioF());
|
||||
QPainter painter(&pixmap);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(pixmap.rect(), color);
|
||||
button->setIcon(QIcon(pixmap));
|
||||
};
|
||||
|
||||
set_icon(m_ui->global_settings_button, QStringLiteral(":/icons/global-settings.svg"));
|
||||
set_icon(m_ui->input_mapping_button, QStringLiteral(":/icons/input-mapping.svg"));
|
||||
set_icon(m_ui->edit_button, QStringLiteral(":/icons/edit-configuration.svg"));
|
||||
set_icon(m_ui->delete_button, QStringLiteral(":/icons/remove-configuration.svg"));
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::WriteSettings() {
|
||||
QFile file = QFile(QDir(".").absoluteFilePath(CONF_FILE_NAME));
|
||||
std::unique_ptr<QSettings> s;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "launcherTheme.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
namespace {
|
||||
|
||||
class PaletteChangeFilter final: public QObject {
|
||||
public:
|
||||
explicit PaletteChangeFilter(QApplication& application)
|
||||
: QObject(&application), m_application(application) {}
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* watched, QEvent* event) override {
|
||||
if (event->type() == QEvent::ApplicationPaletteChange && !m_refresh_pending) {
|
||||
m_refresh_pending = true;
|
||||
QTimer::singleShot(0, &m_application, [this] {
|
||||
m_application.setStyleSheet(m_application.styleSheet());
|
||||
m_refresh_pending = false;
|
||||
});
|
||||
}
|
||||
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
private:
|
||||
QApplication& m_application;
|
||||
bool m_refresh_pending = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace LauncherTheme {
|
||||
|
||||
void Initialize(QApplication& application) {
|
||||
QFile style_sheet_file(QStringLiteral(":/styles/launcher.qss"));
|
||||
if (style_sheet_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
application.setStyleSheet(QString::fromUtf8(style_sheet_file.readAll()));
|
||||
application.installEventFilter(new PaletteChangeFilter(application));
|
||||
}
|
||||
}
|
||||
} // namespace LauncherTheme
|
||||
@@ -1,18 +1,15 @@
|
||||
#include "mainDialog.h"
|
||||
#include "launcherTheme.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QArgument>
|
||||
#include <QObject>
|
||||
#include <QStyleFactory>
|
||||
|
||||
class QStyle;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
MainDialog w;
|
||||
LauncherTheme::Initialize(a);
|
||||
|
||||
QStyle* s = QStyleFactory::create("fusion");
|
||||
QApplication::setStyle(s);
|
||||
MainDialog w;
|
||||
|
||||
QObject::connect(&a, &QApplication::aboutToQuit, &w, &MainDialog::Quit);
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
#include "configurationItem.h"
|
||||
#include "configurationListWidget.h"
|
||||
#include "patchesDialog.h"
|
||||
#include "updateChecker.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QByteArray>
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
@@ -15,6 +17,7 @@
|
||||
#include <QIODevice>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPointer>
|
||||
#include <QProcess>
|
||||
#include <QRadioButton>
|
||||
#include <QRegularExpression>
|
||||
@@ -53,6 +56,7 @@ constexpr DWORD CMD_Y_CHARS = 1000;
|
||||
#endif
|
||||
constexpr char SETTINGS_MAIN_DIALOG[] = "MainDialog";
|
||||
constexpr char SETTINGS_MAIN_LAST_GEOMETRY[] = "geometry";
|
||||
constexpr char SETTINGS_CHECK_UPDATES[] = "check_updates_on_startup";
|
||||
|
||||
class DetachableProcess: public QProcess {
|
||||
Q_OBJECT;
|
||||
@@ -88,17 +92,20 @@ public:
|
||||
|
||||
private:
|
||||
static QByteArray g_last_geometry;
|
||||
static bool g_check_updates_on_startup;
|
||||
|
||||
Ui::MainDialog* m_ui = {nullptr};
|
||||
MainDialog* m_main_dialog = nullptr;
|
||||
Ui::MainDialog* m_ui = {nullptr};
|
||||
MainDialog* m_main_dialog = nullptr;
|
||||
UpdateChecker* m_update_checker = nullptr;
|
||||
QString m_interpreter;
|
||||
|
||||
/*DetachableProcess*/ QProcess m_process;
|
||||
|
||||
ConfigurationItem* m_running_item = nullptr;
|
||||
QPointer<ConfigurationItem> m_running_item;
|
||||
};
|
||||
|
||||
QByteArray MainDialogPrivate::g_last_geometry;
|
||||
bool MainDialogPrivate::g_check_updates_on_startup = true;
|
||||
|
||||
MainDialog::MainDialog(QWidget* parent): QDialog(parent), m_p(new MainDialogPrivate(this)) {
|
||||
m_p->Setup(this);
|
||||
@@ -113,7 +120,11 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
m_ui->setupUi(main_dialog);
|
||||
|
||||
m_main_dialog = main_dialog;
|
||||
m_update_checker = new UpdateChecker(main_dialog);
|
||||
m_ui->widget->SetMainDialog(main_dialog);
|
||||
m_ui->check_updates_on_startup->setChecked(g_check_updates_on_startup);
|
||||
m_ui->check_updates_link->setVisible(UpdateChecker::IsSupported());
|
||||
m_ui->check_updates_on_startup->setVisible(UpdateChecker::IsSupported());
|
||||
|
||||
main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/);
|
||||
|
||||
@@ -121,6 +132,14 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
Qt::QueuedConnection);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Run, this, &MainDialogPrivate::Run);
|
||||
connect(m_ui->check_updates_link, &QLabel::linkActivated, this,
|
||||
[this](const QString&) { m_update_checker->Check(true); });
|
||||
connect(m_update_checker, &UpdateChecker::CheckingChanged, m_ui->check_updates_link,
|
||||
&QLabel::setDisabled);
|
||||
connect(m_ui->check_updates_on_startup, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
g_check_updates_on_startup = checked;
|
||||
m_ui->widget->WriteSettings();
|
||||
});
|
||||
connect(main_dialog, &MainDialog::Resize, [this]() {
|
||||
g_last_geometry = m_main_dialog->saveGeometry();
|
||||
m_ui->widget->WriteSettings();
|
||||
@@ -188,6 +207,9 @@ void MainDialogPrivate::FindInterpreter() {
|
||||
m_ui->label_settings_file->setText(tr("Settings file: ") + m_ui->widget->GetSettingsFile());
|
||||
|
||||
Update();
|
||||
if (m_ui->check_updates_on_startup->isChecked()) {
|
||||
m_update_checker->Check(false);
|
||||
}
|
||||
}
|
||||
|
||||
static QString BoolArg(bool value) {
|
||||
@@ -447,6 +469,7 @@ void MainDialogPrivate::WriteSettings(QSettings& s) {
|
||||
if (!g_last_geometry.isEmpty()) {
|
||||
s.setValue(SETTINGS_MAIN_LAST_GEOMETRY, g_last_geometry);
|
||||
}
|
||||
s.setValue(SETTINGS_CHECK_UPDATES, g_check_updates_on_startup);
|
||||
|
||||
s.endGroup();
|
||||
}
|
||||
@@ -455,6 +478,7 @@ void MainDialogPrivate::ReadSettings(QSettings& s) {
|
||||
s.beginGroup(SETTINGS_MAIN_DIALOG);
|
||||
|
||||
g_last_geometry = s.value(SETTINGS_MAIN_LAST_GEOMETRY, g_last_geometry).toByteArray();
|
||||
g_check_updates_on_startup = s.value(SETTINGS_CHECK_UPDATES, true).toBool();
|
||||
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "updateChecker.h"
|
||||
|
||||
#include "kytyGitVersion.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char DEFAULT_FEED_URL[] = "https://kytyps5.github.io/data/updates.json";
|
||||
constexpr char FALLBACK_FEED_URL[] =
|
||||
"https://api.github.com/repos/KytyPS5/KytyPS5/releases/latest";
|
||||
|
||||
} // namespace
|
||||
|
||||
struct UpdateChecker::UpdateInfo {
|
||||
QString tag;
|
||||
QUrl page_url;
|
||||
QString error;
|
||||
};
|
||||
|
||||
UpdateChecker::UpdateChecker(QWidget* parent): QObject(parent), m_parent(parent), m_network(this) {}
|
||||
|
||||
bool UpdateChecker::IsSupported() {
|
||||
#if defined(KYTY_OFFICIAL_BUILD) && defined(NDEBUG)
|
||||
return !QString::fromLatin1(KYTY_GIT_HASH).endsWith(QStringLiteral("-dirty"));
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdateChecker::UpdateInfo UpdateChecker::ParseUpdateInfo(const QByteArray& data) {
|
||||
const auto document = QJsonDocument::fromJson(data);
|
||||
if (!document.isObject()) {
|
||||
return {{}, {}, tr("Invalid update feed")};
|
||||
}
|
||||
|
||||
const auto root = document.object();
|
||||
UpdateInfo info {root.value(QStringLiteral("tag")).toString(),
|
||||
QUrl(root.value(QStringLiteral("html_url")).toString()), {}};
|
||||
if (info.tag.isEmpty()) {
|
||||
info.tag = root.value(QStringLiteral("tag_name")).toString();
|
||||
}
|
||||
if (info.tag.isEmpty() || !info.page_url.isValid() ||
|
||||
info.page_url.scheme() != QStringLiteral("https")) {
|
||||
info.error = tr("Incomplete update feed");
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
void UpdateChecker::Check(bool manual) {
|
||||
if (!IsSupported() || m_checking_updates) {
|
||||
return;
|
||||
}
|
||||
m_checking_updates = true;
|
||||
emit CheckingChanged(true);
|
||||
FetchUpdateInfo(DEFAULT_FEED_URL, false, manual);
|
||||
}
|
||||
|
||||
void UpdateChecker::FetchUpdateInfo(const char* url, bool fallback, bool manual) {
|
||||
QNetworkRequest request(QUrl(QString::fromLatin1(url)));
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
request.setRawHeader("User-Agent", "Kyty-Launcher");
|
||||
request.setRawHeader("Accept", "application/vnd.github+json");
|
||||
request.setTransferTimeout(15000);
|
||||
|
||||
auto* reply = m_network.get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, fallback, manual]() {
|
||||
UpdateInfo info;
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
info = ParseUpdateInfo(reply->readAll());
|
||||
} else {
|
||||
info.error = reply->errorString();
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
if (!info.error.isEmpty() && !fallback) {
|
||||
FetchUpdateInfo(FALLBACK_FEED_URL, true, manual);
|
||||
return;
|
||||
}
|
||||
|
||||
m_checking_updates = false;
|
||||
emit CheckingChanged(false);
|
||||
ShowUpdateResult(info, manual);
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateChecker::ShowUpdateResult(const UpdateInfo& info, bool manual) {
|
||||
if (!info.error.isEmpty()) {
|
||||
qWarning() << "Could not check for updates:" << info.error;
|
||||
if (manual) {
|
||||
QMessageBox::warning(m_parent, tr("Update Check"),
|
||||
tr("Could not check for updates:\n%1").arg(info.error));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const bool current = info.tag == QString::fromLatin1(KYTY_RELEASE_TAG);
|
||||
if (current) {
|
||||
if (manual) {
|
||||
QMessageBox::information(m_parent, tr("Update Check"),
|
||||
tr("You are using the latest version (%1).").arg(info.tag));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto message =
|
||||
tr("An update is available.\n\nCurrent: %1\nLatest: %2\n\n"
|
||||
"Open the release page?")
|
||||
.arg(QString::fromLatin1(KYTY_RELEASE_TAG), info.tag);
|
||||
if (QMessageBox::question(m_parent, tr("KytyPS5 Update"), message,
|
||||
QMessageBox::Open | QMessageBox::Cancel,
|
||||
QMessageBox::Open) == QMessageBox::Open) {
|
||||
QDesktopServices::openUrl(info.page_url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
GameListTreeWidget#cfgs_list {
|
||||
background: transparent;
|
||||
border: 1px solid palette(mid);
|
||||
color: palette(text);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
GameListTreeWidget#cfgs_list::item {
|
||||
background: transparent;
|
||||
border-bottom: 1px solid palette(mid);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
GameListTreeWidget#cfgs_list::item:selected {
|
||||
background: palette(highlight);
|
||||
color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
GameListTreeWidget#cfgs_list QHeaderView::section {
|
||||
background: palette(button);
|
||||
color: palette(button-text);
|
||||
border: 0;
|
||||
border-right: 1px solid palette(mid);
|
||||
border-bottom: 1px solid palette(mid);
|
||||
padding: 5px 6px;
|
||||
}
|
||||
|
||||
QLineEdit#search_line_edit {
|
||||
background: palette(base);
|
||||
border: 1px solid palette(mid);
|
||||
border-radius: 4px;
|
||||
color: palette(text);
|
||||
padding: 5px 8px;
|
||||
selection-background-color: palette(highlight);
|
||||
selection-color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
QLineEdit#search_line_edit:focus {
|
||||
border-color: palette(highlight);
|
||||
}
|
||||
|
||||
QToolButton#global_settings_button,
|
||||
QToolButton#input_mapping_button,
|
||||
QToolButton#edit_button,
|
||||
QToolButton#delete_button {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
QToolButton#global_settings_button:hover,
|
||||
QToolButton#input_mapping_button:hover,
|
||||
QToolButton#edit_button:hover,
|
||||
QToolButton#delete_button:hover {
|
||||
background: palette(button);
|
||||
border-color: palette(mid);
|
||||
}
|
||||
|
||||
QToolButton#global_settings_button:pressed,
|
||||
QToolButton#input_mapping_button:pressed,
|
||||
QToolButton#edit_button:pressed,
|
||||
QToolButton#delete_button:pressed {
|
||||
background: palette(midlight);
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: palette(text);
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor:focus {
|
||||
background: palette(button);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor::drop-down {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
QComboBox#game_status_editor QAbstractItemView {
|
||||
background: palette(base);
|
||||
color: palette(text);
|
||||
selection-background-color: palette(highlight);
|
||||
selection-color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
QLineEdit#game_comment_editor {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: palette(text);
|
||||
padding-left: 2px;
|
||||
selection-background-color: palette(highlight);
|
||||
selection-color: palette(highlighted-text);
|
||||
}
|
||||
|
||||
QLineEdit#game_comment_editor:focus {
|
||||
background: palette(base);
|
||||
border: 1px solid palette(mid);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QLabel#game_status_indicator {
|
||||
border: 1px solid palette(base);
|
||||
border-radius: 6px;
|
||||
}
|
||||
@@ -23,8 +23,8 @@ LIB_NAME("Pad", "Pad");
|
||||
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
|
||||
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
|
||||
|
||||
// SDL limits rumble commands to 0xffff ms; zero strengths stop immediately.
|
||||
constexpr uint32_t RUMBLE_DURATION_MS = 0xffff;
|
||||
constexpr uint32_t RELEASE_FLUSH_MS = 50;
|
||||
|
||||
struct PadControllerInformation {
|
||||
float touch_pixel_density;
|
||||
@@ -102,6 +102,7 @@ public:
|
||||
void RightStick(int id, int x, int y);
|
||||
void TouchPad(int id, int finger, bool down, float x, float y);
|
||||
void ResetInputState();
|
||||
void ReleaseHostPads();
|
||||
void GetConnectionInfo(bool* flag, int* count);
|
||||
void SetVibration(uint8_t large_motor, uint8_t small_motor);
|
||||
void SetLightBar(uint8_t r, uint8_t g, uint8_t b);
|
||||
@@ -252,10 +253,17 @@ void Initialize() {
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
EmergencyShutdown();
|
||||
delete g_controller;
|
||||
g_controller = nullptr;
|
||||
}
|
||||
|
||||
void EmergencyShutdown() {
|
||||
if (g_controller != nullptr) {
|
||||
g_controller->ReleaseHostPads();
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::Connect(int id) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
@@ -404,6 +412,39 @@ void GameController::ResetInputState() {
|
||||
AddState();
|
||||
}
|
||||
|
||||
void GameController::ReleaseHostPads() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
std::vector<SDL_GameController*> pads;
|
||||
for (const auto id: m_connected_ids) {
|
||||
if (id == HOST_INPUT_CONTROLLER_ID) {
|
||||
continue;
|
||||
}
|
||||
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id));
|
||||
pad != nullptr) {
|
||||
if (SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) {
|
||||
DualSenseEffects effect {};
|
||||
effect.enable_bits = 0x0c;
|
||||
effect.right_trigger[0] = 0x05;
|
||||
effect.left_trigger[0] = 0x05;
|
||||
(void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect));
|
||||
}
|
||||
(void)SDL_GameControllerRumble(pad, 0, 0, 0);
|
||||
(void)SDL_GameControllerSetLED(pad, 0, 0, 0);
|
||||
pads.push_back(pad);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pads.empty()) {
|
||||
SDL_Delay(RELEASE_FLUSH_MS);
|
||||
for (auto* pad: pads) {
|
||||
SDL_GameControllerClose(pad);
|
||||
}
|
||||
}
|
||||
|
||||
m_connected_ids.clear();
|
||||
}
|
||||
|
||||
void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ namespace Libs::Controller {
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
void EmergencyShutdown();
|
||||
|
||||
struct Lifecycle {
|
||||
static constexpr const char* name = "Controller";
|
||||
static constexpr auto initialize = Libs::Controller::Initialize;
|
||||
static constexpr auto shutdown = Libs::Controller::Shutdown;
|
||||
static constexpr const char* name = "Controller";
|
||||
static constexpr auto initialize = Libs::Controller::Initialize;
|
||||
static constexpr auto shutdown = Libs::Controller::Shutdown;
|
||||
static constexpr auto emergency_shutdown = Libs::Controller::EmergencyShutdown;
|
||||
};
|
||||
|
||||
constexpr int HOST_INPUT_CONTROLLER_ID = -1000;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -69,21 +69,22 @@ struct Call9 {
|
||||
template <class Handler>
|
||||
void SetFunc(Handler func) {
|
||||
auto func_addr = reinterpret_cast<int64_t>(reinterpret_cast<void*>(func));
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[5]);
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[6]);
|
||||
auto offset64 = func_addr - rip_addr;
|
||||
auto offset32 = static_cast<uint32_t>(static_cast<uint64_t>(offset64) & 0xffffffffu);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&code[1]) = offset32;
|
||||
*reinterpret_cast<uint32_t*>(&code[2]) = offset32;
|
||||
}
|
||||
|
||||
void SetOutputReg(uint8_t reg) { code[7] = 0xc0u | (reg & 7u); }
|
||||
void SetOutputReg(uint8_t reg) { code[8] = 0xc0u | (reg & 7u); }
|
||||
|
||||
static uint64_t GetSize() { return 9; }
|
||||
|
||||
// call func
|
||||
// rex.w; call func
|
||||
// mov rax,rax
|
||||
// nop
|
||||
uint8_t code[9] = {0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC0, 0x90};
|
||||
// REX.W is required because AMD processors honor a retained 0x66 operand-size
|
||||
// prefix on a near call and would otherwise execute callw.
|
||||
uint8_t code[9] = {0x48, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC0};
|
||||
};
|
||||
|
||||
struct TlsRegStub {
|
||||
|
||||
@@ -1143,7 +1143,8 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!program->elf->IsShared() && program->tls.handler_vaddr != 0) {
|
||||
if (!program->elf->IsShared() && program->tls.handler_vaddr != 0 &&
|
||||
size >= Jit::Call9::GetSize()) {
|
||||
// Replace:
|
||||
// 66 66 66
|
||||
// mov <reg>, qword ptr fs:[0x00]
|
||||
@@ -1151,7 +1152,8 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) {
|
||||
// call <handler>
|
||||
// mov <reg>,rax
|
||||
// nop ...
|
||||
const uint8_t tls_pattern[5] = {0x64, 0x48, 0x8B, 0x00, 0x25};
|
||||
const uint8_t tls_pattern[5] = {0x64, 0x48, 0x8B, 0x00, 0x25};
|
||||
const uint8_t zero_displacement[4] = {};
|
||||
|
||||
EXIT_IF(Jit::Call9::GetSize() != 9);
|
||||
|
||||
@@ -1173,14 +1175,15 @@ static void PatchProgram(Program* program, uint64_t address, uint64_t size) {
|
||||
const uint8_t modrm = inst_ptr[3];
|
||||
if (memcmp(inst_ptr, tls_pattern, 3) == 0 && (modrm & 0xc7u) == 0x04u &&
|
||||
inst_ptr[4] == tls_pattern[4] &&
|
||||
*reinterpret_cast<const uint32_t*>(inst_ptr + 5) == 0) {
|
||||
LOGF("Patch tls at addr: [%016" PRIx64 "]\n",
|
||||
reinterpret_cast<uint64_t>(inst_ptr));
|
||||
memcmp(inst_ptr + 5, zero_displacement, sizeof(zero_displacement)) == 0) {
|
||||
LOGF("Patch tls at addr: [%016" PRIx64 "]\n", reinterpret_cast<uint64_t>(inst_ptr));
|
||||
|
||||
const auto reg = (modrm >> 3u) & 7u;
|
||||
EXIT_NOT_IMPLEMENTED(reg == 4u);
|
||||
|
||||
// A raw scan can encounter a 0x66 in the preceding instruction.
|
||||
// A raw scan can encounter a 0x66 in the preceding instruction, so do not
|
||||
// overwrite it. Call9 starts with REX.W to neutralize genuine 0x66
|
||||
// prefixes on AMD processors (before it could turn E8 into callw 16bit).
|
||||
auto* code = new (inst_ptr) Jit::Call9;
|
||||
code->SetFunc(reg == 0
|
||||
? program->tls.handler_vaddr
|
||||
|
||||
@@ -70,7 +70,6 @@ void CheckSaveRename(const std::filesystem::path &root,
|
||||
Common::File result(root / "SDATA000.DAT", Common::File::Mode::Read);
|
||||
Check(!result.IsInvalid(), "open renamed save file");
|
||||
const auto data = result.ReadWholeBuffer();
|
||||
result.Close();
|
||||
const std::string expected = std::string(payload) + Suffix;
|
||||
Check(data.Size() == expected.size(), "renamed save size");
|
||||
Check(std::memcmp(data.GetData(), expected.data(), expected.size()) == 0,
|
||||
|
||||
@@ -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"
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||