mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0685bffb2f | ||
|
|
a5b3673a39 | ||
|
|
09753bf8ff | ||
|
|
9d8c458535 | ||
|
|
358333125d |
No files matched your search
Vendored
+22
@@ -42,6 +42,28 @@ if(NOT KYTY_CLANG_CL)
|
||||
endif()
|
||||
install(FILES "${openxr_SOURCE_DIR}/LICENSE" DESTINATION "licenses/openxr")
|
||||
|
||||
# Noto Sans v2.015 (SIL Open Font License 1.1) is libFont's fallback font.
|
||||
set(KYTY_NOTO_SANS_REPOSITORY "https://raw.githubusercontent.com/notofonts/notofonts.github.io")
|
||||
set(KYTY_NOTO_SANS_COMMIT 42af67ccd12bc0e31ed85384bf73e39248265374)
|
||||
set(KYTY_NOTO_SANS_BASE_URL "${KYTY_NOTO_SANS_REPOSITORY}/${KYTY_NOTO_SANS_COMMIT}/fonts")
|
||||
set(KYTY_NOTO_SANS_DIR "${FETCHCONTENT_BASE_DIR}/noto-sans")
|
||||
function(kyty_fetch_noto_sans path sha256)
|
||||
get_filename_component(name "${path}" NAME)
|
||||
file(DOWNLOAD "${KYTY_NOTO_SANS_BASE_URL}/${path}" "${KYTY_NOTO_SANS_DIR}/${name}"
|
||||
EXPECTED_HASH SHA256=${sha256})
|
||||
endfunction()
|
||||
kyty_fetch_noto_sans(LICENSE f2095b08bed08b23a6fe26112fcd679a2bee3f002eef077eb05d215ed1051bd8)
|
||||
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Regular.ttf
|
||||
478c558ea716033cd60c03438f628dfa75694dcf6b5f6d505a2f05fd2b4f3823)
|
||||
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Bold.ttf
|
||||
1df075a380fc7cb898acf64c1f7b3b4dd780de3caa860178bf929de35817a913)
|
||||
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Italic.ttf
|
||||
467e3f89eeca4108bb8710a2b9e0cf2281ac56d5b0609211a83776d0505eecb5)
|
||||
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-BoldItalic.ttf
|
||||
1b602a9d6353be42c91df097a4857b69fa2696f26703d7a33b54a15d87c2622c)
|
||||
install(FILES "${KYTY_NOTO_SANS_DIR}/LICENSE" DESTINATION "licenses/noto-sans")
|
||||
set(KYTY_NOTO_SANS_DIR "${KYTY_NOTO_SANS_DIR}" PARENT_SCOPE)
|
||||
|
||||
# The guest instruction patcher needs a decoder/encoder and a small
|
||||
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
||||
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
|
||||
@@ -815,6 +815,19 @@ add_custom_target(kyty_touch_cursors
|
||||
add_dependencies(kyty_emulator kyty_touch_cursors)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors" DESTINATION assets)
|
||||
|
||||
set(KYTY_NOTO_SANS_FONTS
|
||||
"${KYTY_NOTO_SANS_DIR}/NotoSans-Regular.ttf"
|
||||
"${KYTY_NOTO_SANS_DIR}/NotoSans-Bold.ttf"
|
||||
"${KYTY_NOTO_SANS_DIR}/NotoSans-Italic.ttf"
|
||||
"${KYTY_NOTO_SANS_DIR}/NotoSans-BoldItalic.ttf")
|
||||
add_custom_target(kyty_noto_sans_fonts
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${KYTY_NOTO_SANS_FONTS}
|
||||
"$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||
VERBATIM)
|
||||
add_dependencies(kyty_emulator kyty_noto_sans_fonts)
|
||||
install(FILES ${KYTY_NOTO_SANS_FONTS} DESTINATION assets/fonts)
|
||||
|
||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||
if (WIN32)
|
||||
target_link_libraries(kyty_emulator opengl32)
|
||||
|
||||
@@ -99,10 +99,6 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
|
||||
|
||||
if (Common::File::IsFileExisting(param_json)) {
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
flexible_memory_size != 0) {
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
if (cfg.splash_enabled) {
|
||||
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
||||
|
||||
@@ -814,6 +814,27 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
||||
write(registers.draw_index, draw_index);
|
||||
}
|
||||
|
||||
// Writes the GS user-data slots the packet patches; false when the shaders cannot read the
|
||||
// offsets from GPU records: a first-index or subdraw-index destination, or another stage's
|
||||
// user data.
|
||||
static bool IndirectDrawOffsets(IndirectDrawRegisters registers, ShaderIndirectOffsets& offsets) {
|
||||
if (registers.index_offset != Pm4::SH_NOP || registers.draw_index != Pm4::SH_NOP) {
|
||||
return false;
|
||||
}
|
||||
for (const auto [location, slot]: {std::pair {registers.vertex_offset, &offsets.vertex},
|
||||
std::pair {registers.instance_offset, &offsets.instance}}) {
|
||||
if (location == Pm4::SH_NOP) {
|
||||
continue;
|
||||
}
|
||||
const uint32_t index = location - Pm4::SPI_SHADER_USER_DATA_GS_0;
|
||||
if (index >= HW::UserSgprInfo::SGPRS_MAX) {
|
||||
return false;
|
||||
}
|
||||
*slot = static_cast<uint8_t>(index);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
const auto args_size =
|
||||
@@ -845,13 +866,11 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
// NUM_INSTANCES keeps the last draw's instance count.
|
||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
|
||||
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
|
||||
// while they are zero.
|
||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
||||
registers.index_offset == Pm4::SH_NOP && registers.draw_index == Pm4::SH_NOP &&
|
||||
(!indexed || m_index_buffer_size != 0) &&
|
||||
// The GPU reads the records if the shaders allow; the VS prolog loads the patched SGPRs from
|
||||
// Vulkan's bases. With no SGPR patched, Vulkan applies the offsets, which matches while they
|
||||
// are zero.
|
||||
DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) &&
|
||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = m_index_buffer_size,
|
||||
.index_addr = IndexAddress(0),
|
||||
|
||||
+30
-30
@@ -488,16 +488,15 @@ void TextureCache::RefreshCopySource(ImageId id) {
|
||||
}
|
||||
|
||||
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
const bool source_depth = source.info.IsDepth();
|
||||
const bool destination_depth = destination.info.IsDepth();
|
||||
if (source_depth == destination_depth) {
|
||||
// Converts guest D16 between a four-byte host depth aspect and two-byte color or D16 depth.
|
||||
const bool source_wide = DepthAspectTransferBytes(source.backing.format) == sizeof(uint32_t);
|
||||
if (source_wide == (DepthAspectTransferBytes(destination.backing.format) == sizeof(uint32_t))) {
|
||||
return false;
|
||||
}
|
||||
auto& depth = source_depth ? source : destination;
|
||||
auto& color = source_depth ? destination : source;
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(depth.backing.format);
|
||||
auto& depth = source_wide ? source : destination;
|
||||
auto& narrow = source_wide ? destination : source;
|
||||
if (depth.info.bytes_per_block != sizeof(uint16_t) ||
|
||||
color.info.bytes_per_block != sizeof(uint16_t) || transfer_bytes != sizeof(uint32_t)) {
|
||||
narrow.info.bytes_per_block != sizeof(uint16_t)) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||
@@ -507,42 +506,42 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
|
||||
const auto layers = depth.info.resources.layers;
|
||||
const uint64_t depth_slice =
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * transfer_bytes;
|
||||
const uint64_t color_slice =
|
||||
static_cast<uint64_t>(color.info.pitch) * color.info.extent.height * sizeof(uint16_t);
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || color_slice > UINT64_MAX / layers);
|
||||
const auto depth_size = depth_slice * layers;
|
||||
const auto color_size = color_slice * layers;
|
||||
const std::array depth_copies {
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * sizeof(uint32_t);
|
||||
const uint64_t narrow_slice =
|
||||
static_cast<uint64_t>(narrow.info.pitch) * narrow.info.extent.height * sizeof(uint16_t);
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || narrow_slice > UINT64_MAX / layers);
|
||||
const std::array depth_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
depth.info.pitch,
|
||||
depth.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||
{},
|
||||
depth.info.extent}};
|
||||
const std::array color_copies {
|
||||
const std::array narrow_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
color.info.pitch,
|
||||
color.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 0, layers},
|
||||
narrow.info.pitch,
|
||||
narrow.info.extent.height,
|
||||
{narrow.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor,
|
||||
0, 0, layers},
|
||||
{},
|
||||
color.info.extent}};
|
||||
narrow.info.extent}};
|
||||
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_size);
|
||||
auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer);
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_slice * layers);
|
||||
auto narrow_buffer = m_tiler.GetScratchBuffer(narrow_slice * layers, depth_buffer.buffer);
|
||||
const TileManager::D16Layout promote_layout {
|
||||
.width = depth.info.extent.width,
|
||||
.height = depth.info.extent.height,
|
||||
.layers = layers,
|
||||
.source_row_stride = static_cast<uint64_t>(color.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * transfer_bytes,
|
||||
.source_slice_stride = color_slice,
|
||||
.source_row_stride = static_cast<uint64_t>(narrow.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * sizeof(uint32_t),
|
||||
.source_slice_stride = narrow_slice,
|
||||
.target_slice_stride = depth_slice,
|
||||
};
|
||||
const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat;
|
||||
if (source_depth) {
|
||||
if (source_wide) {
|
||||
source.Download(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size);
|
||||
m_tiler.ConvertD16(depth_buffer, color_buffer, TileManager::D16Direction::Demote, d32,
|
||||
m_tiler.ConvertD16(depth_buffer, narrow_buffer, TileManager::D16Direction::Demote, d32,
|
||||
{.width = promote_layout.width,
|
||||
.height = promote_layout.height,
|
||||
.layers = promote_layout.layers,
|
||||
@@ -550,11 +549,12 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
.target_row_stride = promote_layout.source_row_stride,
|
||||
.source_slice_stride = promote_layout.target_slice_stride,
|
||||
.target_slice_stride = promote_layout.source_slice_stride});
|
||||
destination.Upload(color_copies, color_buffer.buffer, color_buffer.offset,
|
||||
color_buffer.size);
|
||||
destination.Upload(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
} else {
|
||||
source.Download(color_copies, color_buffer.buffer, color_buffer.offset, color_buffer.size);
|
||||
m_tiler.ConvertD16(color_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
source.Download(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
m_tiler.ConvertD16(narrow_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
promote_layout);
|
||||
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
||||
depth_buffer.size);
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace {
|
||||
|
||||
namespace IR = ShaderRecompiler::IR;
|
||||
|
||||
// Smaller tables are cheap to materialize on each draw and are often transient.
|
||||
// Smaller tables are often written for one draw, so they enter on a second draw of a frame.
|
||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||
|
||||
@@ -133,12 +133,16 @@ ImageHeap::~ImageHeap() {
|
||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto size = request.size & ~uint64_t {3};
|
||||
if (size < MinTableBytes || request.count == 0u || request.step == 0u ||
|
||||
if (request.count == 0u || request.step == 0u ||
|
||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||
return false;
|
||||
}
|
||||
auto memory = std::ranges::find_if(
|
||||
m_memories, [&](const auto& candidate) { return candidate->base == request.base; });
|
||||
if (memory == m_memories.end() && size < MinTableBytes &&
|
||||
m_sightings.try_emplace(request.base, m_draw).first->second == m_draw) {
|
||||
return false;
|
||||
}
|
||||
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
||||
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||
if (!m_context.IsMapped(request.base, size)) return false;
|
||||
@@ -593,26 +597,42 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
} else {
|
||||
m_unmapped.push_back({address, size});
|
||||
}
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
||||
return false;
|
||||
}
|
||||
Unwatch(*memory);
|
||||
for (auto& view: memory->views) {
|
||||
std::erase(m_active, view.get());
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
Unreference(m_elements[index]);
|
||||
view->released.push_back(index);
|
||||
}
|
||||
Retire(*view);
|
||||
if (scheduler.Active()) {
|
||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||
}
|
||||
}
|
||||
Drop(*memory);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void ImageHeap::RunGarbageCollector() {
|
||||
// A smaller table rebuilt each frame moves to a new base. Larger ones keep their dead words.
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (memory->size >= MinTableBytes || memory->draw > m_suspend_draw) return false;
|
||||
Drop(*memory);
|
||||
return true;
|
||||
});
|
||||
m_suspend_draw = m_draw;
|
||||
m_sightings.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Drop(Memory& memory) {
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
Unwatch(memory);
|
||||
for (auto& view: memory.views) {
|
||||
std::erase(m_active, view.get());
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
Unreference(m_elements[index]);
|
||||
view->released.push_back(index);
|
||||
}
|
||||
Retire(*view);
|
||||
if (scheduler.Active()) {
|
||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -26,7 +26,7 @@ struct ImageHeapTestAccess;
|
||||
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||
// Image::heap_epoch.
|
||||
// Image::heap_epoch. A smaller table leaves at a suspend point if the frame did not acquire it.
|
||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||
public:
|
||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
// CPU write fault, on any thread.
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
// At a suspend point.
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||
@@ -107,6 +109,7 @@ private:
|
||||
template <bool track>
|
||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||
void Unwatch(const Memory& memory);
|
||||
void Drop(Memory& memory);
|
||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||
void UpdateProbe(View& view, uint64_t probe);
|
||||
void Assign(View& view, size_t slot, uint32_t entry);
|
||||
@@ -125,9 +128,11 @@ private:
|
||||
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
||||
std::vector<std::unique_ptr<Memory>> m_memories;
|
||||
std::vector<View*> m_active; // Acquired since the last commit.
|
||||
uint64_t m_draw = 1;
|
||||
uint64_t m_draw = 1;
|
||||
uint64_t m_suspend_draw = 0; // m_draw at the last suspend point.
|
||||
std::vector<Element> m_elements;
|
||||
std::vector<uint32_t> m_free;
|
||||
std::unordered_map<uint64_t, uint64_t> m_sightings; // Smaller tables: base, first draw.
|
||||
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
||||
std::vector<std::pair<size_t, size_t>> m_runs;
|
||||
std::vector<uint32_t> m_scratch;
|
||||
|
||||
@@ -687,6 +687,9 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
||||
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
||||
}
|
||||
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
if (!tess_active && !mesh_active) {
|
||||
vertex_info[0].indirect_offsets = state.indirect_offsets;
|
||||
}
|
||||
if (object_primitive_id) {
|
||||
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
||||
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
|
||||
|
||||
@@ -137,6 +137,8 @@ struct DrawRenderState {
|
||||
bool ps_active = false;
|
||||
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
// Input: the user-data slots a GPU-read indirect draw patches.
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
|
||||
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
||||
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
||||
|
||||
@@ -45,9 +45,10 @@ enum class DrawOffsetSource : uint8_t {
|
||||
|
||||
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
||||
struct DrawIndirectArgs {
|
||||
uint64_t address = 0;
|
||||
uint32_t count = 0;
|
||||
uint32_t stride = 0;
|
||||
uint64_t address = 0;
|
||||
uint32_t count = 0;
|
||||
uint32_t stride = 0;
|
||||
ShaderIndirectOffsets offsets;
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
|
||||
@@ -147,6 +147,7 @@ void RenderContext::RunGarbageCollector() {
|
||||
m_fault_process_pending = false;
|
||||
m_buffer_cache.ProcessFaultBuffer();
|
||||
}
|
||||
m_image_heap.RunGarbageCollector();
|
||||
m_texture_cache.ProcessDownloadImages();
|
||||
m_texture_cache.RunGarbageCollector();
|
||||
m_buffer_cache.RunGarbageCollector();
|
||||
|
||||
@@ -1250,8 +1250,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
}
|
||||
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
|
||||
// mesh and quad-list draws do. Indirect records carry their own offsets.
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
|
||||
// and quad-list draws for counts, tessellation draws for patched SGPRs. Indirect records carry
|
||||
// their own offsets.
|
||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||
const ShaderVertexInputInfo& vs,
|
||||
DrawOffsetSource source,
|
||||
@@ -1260,7 +1261,8 @@ static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig&
|
||||
const DrawIndirectArgs& indirect) {
|
||||
if (indirect.address != 0) {
|
||||
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
|
||||
vs.indirect_offsets != indirect.offsets) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DrawEmitInfo {.indirect = indirect};
|
||||
@@ -1324,7 +1326,7 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
DrawRenderState state {};
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1429,7 +1431,7 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
DrawRenderState state {};
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
ResetBindings();
|
||||
return true;
|
||||
|
||||
@@ -533,16 +533,16 @@ void DrawMemoryAndIo(const Snapshot& shown) {
|
||||
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
||||
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
||||
Row("Extended", Bytes(guest.automatic_allocated));
|
||||
// Past the pool size the guest sees no page-table entries available.
|
||||
const auto entries = std::max(guest.cpu_page_entries, guest.gpu_page_entries);
|
||||
// Past its total a side sees no page-table entries available.
|
||||
const auto percent = std::max(guest.cpu_page_entries * 100 / guest.cpu_page_entries_total,
|
||||
guest.gpu_page_entries * 100 / guest.gpu_page_entries_total);
|
||||
Row("Page table (2 MB)",
|
||||
Fmt("CPU %llu GPU %llu / %llu",
|
||||
Fmt("CPU %llu / %llu GPU %llu / %llu",
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries_total),
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.page_entries_total)),
|
||||
entries > guest.page_entries_total ? BAD
|
||||
: entries * 10 > guest.page_entries_total * 9 ? MEDIUM
|
||||
: GOOD);
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries_total)),
|
||||
LevelColor(static_cast<double>(percent), 90, 100));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
if (BeginSection("Emulator process")) {
|
||||
|
||||
@@ -51,6 +51,7 @@ struct VulkanExtensions {
|
||||
vk::PhysicalDeviceVulkan11Features WindowContext::RequiredVulkan11Features() noexcept {
|
||||
vk::PhysicalDeviceVulkan11Features features {};
|
||||
features.storageBuffer16BitAccess = VK_TRUE;
|
||||
features.shaderDrawParameters = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -278,6 +279,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
required_features12.samplerMirrorClampToEdge);
|
||||
check_feature(features11.storageBuffer16BitAccess, "storageBuffer16BitAccess",
|
||||
WindowContext::RequiredVulkan11Features().storageBuffer16BitAccess);
|
||||
check_feature(features11.shaderDrawParameters, "shaderDrawParameters",
|
||||
WindowContext::RequiredVulkan11Features().shaderDrawParameters);
|
||||
check_feature(features12.storageBuffer8BitAccess, "storageBuffer8BitAccess",
|
||||
required_features12.storageBuffer8BitAccess);
|
||||
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
||||
|
||||
@@ -63,7 +63,8 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
}
|
||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
||||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId ||
|
||||
kind == IR::StageInputKind::BaseVertex || kind == IR::StageInputKind::BaseInstance) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
||||
|
||||
@@ -476,6 +476,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
||||
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
||||
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
||||
case IR::StageInputKind::BaseVertex: return spv::BuiltInBaseVertex;
|
||||
case IR::StageInputKind::BaseInstance: return spv::BuiltInBaseInstance;
|
||||
default: return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
@@ -526,7 +528,9 @@ void DefineInputs(EmitterState& state) {
|
||||
case IR::StageInputKind::PrimitiveId:
|
||||
case IR::StageInputKind::InstanceIndex:
|
||||
case IR::StageInputKind::Layer:
|
||||
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::SampleId:
|
||||
case IR::StageInputKind::BaseVertex:
|
||||
case IR::StageInputKind::BaseInstance: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::WorkgroupId:
|
||||
case IR::StageInputKind::NumWorkgroups:
|
||||
case IR::StageInputKind::LocalInvocationId:
|
||||
@@ -802,6 +806,10 @@ void DefineModule(EmitterState& state) {
|
||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
||||
}
|
||||
if (InputVariableForKind(state, IR::StageInputKind::BaseVertex) != 0 ||
|
||||
InputVariableForKind(state, IR::StageInputKind::BaseInstance) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilityDrawParameters);
|
||||
}
|
||||
if (state.program.info.image_gather_extended) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||
}
|
||||
|
||||
@@ -1389,10 +1389,27 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
IR::U32(IR::Value(options.wave_size << 12u)));
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
||||
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u))));
|
||||
// Vulkan adds an indirect record's bases to the indices; the hardware passes them
|
||||
// only in the user SGPRs the packet patches.
|
||||
const auto& offsets = options.input_info.vertex->indirect_offsets;
|
||||
const auto index = [&](uint8_t slot, IR::StageInputKind kind,
|
||||
IR::StageInputKind base_kind) {
|
||||
if (offsets == ShaderIndirectOffsets {}) {
|
||||
return builtin(kind);
|
||||
}
|
||||
const auto base = builtin(base_kind);
|
||||
if (slot < options.user_data_count) {
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(options.user_data_base + slot),
|
||||
base);
|
||||
}
|
||||
return entry_ir.ISub(builtin(kind), base);
|
||||
};
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||
builtin(IR::StageInputKind::VertexIndex));
|
||||
index(offsets.vertex, IR::StageInputKind::VertexIndex,
|
||||
IR::StageInputKind::BaseVertex));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||
builtin(IR::StageInputKind::InstanceIndex));
|
||||
index(offsets.instance, IR::StageInputKind::InstanceIndex,
|
||||
IR::StageInputKind::BaseInstance));
|
||||
}
|
||||
}
|
||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||
|
||||
@@ -251,6 +251,8 @@ enum class StageInputKind {
|
||||
LocalInvocationId,
|
||||
LocalInvocationIndex,
|
||||
GlobalInvocationId,
|
||||
BaseVertex,
|
||||
BaseInstance,
|
||||
Parameter,
|
||||
};
|
||||
|
||||
|
||||
@@ -175,6 +175,8 @@ void AddBuiltinInput(ShaderInfo& info, StageInputKind kind) {
|
||||
case StageInputKind::GlobalInvocationId:
|
||||
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
||||
break;
|
||||
case StageInputKind::BaseVertex: AddInput(info, kind, 0, 1, "gl_BaseVertex"); break;
|
||||
case StageInputKind::BaseInstance: AddInput(info, kind, 0, 1, "gl_BaseInstance"); break;
|
||||
case StageInputKind::PackedAncillary:
|
||||
case StageInputKind::Parameter: break;
|
||||
}
|
||||
@@ -444,6 +446,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
case StageInputKind::InstanceIndex:
|
||||
case StageInputKind::FrontFacing:
|
||||
case StageInputKind::LocalInvocationIndex:
|
||||
case StageInputKind::BaseVertex:
|
||||
case StageInputKind::BaseInstance:
|
||||
if (component != 0u) {
|
||||
return Fail("typed scalar builtin component is out of range");
|
||||
}
|
||||
|
||||
@@ -422,6 +422,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
||||
key.push_back(info.lds_size_dwords);
|
||||
key.push_back(info.pa_cl_vs_out_cntl);
|
||||
key.push_back(info.user_clip_planes);
|
||||
key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u));
|
||||
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
|
||||
if (info.clip_space.enabled) {
|
||||
for (const float value: info.clip_space.scale) {
|
||||
|
||||
@@ -124,6 +124,18 @@ struct ShaderTessellationInputInfo {
|
||||
uint32_t output_topology = 0;
|
||||
};
|
||||
|
||||
// GS user-data slots an indirect draw packet writes its record's base vertex and first instance
|
||||
// to. Like the hardware index VGPRs, the vertex shader's indices exclude both bases once either
|
||||
// slot is set.
|
||||
struct ShaderIndirectOffsets {
|
||||
static constexpr uint8_t None = UINT8_MAX;
|
||||
|
||||
uint8_t vertex = None;
|
||||
uint8_t instance = None;
|
||||
|
||||
bool operator==(const ShaderIndirectOffsets&) const = default;
|
||||
};
|
||||
|
||||
struct ShaderVertexInputInfo {
|
||||
static constexpr int RES_MAX = 32;
|
||||
|
||||
@@ -146,6 +158,7 @@ struct ShaderVertexInputInfo {
|
||||
ShaderClipSpaceTransform clip_space;
|
||||
ShaderMeshInputInfo mesh;
|
||||
ShaderTessellationInputInfo tess;
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
bool fetch_external = false;
|
||||
bool fetch_embedded = false;
|
||||
};
|
||||
|
||||
+40
-29
@@ -9,6 +9,7 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -63,15 +64,21 @@ constexpr int PROT_GPU_WRITE = 0x20;
|
||||
|
||||
enum class GpuAccessMode { NoAccess, Read, Write, ReadWrite };
|
||||
|
||||
constexpr uint64_t PAGE_TABLE_POOL_SIZE = 4ull * 1024ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t PAGE_TABLE_GRANULARITY = 2ull * 1024ull * 1024ull;
|
||||
constexpr int PAGE_TABLE_POOL_ENTRIES =
|
||||
static_cast<int>(PAGE_TABLE_POOL_SIZE / PAGE_TABLE_GRANULARITY);
|
||||
constexpr uint64_t PAGE_TABLE_GRANULARITY = 2ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t DEFAULT_FLEXIBLE_MEMORY_SIZE = 448ull * 1024ull * 1024ull;
|
||||
// A game gets 12.5 GiB. A fixed 64 MiB cache and mapping table memory past the 64 MiB included
|
||||
// for each of the CPU, GPU and AMM tables come off the top; flexible and direct memory share what
|
||||
// is left. One 4 KiB table maps 2 MiB.
|
||||
constexpr uint64_t GUEST_MEMORY_BUDGET = 12800ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t RESERVED_CACHE_SIZE = 64ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t INCLUDED_TABLE_MEMORY = 64ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t MAPPING_TABLE_SIZE = 4ull * 1024ull;
|
||||
|
||||
static uint64_t g_flexible_memory_size = DEFAULT_FLEXIBLE_MEMORY_SIZE;
|
||||
static bool g_flexible_memory_size_frozen = false;
|
||||
static Graphics::RenderContext* g_gpu_resources = nullptr;
|
||||
static uint64_t g_guest_memory_size = GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE;
|
||||
static uint64_t g_flexible_memory_size = DEFAULT_FLEXIBLE_MEMORY_SIZE;
|
||||
static uint64_t g_cpu_table_memory = INCLUDED_TABLE_MEMORY;
|
||||
static uint64_t g_gpu_table_memory = INCLUDED_TABLE_MEMORY;
|
||||
static Graphics::RenderContext* g_gpu_resources = nullptr;
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
static void (*g_test_gpu_range)(bool, uint64_t, size_t) = nullptr;
|
||||
#endif
|
||||
@@ -664,11 +671,8 @@ public:
|
||||
|
||||
KYTY_CLASS_NO_COPY(PhysicalMemory);
|
||||
|
||||
static constexpr uint64_t TotalSize() { return static_cast<uint64_t>(13824) * 1024 * 1024; }
|
||||
static uint64_t Size() {
|
||||
EXIT_IF(g_flexible_memory_size >= TotalSize());
|
||||
return TotalSize() - g_flexible_memory_size;
|
||||
}
|
||||
static uint64_t TotalSize() { return g_guest_memory_size; }
|
||||
static uint64_t Size() { return TotalSize() - g_flexible_memory_size; }
|
||||
|
||||
bool Alloc(uint64_t search_start, uint64_t search_end, size_t len, size_t alignment,
|
||||
uint64_t* phys_addr_out, int memory_type,
|
||||
@@ -998,7 +1002,22 @@ static bool SelfTestSub64SharedPlaceholderAlias() {
|
||||
static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size);
|
||||
|
||||
void Initialize() {
|
||||
g_flexible_memory_size_frozen = true;
|
||||
const auto params = Loader::SystemContentGetMemoryParams();
|
||||
if (params.flexible_size != 0) {
|
||||
g_flexible_memory_size = params.flexible_size;
|
||||
}
|
||||
g_cpu_table_memory = std::max(params.cpu_table_memory, INCLUDED_TABLE_MEMORY);
|
||||
g_gpu_table_memory = std::max(params.gpu_table_memory, INCLUDED_TABLE_MEMORY);
|
||||
|
||||
const auto table_overhead = g_cpu_table_memory + g_gpu_table_memory +
|
||||
std::max(params.amm_table_memory, INCLUDED_TABLE_MEMORY) -
|
||||
3 * INCLUDED_TABLE_MEMORY;
|
||||
EXIT_IF(g_flexible_memory_size + table_overhead >= GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE);
|
||||
g_guest_memory_size = GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE - table_overhead;
|
||||
LOGF("\t flexible memory size = %" PRIu64 " MiB, direct memory size = %" PRIu64 " MiB\n",
|
||||
g_flexible_memory_size / (1024ull * 1024ull),
|
||||
(g_guest_memory_size - g_flexible_memory_size) / (1024ull * 1024ull));
|
||||
|
||||
VirtualMemory::Init();
|
||||
g_guest_address_space = std::make_unique<GuestAddressSpace>(PhysicalMemory::TotalSize());
|
||||
g_physical_memory = std::make_unique<PhysicalMemory>();
|
||||
@@ -1066,15 +1085,6 @@ void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func) {
|
||||
}
|
||||
}
|
||||
|
||||
void SetFlexibleMemorySize(uint64_t size) {
|
||||
constexpr uint64_t GuestPageSize = 0x4000;
|
||||
EXIT_IF(g_flexible_memory_size_frozen || g_guest_address_space != nullptr);
|
||||
EXIT_IF(size == 0 || (size & (GuestPageSize - 1u)) != 0 || size >= PhysicalMemory::TotalSize());
|
||||
g_flexible_memory_size = size;
|
||||
LOGF("\t flexible memory size = 0x%016" PRIx64 " (%" PRIu64 " MiB)\n", size,
|
||||
size / (1024ull * 1024ull));
|
||||
}
|
||||
|
||||
bool PhysicalMemory::Alloc(uint64_t search_start, uint64_t search_end, size_t len, size_t alignment,
|
||||
uint64_t* phys_addr_out, int memory_type, AllocationKind kind) {
|
||||
if (phys_addr_out == nullptr) {
|
||||
@@ -2655,12 +2665,12 @@ int KYTY_SYSV_ABI KernelGetPageTableStats(int* cpu_total, int* cpu_available, in
|
||||
const auto gpu_used =
|
||||
(g_virtual_ranges != nullptr ? g_virtual_ranges->CountPageTableEntries(true) : 0);
|
||||
|
||||
*cpu_total = PAGE_TABLE_POOL_ENTRIES;
|
||||
*gpu_total = PAGE_TABLE_POOL_ENTRIES;
|
||||
*cpu_available = PAGE_TABLE_POOL_ENTRIES -
|
||||
static_cast<int>(std::min<uint64_t>(cpu_used, PAGE_TABLE_POOL_ENTRIES));
|
||||
*gpu_available = PAGE_TABLE_POOL_ENTRIES -
|
||||
static_cast<int>(std::min<uint64_t>(gpu_used, PAGE_TABLE_POOL_ENTRIES));
|
||||
const auto cpu_tables = g_cpu_table_memory / MAPPING_TABLE_SIZE;
|
||||
const auto gpu_tables = g_gpu_table_memory / MAPPING_TABLE_SIZE;
|
||||
*cpu_total = static_cast<int>(cpu_tables);
|
||||
*gpu_total = static_cast<int>(gpu_tables);
|
||||
*cpu_available = static_cast<int>(cpu_tables - std::min(cpu_used, cpu_tables));
|
||||
*gpu_available = static_cast<int>(gpu_tables - std::min(gpu_used, gpu_tables));
|
||||
|
||||
LOGF_COLOR(Log::Color::Green,
|
||||
"\t cpu_total = %d\n"
|
||||
@@ -4381,7 +4391,8 @@ DebugStats GetDebugStats() {
|
||||
stats.direct_total = PhysicalMemory::Size();
|
||||
stats.flexible_total = FlexibleMemory::Size();
|
||||
stats.pool_committed = g_memory_pool_committed.load(std::memory_order_relaxed);
|
||||
stats.page_entries_total = PAGE_TABLE_POOL_ENTRIES;
|
||||
stats.cpu_page_entries_total = g_cpu_table_memory / MAPPING_TABLE_SIZE;
|
||||
stats.gpu_page_entries_total = g_gpu_table_memory / MAPPING_TABLE_SIZE;
|
||||
if (g_physical_memory != nullptr) {
|
||||
Common::LockGuard lock(g_physical_memory->GetMutex());
|
||||
for (const auto& [start, block]: g_physical_memory->GetPhysicalBlocks()) {
|
||||
|
||||
+4
-4
@@ -26,9 +26,10 @@ struct DebugStats {
|
||||
uint64_t flexible_total = 0;
|
||||
uint64_t flexible_used = 0;
|
||||
uint64_t pool_committed = 0;
|
||||
uint64_t cpu_page_entries = 0; // 2 MiB page-table entries in use.
|
||||
uint64_t gpu_page_entries = 0;
|
||||
uint64_t page_entries_total = 0; // Per side.
|
||||
uint64_t cpu_page_entries = 0; // 2 MiB page-table entries in use.
|
||||
uint64_t gpu_page_entries = 0;
|
||||
uint64_t cpu_page_entries_total = 0;
|
||||
uint64_t gpu_page_entries_total = 0;
|
||||
};
|
||||
[[nodiscard]] DebugStats GetDebugStats();
|
||||
|
||||
@@ -125,7 +126,6 @@ static_assert(sizeof(KernelMemoryPoolBlockStats) == 16,
|
||||
"KernelMemoryPoolBlockStats struct size is incorrect");
|
||||
|
||||
void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func);
|
||||
void SetFlexibleMemorySize(uint64_t size);
|
||||
int AllocateDirectMemory(int64_t search_start, int64_t search_end, size_t size, size_t alignment,
|
||||
int memory_type, int64_t* phys_addr_out, bool automatic = false);
|
||||
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
|
||||
|
||||
@@ -125,6 +125,7 @@ if(APPLE)
|
||||
install(TARGETS kyty_emulator DESTINATION "KytyPS5.app/Contents/MacOS")
|
||||
install(TARGETS kyty_updater DESTINATION "KytyPS5.app/Contents/MacOS")
|
||||
install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION "KytyPS5.app/Contents/Resources/assets/sounds")
|
||||
install(FILES ${KYTY_NOTO_SANS_FONTS} DESTINATION "KytyPS5.app/Contents/Resources/assets/fonts")
|
||||
install(DIRECTORY "${CMAKE_SOURCE_DIR}/assets/cursors" DESTINATION "KytyPS5.app/Contents/Resources/assets")
|
||||
# Deploy and ad-hoc sign the Qt frameworks and plugins in the bundle.
|
||||
find_program(KYTY_MACDEPLOYQT NAMES macdeployqt PATHS "${Qt6_DIR}/../../../bin" NO_DEFAULT_PATH)
|
||||
|
||||
+20
-46
@@ -2,10 +2,12 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
@@ -675,19 +677,6 @@ static bool font_is_italic(const FontState* font) {
|
||||
font->render_effect_slant != 0.0f;
|
||||
}
|
||||
|
||||
static const char* fallback_font_file_name(bool bold, bool italic) {
|
||||
if (bold && italic) {
|
||||
return "Roboto-BoldItalic.ttf";
|
||||
}
|
||||
if (bold) {
|
||||
return "Roboto-Bold.ttf";
|
||||
}
|
||||
if (italic) {
|
||||
return "Roboto-Italic.ttf";
|
||||
}
|
||||
return "Roboto-Regular.ttf";
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> read_file_bytes(const std::filesystem::path& path) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
@@ -709,49 +698,34 @@ static std::vector<uint8_t> read_file_bytes(const std::filesystem::path& path) {
|
||||
return data;
|
||||
}
|
||||
|
||||
static std::vector<std::filesystem::path> fallback_font_candidates(const char* file_name) {
|
||||
const auto rel =
|
||||
std::filesystem::path("3rdparty") / "tracy" / "profiler" / "src" / "font" / file_name;
|
||||
std::vector<std::filesystem::path> paths;
|
||||
|
||||
auto source_root = std::filesystem::path(__FILE__).parent_path().parent_path().parent_path();
|
||||
paths.push_back(source_root / rel);
|
||||
|
||||
std::error_code ec;
|
||||
auto cwd = std::filesystem::current_path(ec);
|
||||
if (!ec) {
|
||||
paths.push_back(cwd / rel);
|
||||
paths.push_back(cwd / ".." / rel);
|
||||
paths.push_back(cwd / ".." / ".." / rel);
|
||||
static std::vector<uint8_t> load_fallback_font_data(const char* file_name) {
|
||||
const char* base_path = SDL_GetBasePath();
|
||||
if (base_path == nullptr) {
|
||||
LOGF("Font: SDL_GetBasePath failed: %s\n", SDL_GetError());
|
||||
return {};
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> load_fallback_font_data(bool bold, bool italic) {
|
||||
for (const auto& path: fallback_font_candidates(fallback_font_file_name(bold, italic))) {
|
||||
auto data = read_file_bytes(path);
|
||||
if (!data.empty()) {
|
||||
return data;
|
||||
}
|
||||
// SDL returns UTF-8; path(const char*) would read it in the Windows ANSI code page.
|
||||
const auto path = Common::PathFromUtf8(base_path) / "assets" / "fonts" / file_name;
|
||||
auto data = read_file_bytes(path);
|
||||
if (data.empty()) {
|
||||
LOGF("Font: fallback font not found: %s\n", Common::PathToString(path).c_str());
|
||||
}
|
||||
|
||||
return {};
|
||||
return data;
|
||||
}
|
||||
|
||||
static const std::vector<uint8_t>& fallback_font_data(bool bold, bool italic) {
|
||||
static const auto regular = load_fallback_font_data(false, false);
|
||||
static const auto bold_data = load_fallback_font_data(true, false);
|
||||
static const auto italic_data = load_fallback_font_data(false, true);
|
||||
static const auto bold_italic = load_fallback_font_data(true, true);
|
||||
static const auto regular = load_fallback_font_data("NotoSans-Regular.ttf");
|
||||
static const auto bold_data = load_fallback_font_data("NotoSans-Bold.ttf");
|
||||
static const auto italic_data = load_fallback_font_data("NotoSans-Italic.ttf");
|
||||
static const auto bold_italic = load_fallback_font_data("NotoSans-BoldItalic.ttf");
|
||||
|
||||
if (bold && italic && !bold_italic.empty()) {
|
||||
if (bold && italic) {
|
||||
return bold_italic;
|
||||
}
|
||||
if (bold && !bold_data.empty()) {
|
||||
if (bold) {
|
||||
return bold_data;
|
||||
}
|
||||
if (italic && !italic_data.empty()) {
|
||||
if (italic) {
|
||||
return italic_data;
|
||||
}
|
||||
return regular;
|
||||
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
bool GetParamInt(const char* name, int32_t* value);
|
||||
bool GetParamString(const char* name, std::string* value);
|
||||
bool GetParamString(const char* name, char* value, size_t value_size);
|
||||
uint64_t GetFlexibleMemorySize();
|
||||
MemoryParams GetMemoryParams();
|
||||
|
||||
void DbgPrint();
|
||||
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
Common::Mutex m_mutex;
|
||||
bool m_opened = false;
|
||||
std::vector<Param> m_params;
|
||||
uint64_t m_flexible_memory_size = 0;
|
||||
MemoryParams m_memory_params;
|
||||
};
|
||||
|
||||
class PlayGo {
|
||||
@@ -172,11 +172,21 @@ void Psf::OpenJson(const std::filesystem::path& file_name) {
|
||||
add_int_param("userDefinedParam3", "USER_DEFINED_PARAM_3");
|
||||
add_int_param("userDefinedParam4", "USER_DEFINED_PARAM_4");
|
||||
|
||||
auto read_size = [](const nlohmann::ordered_json& object, const char* key, uint64_t* size,
|
||||
uint64_t unit = 1) {
|
||||
if (auto it = object.find(key); it != object.end()) {
|
||||
*size = it->get<uint64_t>() * unit;
|
||||
}
|
||||
};
|
||||
if (auto kernel = param_json.find("kernel");
|
||||
kernel != param_json.end() && kernel->is_object()) {
|
||||
if (auto it = kernel->find("flexibleMemorySize"); it != kernel->end()) {
|
||||
m_flexible_memory_size = it->get<uint64_t>();
|
||||
}
|
||||
read_size(*kernel, "flexibleMemorySize", &m_memory_params.flexible_size);
|
||||
read_size(*kernel, "cpuPageTableSize", &m_memory_params.cpu_table_memory);
|
||||
read_size(*kernel, "gpuPageTableSize", &m_memory_params.gpu_table_memory);
|
||||
}
|
||||
if (auto amm = param_json.find("amm"); amm != param_json.end() && amm->is_object()) {
|
||||
read_size(*amm, "pagetableMemorySizeInMib", &m_memory_params.amm_table_memory,
|
||||
1024ull * 1024ull);
|
||||
}
|
||||
|
||||
auto localized = param_json.find("localizedParameters");
|
||||
@@ -227,8 +237,8 @@ void Psf::Close() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
m_params.clear();
|
||||
m_flexible_memory_size = 0;
|
||||
m_opened = false;
|
||||
m_memory_params = {};
|
||||
m_opened = false;
|
||||
}
|
||||
|
||||
bool Psf::IsValid() {
|
||||
@@ -339,10 +349,10 @@ bool Psf::GetParamString(const char* name, char* value, size_t value_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t Psf::GetFlexibleMemorySize() {
|
||||
MemoryParams Psf::GetMemoryParams() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
return m_flexible_memory_size;
|
||||
return m_memory_params;
|
||||
}
|
||||
|
||||
PlayGo::~PlayGo() {
|
||||
@@ -646,10 +656,10 @@ bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_
|
||||
return sc->psf.GetParamString(name, value, value_size);
|
||||
}
|
||||
|
||||
uint64_t SystemContentGetFlexibleMemorySize() {
|
||||
MemoryParams SystemContentGetMemoryParams() {
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
return sc->psf.GetFlexibleMemorySize();
|
||||
return sc->psf.GetMemoryParams();
|
||||
}
|
||||
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path) {
|
||||
|
||||
@@ -14,7 +14,16 @@ void SystemContentLoadParamSfo(const std::filesystem::path& file_name);
|
||||
bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, std::string* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
|
||||
uint64_t SystemContentGetFlexibleMemorySize();
|
||||
// Memory sizes a game's param.json sets, in bytes; 0 where it sets none.
|
||||
struct MemoryParams {
|
||||
uint64_t flexible_size = 0;
|
||||
uint64_t cpu_table_memory = 0;
|
||||
uint64_t gpu_table_memory = 0;
|
||||
uint64_t amm_table_memory = 0;
|
||||
};
|
||||
|
||||
MemoryParams SystemContentGetMemoryParams();
|
||||
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path);
|
||||
bool SystemContentGetChunksNum(uint32_t* num);
|
||||
|
||||
|
||||
@@ -5684,6 +5684,25 @@ public:
|
||||
ImageHeapTestAccess::MemoryCount(heap) == 0u &&
|
||||
textures.GetImage(resident).heap_refs.load() == 0u,
|
||||
"unmapping the table kept its images pinned");
|
||||
// A smaller table enters once a second draw acquires it and leaves at the suspend point
|
||||
// after a frame that did not acquire it. Larger tables stay.
|
||||
const IR::ImageHeapRequest small{table, table_size / 2u, 32u, 0u, table_size / 64u, &sampled};
|
||||
Require(name, "first small draw",
|
||||
acquire(sampled, idle_table) && !heap.Acquire(small, view) &&
|
||||
!heap.Acquire(small, view),
|
||||
"a smaller table entered on its first draw");
|
||||
draw();
|
||||
Require(name, "second small draw", heap.Acquire(small, view),
|
||||
"a smaller table was declined on its second draw");
|
||||
context.RunGarbageCollector();
|
||||
Require(name, "acquired frame", ImageHeapTestAccess::MemoryCount(heap) == 2u,
|
||||
"a table left at the suspend point of a frame that acquired it");
|
||||
draw();
|
||||
context.RunGarbageCollector();
|
||||
Require(name, "idle frame",
|
||||
ImageHeapTestAccess::MemoryCount(heap) == 1u &&
|
||||
textures.GetImage(resident).heap_refs.load() == 0u,
|
||||
"a suspend point kept an idle smaller table pinned or dropped a larger one");
|
||||
// The idle table stays watched; destroying the heap must unwatch it before the page
|
||||
// manager fails on live watches.
|
||||
Require(name, "idle table",
|
||||
@@ -9959,37 +9978,78 @@ public:
|
||||
d16_storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
||||
const auto d16_storage_image =
|
||||
texture_cache.FindImage(d16_storage_desc, true);
|
||||
auto d16_storage_readback = CreateHostBuffer(
|
||||
name, sizeof(d16_fallback_values),
|
||||
vk::BufferUsageFlagBits::eTransferDst, std::vector<u32>(2, 0));
|
||||
auto &d16_storage_native = texture_cache.GetImage(d16_storage_image);
|
||||
d16_storage_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {},
|
||||
scheduler.Current().Handle());
|
||||
vk::BufferImageCopy d16_storage_copy{};
|
||||
d16_storage_copy.bufferRowLength = 4;
|
||||
d16_storage_copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0,
|
||||
0, 1};
|
||||
d16_storage_copy.imageExtent = {4, 1, 1};
|
||||
scheduler.Current().Handle().copyImageToBuffer(
|
||||
d16_storage_native.backing.image,
|
||||
vk::ImageLayout::eTransferSrcOptimal, d16_storage_readback.buffer, 1,
|
||||
&d16_storage_copy);
|
||||
HostReadBarrier(d16_storage_readback.buffer, d16_storage_readback.size,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferWrite);
|
||||
scheduler.Finish();
|
||||
const auto d16_storage_words = ReadBuffer(name, d16_storage_readback, 2);
|
||||
std::array<uint16_t, 4> d16_storage_values{};
|
||||
std::memcpy(d16_storage_values.data(), d16_storage_words.data(),
|
||||
sizeof(d16_storage_values));
|
||||
const auto read_texels = [&](ImageId id, vk::ImageAspectFlagBits aspect) {
|
||||
auto readback = CreateHostBuffer(name, 4 * sizeof(u32),
|
||||
vk::BufferUsageFlagBits::eTransferDst,
|
||||
std::vector<u32>(4, 0));
|
||||
auto &image = texture_cache.GetImage(id);
|
||||
image.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {},
|
||||
scheduler.Current().Handle());
|
||||
const vk::BufferImageCopy copy{0, 4, 0, {aspect, 0, 0, 1}, {}, {4, 1, 1}};
|
||||
scheduler.Current().Handle().copyImageToBuffer(
|
||||
image.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
readback.buffer, 1, ©);
|
||||
HostReadBarrier(readback.buffer, readback.size,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferWrite);
|
||||
scheduler.Finish();
|
||||
auto words = ReadBuffer(name, readback, 4);
|
||||
DestroyBuffer(&readback);
|
||||
return words;
|
||||
};
|
||||
const auto d16_storage_words =
|
||||
read_texels(d16_storage_image, vk::ImageAspectFlagBits::eColor);
|
||||
Require(
|
||||
name, "D16 fallback depth/storage copy",
|
||||
d16_storage_values == d16_fallback_values &&
|
||||
std::memcmp(d16_storage_words.data(), d16_fallback_values.data(),
|
||||
sizeof(d16_fallback_values)) == 0 &&
|
||||
d16_storage_image != d16_depth_image &&
|
||||
texture_cache.GetImage(d16_storage_image).IsGpuModified(),
|
||||
"D16 fallback reinterpreted four-byte host depth as two-byte color");
|
||||
DestroyBuffer(&d16_storage_readback);
|
||||
|
||||
// Dropping and re-adding the stencil plane keeps the D16 depth values.
|
||||
constexpr uint64_t d16_stenciled_offset = 0x33e000;
|
||||
constexpr std::array<uint16_t, 4> d16_stale_values{};
|
||||
std::memcpy(memory + d16_stenciled_offset, d16_fallback_values.data(),
|
||||
sizeof(d16_fallback_values));
|
||||
auto d16_stenciled_desc = d16_depth_desc;
|
||||
d16_stenciled_desc.info.data.address = base + d16_stenciled_offset;
|
||||
d16_stenciled_desc.info.stencil.address = base + 0x33f000;
|
||||
const auto d16_stenciled_image =
|
||||
texture_cache.FindImage(d16_stenciled_desc);
|
||||
(void)texture_cache.FindDepthTarget(d16_stenciled_image,
|
||||
d16_stenciled_desc);
|
||||
texture_cache.MarkGpuWritten(d16_stenciled_image);
|
||||
// Stale guest bytes: the values below can come only from image copies.
|
||||
Libs::LibKernel::Memory::WriteBacking(base + d16_stenciled_offset,
|
||||
d16_stale_values.data(),
|
||||
sizeof(d16_stale_values));
|
||||
auto d16_native_desc = d16_stenciled_desc;
|
||||
d16_native_desc.info.stencil = {};
|
||||
d16_native_desc.info.pixel_format = vk::Format::eD16Unorm;
|
||||
d16_native_desc.view_info.format = vk::Format::eD16Unorm;
|
||||
d16_native_desc.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
const auto d16_native_image = texture_cache.FindImage(d16_native_desc);
|
||||
const auto d16_native_words =
|
||||
read_texels(d16_native_image, vk::ImageAspectFlagBits::eDepth);
|
||||
Require(name, "D16 fallback depth/native depth copy",
|
||||
std::memcmp(d16_native_words.data(), d16_fallback_values.data(),
|
||||
sizeof(d16_fallback_values)) == 0 &&
|
||||
texture_cache.GetImage(d16_native_image).backing.format ==
|
||||
vk::Format::eD16Unorm,
|
||||
"D16 depth lost its values when its target dropped the stencil");
|
||||
const auto d16_restenciled_image =
|
||||
texture_cache.FindImage(d16_stenciled_desc);
|
||||
const auto d16_restenciled_words =
|
||||
read_texels(d16_restenciled_image, vk::ImageAspectFlagBits::eDepth);
|
||||
bool d16_promoted = d16_restenciled_image != d16_native_image;
|
||||
for (size_t i = 0; i < d16_fallback_values.size(); ++i) {
|
||||
d16_promoted &= (d16_restenciled_words[i] & 0x00ffffffu) ==
|
||||
EncodeD16AsD24(d16_fallback_values[i]);
|
||||
}
|
||||
Require(name, "D16 native depth/fallback depth copy", d16_promoted,
|
||||
"D16 depth lost its values when its target regained the stencil");
|
||||
|
||||
if (!texture_cache.GetImage(combined_destination_image).IsGpuModified()) {
|
||||
texture_cache.MarkGpuWritten(combined_destination_image);
|
||||
@@ -19744,11 +19804,12 @@ public:
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
shaders.SetGsUserSgpr(i, static_cast<u32>(tables[i / 2] >> (i % 2 * 32)));
|
||||
}
|
||||
const auto check = [&](const char *step, uint64_t vertex_shader, bool indexed, u32 first) {
|
||||
const auto check = [&](const char *step, uint64_t vertex_shader, bool indexed, u32 first,
|
||||
ShaderIndirectOffsets offsets = {}) {
|
||||
shaders.SetEsShaderBase(vertex_shader);
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {});
|
||||
const DrawIndirectArgs records{args_address + first * stride, 2, stride};
|
||||
const DrawIndirectArgs records{args_address + first * stride, 2, stride, offsets};
|
||||
const bool drawn = indexed
|
||||
? RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(),
|
||||
{.index_count = 3, .index_addr = reinterpret_cast<const void *>(index_address),
|
||||
@@ -19774,6 +19835,16 @@ public:
|
||||
"no cached graphics pipeline named only compiled modules");
|
||||
check("recreated indexed embedded fetch", fetch_vs, true, 0);
|
||||
check("recreated auto native fetch", vertex_address, false, 6);
|
||||
// Patched user SGPRs 0/1 read the records' base vertex 7 and first instance 5, not the
|
||||
// table pointers they hold, and the index VGPRs exclude both bases.
|
||||
check("indexed patched offsets", indirect_vs, true, 0, {0, 1});
|
||||
check("auto patched offsets", indirect_vs, false, 4, {0, 1});
|
||||
// Slots 4/5 lie past USER_SGPR, so the tables survive; rewritten fetches keep Vulkan's
|
||||
// vertex offset.
|
||||
check("indexed patched embedded fetch", fetch_vs, true, 0, {4, 5});
|
||||
// Patching one offset still leaves both bases out of the index VGPRs.
|
||||
check("auto instance-only patch", vertex_address, false, 5,
|
||||
{ShaderIndirectOffsets::None, 1});
|
||||
}
|
||||
|
||||
// A fourth VS invocation reads beyond the descriptor instead of reconstructing the corner.
|
||||
@@ -51509,6 +51580,16 @@ void CheckPm4DrawIndirectMultiPacket(RenderContext &renderer) {
|
||||
auto_packet[3] = Pm4::SH_NOP;
|
||||
arguments[3] = 103;
|
||||
check_offsets(auto_packet, hs, {53, 101}, "disabled destinations");
|
||||
// GS destinations, with an index buffer size for indexed draws, leave the records to the
|
||||
// GPU and the CP writes no SGPR. A first-index destination stays on the CPU.
|
||||
processor.SetIndexBufferSize(3);
|
||||
arguments = {0, 1, 61, 17, 19};
|
||||
check_offsets(indexed_packet, gs, {41, 7, 8}, "GPU indexed records");
|
||||
auto_packet[2] = Pm4::SPI_SHADER_USER_DATA_GS_0 + 9;
|
||||
auto_packet[3] = Pm4::SPI_SHADER_USER_DATA_GS_0 + 10;
|
||||
check_offsets(auto_packet, gs, {41, 7, 8}, "GPU records");
|
||||
indexed_packet[3] |= 1u << 28u;
|
||||
check_offsets(indexed_packet, gs, {17, 19, 61}, "first-index destination");
|
||||
processor.BufferFlush();
|
||||
std::printf("[host] %-32s ok\n", "Pm4DrawIndirectMulti");
|
||||
}
|
||||
|
||||
@@ -88,9 +88,14 @@ using Libs::LibKernel::Memory::VirtualQueryInfo;
|
||||
|
||||
// Prospero ABI?
|
||||
constexpr uint64_t SceKernelPageSize = 0x4000;
|
||||
constexpr uint64_t SceKernelTotalPhysicalSize = 13824ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t SceKernelFlexibleMemorySize = 448ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t TestFlexibleMemorySize = 3072ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t GuestMemoryBudget = 12800ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t ReservedCacheSize = 64ull * 1024ull * 1024ull;
|
||||
// The 128 MiB CPU and 96 MiB AMM mapping tables add 64 + 32 MiB; a zero GPU table size adds none.
|
||||
constexpr uint64_t TestCpuTableMemory = 128ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t TestAmmTableMemory = 96ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t TestTableOverhead = 96ull * 1024ull * 1024ull;
|
||||
constexpr int SceKernelProtCpuRead = 0x01;
|
||||
constexpr int SceKernelProtCpuRw = 0x02;
|
||||
constexpr int SceKernelProtCpuExec = 0x04;
|
||||
@@ -175,7 +180,9 @@ void InitSubsystems() {
|
||||
const auto param_json = std::filesystem::temp_directory_path() /
|
||||
("kyty_virtual_memory_" +
|
||||
std::to_string(reinterpret_cast<uintptr_t>(&initialized)) + ".json");
|
||||
constexpr char json[] = R"({"kernel":{"flexibleMemorySize":3221225472}})";
|
||||
constexpr char json[] =
|
||||
R"({"kernel":{"flexibleMemorySize":3221225472,"cpuPageTableSize":134217728,)"
|
||||
R"("gpuPageTableSize":0},"amm":{"pagetableMemorySizeInMib":96}})";
|
||||
Common::File param_file;
|
||||
Check("InitSubsystems", param_file.Create(param_json), "failed to create temporary param.json");
|
||||
uint32_t bytes_written = 0;
|
||||
@@ -185,16 +192,19 @@ void InitSubsystems() {
|
||||
"failed to write temporary param.json");
|
||||
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
const auto params = Loader::SystemContentGetMemoryParams();
|
||||
Check("InitSubsystems", Common::File::DeleteFile(param_json),
|
||||
"failed to remove temporary param.json");
|
||||
Check("InitSubsystems", flexible_memory_size == TestFlexibleMemorySize,
|
||||
Check("InitSubsystems", params.flexible_size == TestFlexibleMemorySize,
|
||||
"failed to read flexible memory size from param.json");
|
||||
Check("InitSubsystems",
|
||||
params.cpu_table_memory == TestCpuTableMemory && params.gpu_table_memory == 0 &&
|
||||
params.amm_table_memory == TestAmmTableMemory,
|
||||
"failed to read mapping table sizes from param.json");
|
||||
size_t default_flexible_memory_size = 0;
|
||||
Libs::LibKernel::Memory::KernelConfiguredFlexibleMemorySize(&default_flexible_memory_size);
|
||||
Check("InitSubsystems", default_flexible_memory_size == SceKernelFlexibleMemorySize,
|
||||
"flexible memory does not default to 448 MiB without param.json");
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
|
||||
subsystems.Initialize<Libs::LibKernel::Memory::Lifecycle>();
|
||||
|
||||
@@ -822,8 +832,8 @@ void TestFlexibleMemoryCapacityIsBootFixed() {
|
||||
Check(test, configured == TestFlexibleMemorySize,
|
||||
"boot flexible pool did not use the param.json value");
|
||||
Check(test, configured == baseline, "boot flexible pool did not start at configured capacity");
|
||||
Check(test, backing == SceKernelTotalPhysicalSize,
|
||||
"boot backing is not the single 13.5 GiB physical file");
|
||||
Check(test, backing == GuestMemoryBudget - ReservedCacheSize - TestTableOverhead,
|
||||
"boot backing is not the 12.5 GiB budget less the reserved cache and table overhead");
|
||||
Check(test, backing == Libs::LibKernel::Memory::KernelGetDirectMemorySize() + configured,
|
||||
"direct and flexible regions do not partition the boot backing");
|
||||
|
||||
@@ -1409,7 +1419,10 @@ void TestPageTableStatsTrackRangeEdits() {
|
||||
"KernelGetPageTableStats");
|
||||
return stats;
|
||||
};
|
||||
const auto baseline = read_stats();
|
||||
// A 4 KiB table maps 2 MiB: 128 MiB of CPU tables gives 32768, the default 64 MiB GPU 16384.
|
||||
const auto baseline = read_stats();
|
||||
Check(test, baseline[0] == 32768 && baseline[2] == 16384,
|
||||
"page-table totals do not follow the configured table memory");
|
||||
const auto expect_used = [&](int cpu, int gpu, const char* action) {
|
||||
const auto stats = read_stats();
|
||||
Check(test,
|
||||
|
||||
Reference in new issue
Block a user