mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 11:26:56 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16a7fe689a | |||
| 6f0cddd440 | |||
| 4084a008ea | |||
| c0ebf22476 | |||
| cb375ec35a | |||
| 6f3bebcb50 | |||
| 1029511bbc | |||
| 65144ce4ac | |||
| f848d2761b |
+8
-3
@@ -40,6 +40,11 @@ if (NOT (WIN32 OR LINUX OR APPLE))
|
||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Prevent Windows headers from defining min/max macros in all targets.
|
||||
add_compile_definitions(NOMINMAX)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
@@ -399,7 +404,7 @@ add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
@@ -467,7 +472,7 @@ add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(resource_materialization_tests fmt::fmt)
|
||||
target_link_libraries(resource_materialization_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
@@ -480,7 +485,7 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
|
||||
@@ -182,4 +182,8 @@ const Keymap& GetKeymap() {
|
||||
return g_config->keymap;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetGuestArguments() {
|
||||
return g_config->guest_arguments;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
|
||||
@@ -83,6 +83,8 @@ struct ConfigOptions {
|
||||
bool red_zone_protection_enabled = false;
|
||||
#endif
|
||||
Keymap keymap;
|
||||
// Ordinary boots need a non-null argv[0]; --guest-exec supplies the caller's arguments.
|
||||
std::vector<std::string> guest_arguments {""};
|
||||
};
|
||||
|
||||
void Load(const ConfigOptions& cfg);
|
||||
@@ -136,6 +138,7 @@ bool RedZoneProtectionEnabled();
|
||||
#endif
|
||||
|
||||
const Keymap& GetKeymap();
|
||||
const std::vector<std::string>& GetGuestArguments();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ struct RunOptions {
|
||||
};
|
||||
|
||||
void Run(const RunOptions& options);
|
||||
int LoadExec(const char* path, const char* const argv[]);
|
||||
|
||||
} // namespace Emulator
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ struct GraphicContext {
|
||||
// bool fp64_denorm_preserve = false; // Temporarily disabled.
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
bool depth_color_copy_enabled = false;
|
||||
bool supports_block_texel_view = false;
|
||||
bool mesh_shader_enabled = false;
|
||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||
|
||||
@@ -394,26 +394,20 @@ static void RcPrint(const char* func, const HW::RenderControl& c) {
|
||||
|
||||
LOGF("\t depth_clear_enable = %s\n"
|
||||
"\t stencil_clear_enable = %s\n"
|
||||
"\t copy_depth_to_color = %s\n"
|
||||
"\t copy_stencil_to_color = %s\n"
|
||||
"\t resummarize_enable = %s\n"
|
||||
"\t stencil_compress_disable = %s\n"
|
||||
"\t depth_compress_disable = %s\n"
|
||||
"\t copy_centroid = %s\n"
|
||||
"\t copy_sample = %" PRIu8 "\n",
|
||||
c.depth_clear_enable ? "true" : "false", c.stencil_clear_enable ? "true" : "false",
|
||||
c.copy_depth_to_color ? "true" : "false", c.copy_stencil_to_color ? "true" : "false",
|
||||
c.resummarize_enable ? "true" : "false", c.stencil_compress_disable ? "true" : "false",
|
||||
c.depth_compress_disable ? "true" : "false", c.copy_centroid ? "true" : "false",
|
||||
c.copy_sample);
|
||||
}
|
||||
|
||||
static void RcCheck(const HW::RenderControl& c) {
|
||||
// EXIT_NOT_IMPLEMENTED(c.depth_clear_enable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.stencil_clear_enable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.stencil_compress_disable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.depth_compress_disable != false);
|
||||
EXIT_NOT_IMPLEMENTED(c.copy_centroid != false);
|
||||
EXIT_NOT_IMPLEMENTED(c.copy_sample != 0);
|
||||
}
|
||||
|
||||
static void McPrint(const char* func, const HW::ModeControl& c) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
@@ -869,9 +863,6 @@ void hw_check(const CommandBuffer& buffer) {
|
||||
VpCheck(vp, smc);
|
||||
log_phase("clip");
|
||||
ClipCheck(c);
|
||||
log_phase("rc");
|
||||
RcCheck(rc);
|
||||
log_phase("depth");
|
||||
log_phase("mode");
|
||||
McCheck(mc);
|
||||
log_phase("blend");
|
||||
|
||||
@@ -298,13 +298,20 @@ void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepth
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
|
||||
rc.copy_sample != 0 || dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
if (rc.copy_stencil_to_color ||
|
||||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
|
||||
(has_stencil && !z.depth_view.stencil_write_disable &&
|
||||
z.stencil_write_base_addr != z.stencil_read_base_addr)) {
|
||||
DepthFatal("unsupported depth register state");
|
||||
}
|
||||
if (rc.copy_depth_to_color &&
|
||||
(z.z_info.num_samples != 0 || rc.copy_sample != 0 ||
|
||||
z.z_info.format != Prospero::DepthFormat::kZ32F ||
|
||||
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || dc.stencil_enable ||
|
||||
rc.depth_clear_enable || rc.stencil_clear_enable)) {
|
||||
DepthFatal("unsupported depth-to-color copy state");
|
||||
}
|
||||
r.desc = MakeDepthTargetDesc(buffer, z);
|
||||
r.depth_clear_enable = rc.depth_clear_enable;
|
||||
r.depth_load_clear_enable = r.depth_clear_enable;
|
||||
|
||||
@@ -186,6 +186,8 @@ private:
|
||||
bool ignore_target_mask = false, bool exact_format = false);
|
||||
void ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& target);
|
||||
[[nodiscard]] bool DepthStencilCopy(CommandBuffer& buffer);
|
||||
[[nodiscard]] bool CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit, DrawRenderState& state);
|
||||
[[nodiscard]] bool PrepareDrawRenderState(CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
|
||||
@@ -1029,12 +1029,71 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit, DrawRenderState& state) {
|
||||
if (!buffer.GetRegisters().GetRenderControl().copy_depth_to_color) {
|
||||
return false;
|
||||
}
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
auto& depth = state.depth_info;
|
||||
auto& color = state.color_info[0];
|
||||
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().depth_color_copy_enabled ||
|
||||
!Prospero::IsRectList(ucfg.GetPrimType()) ||
|
||||
draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 ||
|
||||
emit.first_vertex != 0 || emit.first_instance != 0 ||
|
||||
state.color_count != 1 || color.target_slot != 0 || !depth.image_id ||
|
||||
color.desc.info.samples != 1 ||
|
||||
depth.desc.info.extent.width != color.Extent().width ||
|
||||
depth.desc.info.extent.height != color.Extent().height ||
|
||||
depth.desc.view_info.layer_count != 1 ||
|
||||
color.desc.view_info.layer_count != 1 ||
|
||||
hw.GetBlendControl(0).enable || state.ps_input_info.ps_pixel_kill_enable ||
|
||||
state.ps_input_info.ps_depth_export_enable ||
|
||||
state.ps_input_info.ps_sample_mask_export_enable);
|
||||
vk::ImageAspectFlags feedback;
|
||||
const auto rendering = AcquireRenderTargets(buffer, &color, 1, depth, feedback);
|
||||
if (depth.depth_load_clear_enable) {
|
||||
m_context.GetCommandScheduler().BeginRendering(rendering);
|
||||
}
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
auto& source = cache.GetImage(depth.image_id);
|
||||
auto& destination = cache.GetImage(color.image_id);
|
||||
EXIT_NOT_IMPLEMENTED((source.backing.format != vk::Format::eD32Sfloat &&
|
||||
source.backing.format != vk::Format::eD32SfloatS8Uint) ||
|
||||
destination.backing.format != vk::Format::eR32Sfloat);
|
||||
const auto rect = calc_final_scissor(hw.GetScreenViewport(), hw.GetScanModeControl(),
|
||||
{rendering.width, rendering.height}, 0);
|
||||
if (rect.right > rect.left && rect.bottom > rect.top) {
|
||||
const vk::ImageCopy region {
|
||||
{vk::ImageAspectFlagBits::eDepth, depth.desc.view_info.base_level,
|
||||
depth.desc.view_info.base_layer, 1}, {rect.left, rect.top, 0},
|
||||
{vk::ImageAspectFlagBits::eColor, color.desc.view_info.base_level,
|
||||
color.desc.view_info.base_layer, 1}, {rect.left, rect.top, 0},
|
||||
{static_cast<uint32_t>(rect.right - rect.left),
|
||||
static_cast<uint32_t>(rect.bottom - rect.top), 1}};
|
||||
m_context.GetCommandScheduler().EndRendering();
|
||||
const auto command = buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
|
||||
{}, command);
|
||||
destination.Transit(vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
destination.backing.image, vk::ImageLayout::eTransferDstOptimal,
|
||||
1, ®ion);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable) {
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
if (CopyDepthToColor(buffer, draw, emit, state)) {
|
||||
return;
|
||||
}
|
||||
const auto vertex_stages =
|
||||
std::span {state.vertex_info.data(), state.programs.VertexStageCount()};
|
||||
const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh;
|
||||
|
||||
@@ -478,6 +478,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
provoking_vertex.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &provoking_vertex;
|
||||
}
|
||||
const bool maintenance8_extension =
|
||||
HasExtension(device_extensions, VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceMaintenance8FeaturesKHR maintenance8 {};
|
||||
if (maintenance8_extension) {
|
||||
maintenance8.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &maintenance8;
|
||||
}
|
||||
const bool image_atomic_int64_extension =
|
||||
HasExtension(device_extensions, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic_int64 {};
|
||||
@@ -542,6 +549,9 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
graphics.attachment_feedback_loop_enabled =
|
||||
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
||||
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
||||
graphics.depth_color_copy_enabled = maintenance8_extension && maintenance8.maintenance8;
|
||||
LOGF("Vulkan depth/color copy support: %s\n",
|
||||
graphics.depth_color_copy_enabled ? "true" : "false");
|
||||
LOGF("Vulkan depth feedback support: %s\n",
|
||||
graphics.attachment_feedback_loop_enabled ? "true" : "false");
|
||||
if (graphics.mesh_shader_enabled) {
|
||||
@@ -638,6 +648,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
image_atomic_int64.sparseImageInt64Atomics = VK_FALSE;
|
||||
create_info.pNext = &image_atomic_int64;
|
||||
}
|
||||
if (graphics.depth_color_copy_enabled) {
|
||||
maintenance8.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &maintenance8;
|
||||
}
|
||||
create_info.pQueueCreateInfos = &queue_create_info;
|
||||
create_info.queueCreateInfoCount = 1;
|
||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||
@@ -1002,6 +1016,7 @@ void WindowContext::CreateVulkan() {
|
||||
graphic_ctx.memory_budget_ext_enabled = true;
|
||||
}
|
||||
for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
|
||||
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
|
||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <xbyak/xbyak.h>
|
||||
#include <map>
|
||||
#include <new>
|
||||
#include <unordered_map>
|
||||
@@ -99,6 +100,9 @@ bool EquivalentValue(const ResourcePlan& program, Value left, Value right,
|
||||
|
||||
} // namespace
|
||||
|
||||
ResourcePlan::ResourcePlan() = default;
|
||||
ResourcePlan::ResourcePlan(ResourcePlan&&) noexcept = default;
|
||||
|
||||
ResourcePlan::~ResourcePlan() {
|
||||
for (auto& inst: value_storage) {
|
||||
inst.Invalidate();
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Xbyak {
|
||||
class CodeGenerator;
|
||||
}
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
enum class ResourceKind {
|
||||
@@ -573,12 +577,12 @@ struct ResourcePlan {
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
ResourcePlan() = default;
|
||||
ResourcePlan();
|
||||
~ResourcePlan();
|
||||
|
||||
ResourcePlan(const ResourcePlan&) = delete;
|
||||
ResourcePlan& operator=(const ResourcePlan&) = delete;
|
||||
ResourcePlan(ResourcePlan&&) noexcept = default;
|
||||
ResourcePlan(ResourcePlan&&) noexcept;
|
||||
ResourcePlan& operator=(ResourcePlan&& other) noexcept;
|
||||
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
@@ -597,6 +601,7 @@ struct ResourcePlan {
|
||||
bool resource_tracking_complete = false;
|
||||
ShaderInfo info;
|
||||
UniformFillPlan uniform_fill;
|
||||
std::unique_ptr<Xbyak::CodeGenerator> srt_code;
|
||||
// GPU-thread scratch for nested clean/EXEC memos, activity and material keys.
|
||||
mutable std::deque<EvaluationContext> evaluation_contexts;
|
||||
mutable uint32_t evaluation_value_count = 0;
|
||||
|
||||
@@ -1064,6 +1064,7 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
}
|
||||
if (capture_indirect_reads || !plan.source_reads.empty())
|
||||
plan.resource_tracking_complete &= !program.has_address_writes;
|
||||
CompileSrtWalker(plan);
|
||||
return plan;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <xbyak/xbyak.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
@@ -359,6 +364,131 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
void CompileSrtWalker(ResourcePlan& program) {
|
||||
if (!program.resource_tracking_complete || !program.srt_plan_complete ||
|
||||
program.capture_specialization_reads || !program.control_flow.empty() ||
|
||||
program.srt_reads.empty()) return;
|
||||
|
||||
using Reads = std::map<int32_t, const SrtRead*>;
|
||||
std::map<uint32_t, Reads> roots;
|
||||
std::unordered_map<const Inst*, Reads> pointer_uses;
|
||||
std::unordered_map<const Inst*, int32_t> offsets;
|
||||
for (const auto& read: program.srt_reads) {
|
||||
const auto* inst = read.value.Resolve().TryInstruction();
|
||||
if (inst == nullptr || inst->GetOpcode() != ValueOpcode::LoadAddressU32 ||
|
||||
inst->NumArgs() < 2 || !IsRawRead(program, *inst) ||
|
||||
read.flat_offset >= program.srt_reads.size()) return;
|
||||
const auto offset = inst->Arg(1).Resolve();
|
||||
if (!offset.IsImmediate() || offset.GetType() != Type::U32) return;
|
||||
const auto& memory = program.memory_info[inst->Flags<SrtReadFlags>().index];
|
||||
const auto relative =
|
||||
(static_cast<int64_t>(static_cast<int32_t>(memory.offset)) & ~int64_t {3}) +
|
||||
static_cast<int64_t>(offset.U32() & ~3u);
|
||||
if (relative < INT32_MIN || relative > INT32_MAX ||
|
||||
!offsets.emplace(inst, static_cast<int32_t>(relative)).second) return;
|
||||
}
|
||||
uint32_t user_data_count = 0;
|
||||
for (const auto& read: program.srt_reads) {
|
||||
const auto* inst = read.value.ResolveInstruction();
|
||||
const auto* handle = inst->Arg(0).Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource ||
|
||||
handle->NumArgs() != 2) return;
|
||||
const auto* low = handle->Arg(0).Resolve().TryInstruction();
|
||||
const auto* high = handle->Arg(1).Resolve().TryInstruction();
|
||||
if (low == nullptr || high == nullptr) return;
|
||||
Reads* uses = nullptr;
|
||||
if (low->GetOpcode() == ValueOpcode::GetUserData &&
|
||||
high->GetOpcode() == ValueOpcode::GetUserData && low->NumArgs() == 1 &&
|
||||
high->NumArgs() == 1 && low->Arg(0).GetType() == Type::ScalarReg &&
|
||||
high->Arg(0).GetType() == Type::ScalarReg) {
|
||||
const auto reg = RegIndex(low->Arg(0).ScalarRegister());
|
||||
if (reg < program.user_data_base ||
|
||||
RegIndex(high->Arg(0).ScalarRegister()) != reg + 1u) return;
|
||||
const auto base = reg - program.user_data_base;
|
||||
user_data_count = std::max(user_data_count, base + 2u);
|
||||
uses = &roots[base];
|
||||
} else {
|
||||
const auto lo = offsets.find(low);
|
||||
const auto hi = offsets.find(high);
|
||||
if (lo == offsets.end() || hi == offsets.end() ||
|
||||
static_cast<int64_t>(lo->second) + 4 != hi->second ||
|
||||
!EquivalentValue(program, low->Arg(0), high->Arg(0))) return;
|
||||
uses = &pointer_uses[low];
|
||||
}
|
||||
if (!uses->emplace(offsets.at(inst), &read).second) return;
|
||||
}
|
||||
std::unordered_set<const Inst*> visited;
|
||||
const auto reachable = [&](auto&& self, const Reads& reads) -> bool {
|
||||
for (const auto& [offset, read]: reads) {
|
||||
const auto* inst = read->value.ResolveInstruction();
|
||||
const auto child = pointer_uses.find(inst);
|
||||
if (child != pointer_uses.end() &&
|
||||
(!visited.insert(inst).second || !self(self, child->second))) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
for (const auto& [reg, reads]: roots) {
|
||||
if (!reachable(reachable, reads)) return;
|
||||
}
|
||||
if (visited.size() != pointer_uses.size()) return;
|
||||
|
||||
program.srt_code = std::make_unique<Xbyak::CodeGenerator>(4096, Xbyak::AutoGrow);
|
||||
auto& code = *program.srt_code;
|
||||
Xbyak::Label failed, finished;
|
||||
code.push(code.rbp);
|
||||
code.mov(code.rbp, code.rsp);
|
||||
code.cmp(code.r8, user_data_count);
|
||||
code.jb(failed, Xbyak::CodeGenerator::T_NEAR);
|
||||
code.mov(code.r9, AddressMask & ~uint64_t {3});
|
||||
// Follow shadPS4's VisitPointer: copy one table level in offset order, then
|
||||
// descend through its child pointers. Seed the descriptor evaluator's memo.
|
||||
const auto visit = [&](auto&& self, int32_t pointer_offset, const Reads& reads) -> void {
|
||||
code.push(code.rdi);
|
||||
code.mov(code.rdi, code.qword[code.rdi + pointer_offset]);
|
||||
code.and_(code.rdi, code.r9);
|
||||
const auto first = reads.begin()->first;
|
||||
const auto last = reads.rbegin()->first;
|
||||
if (first < 0) {
|
||||
code.mov(code.r10, -static_cast<int64_t>(first));
|
||||
code.cmp(code.rdi, code.r10);
|
||||
code.jb(failed, Xbyak::CodeGenerator::T_NEAR);
|
||||
}
|
||||
if (last > 0) {
|
||||
code.mov(code.r10, AddressMask - last);
|
||||
code.cmp(code.rdi, code.r10);
|
||||
code.ja(failed, Xbyak::CodeGenerator::T_NEAR);
|
||||
}
|
||||
for (const auto& [offset, read]: reads) {
|
||||
const auto index = read->value.ResolveInstruction()->EvaluationIndex(program.evaluation_value_count);
|
||||
using Entry = ResourcePlan::EvaluationContext::Entry;
|
||||
const auto slot = code.rdx + index * sizeof(Entry);
|
||||
code.mov(code.r10d, code.dword[code.rdi + offset]);
|
||||
code.mov(code.dword[code.rsi + read->flat_offset * sizeof(uint32_t)], code.r10d);
|
||||
code.mov(code.qword[slot + offsetof(Entry, value)], code.r10);
|
||||
code.mov(code.qword[slot + offsetof(Entry, generation)], code.rcx);
|
||||
}
|
||||
for (const auto& [offset, read]: reads) {
|
||||
const auto child = pointer_uses.find(read->value.ResolveInstruction());
|
||||
if (child == pointer_uses.end()) continue;
|
||||
self(self, offset, child->second);
|
||||
}
|
||||
code.pop(code.rdi);
|
||||
};
|
||||
for (const auto& [reg, reads]: roots) {
|
||||
visit(visit, reg * sizeof(uint32_t), reads);
|
||||
}
|
||||
code.mov(code.eax, 1);
|
||||
code.jmp(finished, Xbyak::CodeGenerator::T_NEAR);
|
||||
code.L(failed);
|
||||
code.xor_(code.eax, code.eax);
|
||||
code.L(finished);
|
||||
code.mov(code.rsp, code.rbp);
|
||||
code.pop(code.rbp);
|
||||
code.ret();
|
||||
code.readyRE();
|
||||
EXIT_IF(Xbyak::GetError() != 0);
|
||||
}
|
||||
|
||||
SrtWalker::SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
SrtWalker* clean_evaluator, Value active_mask)
|
||||
: m_program(program), m_runtime(runtime), m_clean_evaluator(clean_evaluator),
|
||||
@@ -954,6 +1084,15 @@ bool SrtWalker::EvaluateDescriptor(uint32_t source, DescriptorValue& result) {
|
||||
|
||||
bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
|
||||
if (!m_program.srt_plan_complete) return false;
|
||||
if (m_program.srt_code && m_runtime.read_memory == nullptr) {
|
||||
flat.resize(m_program.srt_reads.size());
|
||||
m_program.active_sources.clear();
|
||||
m_context.values.resize(m_program.evaluation_value_count);
|
||||
using Walker = bool KYTY_SYSV_ABI (*)(const uint32_t*, uint32_t*,
|
||||
ResourcePlan::EvaluationContext::Entry*, uint64_t, size_t);
|
||||
return m_program.srt_code->getCode<Walker>()(m_runtime.user_data.data(), flat.data(),
|
||||
m_context.values.data(), m_context.generation, m_runtime.user_data.size());
|
||||
}
|
||||
const auto refresh = [&](uint32_t slot) {
|
||||
if (slot >= m_program.srt_reads.size()) return false;
|
||||
const auto& read = m_program.srt_reads[slot];
|
||||
|
||||
@@ -45,6 +45,7 @@ enum class RuntimeValueType { Any, Integer, ImmutableInteger };
|
||||
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
RuntimeValueType type = RuntimeValueType::Any);
|
||||
void CompileSrtWalker(ResourcePlan& program);
|
||||
// Uses the strict reader for values that affect shader specialization.
|
||||
SrtRuntime CleanRuntime(SrtRuntime runtime);
|
||||
|
||||
|
||||
+2
-8
@@ -54,11 +54,7 @@ struct CContext {
|
||||
std::list<atexit_func_t> atexit;
|
||||
};
|
||||
|
||||
struct InitEnvParams {
|
||||
int argc;
|
||||
uint32_t pad;
|
||||
const char* argv[3];
|
||||
};
|
||||
using InitEnvParams = Loader::EntryParams;
|
||||
|
||||
static int g_argc = 0;
|
||||
static const char* const* g_argv = nullptr;
|
||||
@@ -271,9 +267,7 @@ static KYTY_SYSV_ABI void init_env(const InitEnvParams* params) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int argv_capacity = static_cast<int>(sizeof(params->argv) / sizeof(params->argv[0]));
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(params->argc < 0 || params->argc >= argv_capacity);
|
||||
EXIT_NOT_IMPLEMENTED(params->argc < 0);
|
||||
|
||||
g_argc = params->argc;
|
||||
g_argv = params->argv;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "emulator.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
#include "libs/dialog.h"
|
||||
#include "libs/errno.h"
|
||||
@@ -99,6 +100,11 @@ static int KYTY_SYSV_ABI SystemServiceHideSplashScreen() {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI SystemServiceLoadExec(const char* path, const char* const argv[]) {
|
||||
PRINT_NAME();
|
||||
return Emulator::LoadExec(path, argv);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SystemServiceParamGetInt(int param_id, int* value) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -559,6 +565,7 @@ LIB_DEFINE(InitSystemGesture_1) {
|
||||
} // namespace SystemGesture
|
||||
|
||||
LIB_DEFINE(InitSystemService_1) {
|
||||
LIB_FUNC("JoBqSQt1yyA", SystemService::SystemServiceLoadExec);
|
||||
LIB_FUNC("Vo5V8KAwCmk", SystemService::SystemServiceHideSplashScreen);
|
||||
LIB_FUNC("fZo48un7LK4", SystemService::SystemServiceParamGetInt);
|
||||
LIB_FUNC("SsC-m-S9JTA", SystemService::SystemServiceParamGetString);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "emulator.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#elif defined(__linux__)
|
||||
#include <linux/close_range.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
static std::wstring QuoteArgument(std::wstring_view argument) {
|
||||
std::wstring quoted = L"\"";
|
||||
size_t slashes = 0;
|
||||
for (wchar_t c: argument) {
|
||||
if (c == L'\\') {
|
||||
++slashes;
|
||||
continue;
|
||||
}
|
||||
quoted.append(c == L'"' ? slashes * 2 + 1 : slashes, L'\\');
|
||||
quoted += c;
|
||||
slashes = 0;
|
||||
}
|
||||
quoted.append(slashes * 2, L'\\');
|
||||
return quoted + L'"';
|
||||
}
|
||||
#endif
|
||||
|
||||
int LoadExec(const char* path, const char* const argv[]) {
|
||||
using namespace Libs::SystemService;
|
||||
if (path == nullptr || !std::string_view(path).starts_with("/app0/") ||
|
||||
!Common::File::IsFileExisting(Libs::LibKernel::FileSystem::GetRealFilename(path))) {
|
||||
return SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
}
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || defined(__linux__)
|
||||
std::vector<std::string> arguments {"--guest-exec", path};
|
||||
size_t bytes = 0;
|
||||
for (size_t i = 0; argv != nullptr && argv[i] != nullptr; ++i) {
|
||||
const auto size = strnlen(argv[i], 4096 - bytes) + 1;
|
||||
if (size > 4096 - bytes) {
|
||||
return SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
}
|
||||
bytes += size;
|
||||
arguments.emplace_back(argv[i], size - 1);
|
||||
}
|
||||
LOGF("LoadExec: %s\n", path);
|
||||
Log::Flush();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
wchar_t executable[32768] {};
|
||||
const auto length = GetModuleFileNameW(nullptr, executable, 32768);
|
||||
if (length == 0 || length >= 32768) {
|
||||
return SYSTEM_SERVICE_ERROR_INTERNAL;
|
||||
}
|
||||
if (__wargv == nullptr) {
|
||||
return SYSTEM_SERVICE_ERROR_INTERNAL;
|
||||
}
|
||||
std::wstring command = QuoteArgument(executable);
|
||||
for (int i = 1; i < __argc && __wargv[i] != nullptr; ++i) {
|
||||
if (std::wstring_view(__wargv[i]) == L"--guest-exec") {
|
||||
break;
|
||||
}
|
||||
command += L" " + QuoteArgument(__wargv[i]);
|
||||
}
|
||||
for (const auto& argument: arguments) {
|
||||
const int size =
|
||||
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, argument.c_str(), -1, nullptr, 0);
|
||||
if (size == 0) {
|
||||
return SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
}
|
||||
std::wstring wide(size, L'\0');
|
||||
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, argument.c_str(), -1, wide.data(),
|
||||
size) == 0) {
|
||||
return SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
}
|
||||
wide.resize(size - 1);
|
||||
command += L" " + QuoteArgument(wide);
|
||||
}
|
||||
STARTUPINFOW startup {};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process {};
|
||||
if (!CreateProcessW(executable, command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
|
||||
nullptr, nullptr, &startup, &process)) {
|
||||
LOGF("LoadExec: process creation failed: %lu\n", GetLastError());
|
||||
return SYSTEM_SERVICE_ERROR_INTERNAL;
|
||||
}
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
std::fflush(nullptr);
|
||||
std::quick_exit(0);
|
||||
#else
|
||||
std::ifstream command("/proc/self/cmdline", std::ios::binary);
|
||||
std::vector<std::string> host_arguments;
|
||||
for (std::string argument;
|
||||
std::getline(command, argument, '\0') && argument != "--guest-exec";) {
|
||||
host_arguments.push_back(std::move(argument));
|
||||
}
|
||||
if (host_arguments.empty()) {
|
||||
return SYSTEM_SERVICE_ERROR_INTERNAL;
|
||||
}
|
||||
host_arguments.insert(host_arguments.end(), arguments.begin(), arguments.end());
|
||||
std::vector<char*> host_argv;
|
||||
for (auto& argument: host_arguments) {
|
||||
host_argv.push_back(argument.data());
|
||||
}
|
||||
host_argv.push_back(nullptr);
|
||||
if (close_range(3, ~0u, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC) == 0) {
|
||||
execvp(host_argv[0], host_argv.data());
|
||||
}
|
||||
LOGF("LoadExec failed: %s\n", std::strerror(errno));
|
||||
#endif
|
||||
return SYSTEM_SERVICE_ERROR_INTERNAL;
|
||||
#else
|
||||
return SYSTEM_SERVICE_ERROR_UNAVAILABLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Emulator
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -88,16 +89,6 @@ ThreadLocalStorage::~ThreadLocalStorage() {
|
||||
}
|
||||
}
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
struct EntryParams {
|
||||
int argc;
|
||||
uint32_t pad;
|
||||
const char* argv[3];
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
using atexit_func_t = KYTY_SYSV_ABI void (*)();
|
||||
using entry_func_t = KYTY_SYSV_ABI void (*)(EntryParams* params, atexit_func_t atexit_func);
|
||||
using module_ini_fini_func_t = KYTY_SYSV_ABI int (*)(size_t args, const void* argp,
|
||||
@@ -1283,11 +1274,16 @@ void RuntimeLinker::Execute(const std::filesystem::path& game_patch) {
|
||||
LOGF_COLOR(Log::Color::BrightYellow, "---\n--- Execute: %s\n---\n", "Main");
|
||||
|
||||
if (auto entry = GetEntry(); entry != 0) {
|
||||
const auto& arguments = Config::GetGuestArguments();
|
||||
const auto params_size = offsetof(EntryParams, argv) +
|
||||
(arguments.size() + 2) * sizeof(const char*);
|
||||
auto* params = reinterpret_cast<EntryParams*>(
|
||||
(reinterpret_cast<uintptr_t>(main_stack_top) - 0x100u) & ~static_cast<uintptr_t>(0x0f));
|
||||
std::memset(params, 0, sizeof(EntryParams));
|
||||
params->argc = 1;
|
||||
params->argv[0] = "KytyEmu";
|
||||
(reinterpret_cast<uintptr_t>(main_stack_top) - params_size - 0x100u) &
|
||||
~static_cast<uintptr_t>(0x0f));
|
||||
std::memset(params, 0, params_size);
|
||||
for (const auto& argument: arguments) {
|
||||
params->argv[params->argc++] = argument.c_str();
|
||||
}
|
||||
|
||||
LOGF("stack_addr = %" PRIx64 "\n", reinterpret_cast<uint64_t>(params));
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
|
||||
namespace Loader {
|
||||
|
||||
struct EntryParams {
|
||||
int argc;
|
||||
uint32_t pad;
|
||||
const char* argv[1];
|
||||
};
|
||||
|
||||
class Elf64;
|
||||
struct Elf64_Sym;
|
||||
struct Elf64_Rela;
|
||||
|
||||
@@ -360,10 +360,12 @@ void PlayGo::Open(const std::filesystem::path& file_name) {
|
||||
}
|
||||
|
||||
uint32_t magic1 = 0;
|
||||
constexpr uint32_t PLGO_MAGIC = 0x6f676c70;
|
||||
constexpr uint32_t PLGX_MAGIC = 0x78676c70;
|
||||
|
||||
m_f.Read(&magic1, 4);
|
||||
|
||||
if (magic1 != 0x6f676c70) {
|
||||
if (magic1 != PLGO_MAGIC && magic1 != PLGX_MAGIC) {
|
||||
LOGF("invalid file: magic1 = %08" PRIx32 "\n", magic1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,6 +300,10 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--guest-exec") {
|
||||
options.elf = Common::PathFromUtf8(Common::FixFilenameSlash(value));
|
||||
options.config.guest_arguments.assign(argv + i + 1, argv + argc);
|
||||
break;
|
||||
} else if (arg == "--game-patch") {
|
||||
if (!options.game_patch.empty()) {
|
||||
::printf("--game-patch can only be specified once\n");
|
||||
|
||||
@@ -168,6 +168,117 @@ void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
"cache rematerialization did not use the direct reader by default");
|
||||
}
|
||||
|
||||
void TestNativeNestedSrtRefresh() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
std::array<uint32_t, 3> first{0u, 0x12345678u, 0xabcdef01u};
|
||||
std::array<uint32_t, 3> second{0u, 0x87654321u, 0x10fedcbau};
|
||||
const auto pointer_words = [](const void *pointer) {
|
||||
const auto address = reinterpret_cast<uint64_t>(pointer);
|
||||
return std::array<uint32_t, 2>{static_cast<uint32_t>(address) | 3u,
|
||||
static_cast<uint32_t>(address >> 32u) | 0xabcd0000u};
|
||||
};
|
||||
auto table = pointer_words(first.data());
|
||||
const auto user_data = pointer_words(table.data());
|
||||
auto program = SrtProgram(0u);
|
||||
program.user_data_base = 4;
|
||||
program.user_data_count = 2;
|
||||
auto &block = *program.blocks[0];
|
||||
const auto emit = [&](ValueOpcode opcode, std::initializer_list<Value> args) {
|
||||
return Value(&block.AppendNewInst(opcode, args));
|
||||
};
|
||||
const auto low = emit(ValueOpcode::GetUserData, {Value(ScalarReg(4))});
|
||||
const auto high = emit(ValueOpcode::GetUserData, {Value(ScalarReg(5))});
|
||||
const auto root = emit(ValueOpcode::GetAddressResource, {low, high});
|
||||
const auto read = [&](Value address, uint32_t offset) {
|
||||
const auto value = emit(ValueOpcode::LoadAddressU32,
|
||||
{address, Value(offset), Value(0u), Value(true)});
|
||||
value.Instruction()->SetFlags(MemoryFlags{.index = 0});
|
||||
program.srt_reads.push_back(
|
||||
{value, static_cast<uint32_t>(program.srt_reads.size())});
|
||||
return value;
|
||||
};
|
||||
const auto pointer_low = read(root, 0u);
|
||||
const auto pointer_high = read(root, 4u);
|
||||
auto *leaf = program.srt_reads[0].value.Instruction();
|
||||
leaf->Arg(0).Instruction()->SetArg(0, pointer_low);
|
||||
leaf->Arg(0).Instruction()->SetArg(1, pointer_high);
|
||||
leaf->SetArg(1, Value(7u));
|
||||
read(leaf->Arg(0), 11u);
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
Check(plan.srt_code != nullptr, "nested user-data tree did not compile an x86 walker");
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const SrtRuntime runtime{.user_data = user_data};
|
||||
{
|
||||
SrtWalker walker(plan, runtime);
|
||||
Check(walker.RefreshFlatBuffer(snapshot.flattened_srt),
|
||||
"native SRT refresh failed");
|
||||
const auto loaded = first[1];
|
||||
first[1] ^= 0xffffffffu;
|
||||
uint32_t value = 0;
|
||||
Check(walker.Evaluate(plan.srt_reads[0].value, value) && value == loaded,
|
||||
"descriptor evaluation reloaded a value already copied by the native walker");
|
||||
first[1] = loaded;
|
||||
}
|
||||
for (const auto *data : {&first, &second}) {
|
||||
table = pointer_words(data->data());
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.flattened_srt ==
|
||||
std::vector<uint32_t>{(*data)[1], table[0], table[1], (*data)[2]},
|
||||
"native SRT refresh lost pointer masking, alignment, or current values");
|
||||
}
|
||||
Check(!MaterializeResources(plan, {.user_data = std::span(user_data).first(1)},
|
||||
snapshot, specialization),
|
||||
"native SRT walk accepted truncated user data");
|
||||
const std::array<uint32_t, 2> fake_user_data{0x1003u, 0xabcd0000u};
|
||||
uint32_t reads = 0;
|
||||
const SrtRuntime callback_runtime{
|
||||
.user_data = fake_user_data,
|
||||
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
++*static_cast<uint32_t *>(data);
|
||||
switch (address) {
|
||||
case 0x1000u: words[0] = 0x2003u; break;
|
||||
case 0x1004u: words[0] = 0xabcd0000u; break;
|
||||
case 0x2004u: words[0] = 0x1234u; break;
|
||||
case 0x2008u: words[0] = 0x5678u; break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads};
|
||||
Check(MaterializeResources(plan, callback_runtime, snapshot, specialization) && reads == 4 &&
|
||||
snapshot.flattened_srt ==
|
||||
std::vector<uint32_t>{0x1234u, 0x2003u, 0xabcd0000u, 0x5678u},
|
||||
"native-eligible SRT bypassed its callback or repeated nested pointer reads");
|
||||
leaf->SetArg(0, root);
|
||||
leaf->SetArg(1, Value(0u));
|
||||
program.srt_reads.resize(1);
|
||||
for (const int32_t offset : {-1, 7}) {
|
||||
program.memory_info[0].offset = static_cast<uint32_t>(offset);
|
||||
auto bounded = ExtractResourcePlan(program);
|
||||
auto base = pointer_words(second.data() + 1);
|
||||
Check(bounded.srt_code != nullptr &&
|
||||
MaterializeResources(bounded, {.user_data = base}, snapshot, specialization) &&
|
||||
snapshot.flattened_srt[0] == second[offset < 0 ? 0 : 2],
|
||||
"native SRT applied a signed immediate offset incorrectly");
|
||||
base = offset < 0 ? std::array<uint32_t, 2>{0u, 0u}
|
||||
: std::array<uint32_t, 2>{0xfffffffcu, 0xffffu};
|
||||
Check(!MaterializeResources(bounded, {.user_data = base}, snapshot, specialization),
|
||||
"native SRT accepted address underflow or 48-bit overflow");
|
||||
}
|
||||
high.Instruction()->SetArg(0, Value(ScalarReg(6)));
|
||||
Check(ExtractResourcePlan(program).srt_code == nullptr,
|
||||
"native SRT accepted nonadjacent user-data pointer words");
|
||||
high.Instruction()->SetArg(0, Value(ScalarReg(5)));
|
||||
program.memory_info[0].offset = 0;
|
||||
program.srt_reads.push_back({pointer_low, 1});
|
||||
program.srt_reads.push_back({pointer_high, 2});
|
||||
leaf->SetArg(0, emit(ValueOpcode::GetAddressResource, {pointer_low, pointer_high}));
|
||||
pointer_high.Instruction()->SetArg(1, Value(8u));
|
||||
Check(ExtractResourcePlan(program).srt_code == nullptr,
|
||||
"native SRT accepted nonadjacent nested pointer words");
|
||||
}
|
||||
|
||||
void TestIntegerRuntimeValueFollowsSrtReads() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
auto plan = SrtProgram(0x10000);
|
||||
@@ -256,6 +367,8 @@ void TestSrtAliasesRetainReadPolicy() {
|
||||
plan.srt_reads[1].value.Instruction()->Flags<SrtReadFlags>().clean == 0u &&
|
||||
plan.srt_reads[2].value.Instruction()->Flags<SrtReadFlags>().clean == 1u,
|
||||
"alias normalization lost read identity or retained discarded EXEC provenance");
|
||||
Check(plan.srt_code == nullptr,
|
||||
"captured strict reads compiled an unused x86 walker");
|
||||
struct Reads { uint32_t strict = 0; uint32_t ordinary = 0; bool dirty = false; } reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
@@ -671,6 +784,7 @@ void DbgExit(int) { std::abort(); }
|
||||
|
||||
int main() {
|
||||
TestMappedSrtUsesDirectReaderByDefault();
|
||||
TestNativeNestedSrtRefresh();
|
||||
TestIntegerRuntimeValueFollowsSrtReads();
|
||||
TestSrtAliasesRetainReadPolicy();
|
||||
TestUniformVectorDescriptorRead();
|
||||
|
||||
@@ -17925,9 +17925,9 @@ public:
|
||||
captured.mip_layout[0].pitch == 512 &&
|
||||
captured.mip_layout[0].height == 512,
|
||||
"R64 BC1 texture lost its compressed blocks or array stride");
|
||||
const auto small =
|
||||
const auto small_array =
|
||||
MakeTilingImage(format, 67, 51, 1, layers, tile, 0, false);
|
||||
check_round_trip("R64 BC1 array", small.data.size, small);
|
||||
check_round_trip("R64 BC1 array", small_array.data.size, small_array);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user