mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 07:52:39 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
358333125d | ||
|
|
fd6b28c4b2 | ||
|
|
6cee2dc0bd | ||
|
|
a526a8b9c9 | ||
|
|
1ec69f8e7d | ||
|
|
2463e3aaf0 | ||
|
|
556538bed5 | ||
|
|
770438cee0 |
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)
|
||||
|
||||
@@ -407,6 +407,8 @@ add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(build_flags_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/BuildFlagsTests.cpp")
|
||||
|
||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||
@@ -650,6 +652,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME build_flags COMMAND $<TARGET_FILE:build_flags_tests>)
|
||||
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
||||
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
||||
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||
@@ -743,6 +746,7 @@ if(BUILD_TESTING)
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
build_flags_tests
|
||||
archive_file_tests
|
||||
trophy_system_tests
|
||||
avplayer_file_tests
|
||||
@@ -811,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)
|
||||
|
||||
@@ -976,16 +976,28 @@ void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector
|
||||
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
|
||||
// A label beside GPU-written bytes goes to the backing store, so they are not downloaded.
|
||||
// Other labels go through the guest mapping: its fault keeps a clean cached copy current and
|
||||
// downloads GPU-written bytes under the label before the store.
|
||||
const auto write_label = [this, address](const void* value, uint64_t size) {
|
||||
const auto vaddr = reinterpret_cast<uint64_t>(address);
|
||||
auto& buffers = m_renderer.GetBufferCache();
|
||||
if (!GuestRange {vaddr, size}.Valid() || !buffers.IsRegionGpuModified(vaddr, size) ||
|
||||
buffers.HasGpuDirtyBytes(vaddr, size) ||
|
||||
!LibKernel::Memory::TryWriteBacking(vaddr, value, size)) {
|
||||
std::memcpy(address, value, size);
|
||||
}
|
||||
};
|
||||
// EndOfPipeData: kNone, kImmediate32b, kImmediate64b, kRefClk; 5 reads GDS.
|
||||
switch (data_sel) {
|
||||
case 0: break;
|
||||
case 1: {
|
||||
const auto value = static_cast<uint32_t>(data);
|
||||
std::memcpy(address, &value, sizeof(value));
|
||||
write_label(&value, sizeof(value));
|
||||
break;
|
||||
}
|
||||
case 3: data = Sync::ReadReferenceClock(); [[fallthrough]];
|
||||
case 2: std::memcpy(address, &data, sizeof(data)); break;
|
||||
case 2: write_label(&data, sizeof(data)); break;
|
||||
case 5: {
|
||||
GetScheduler().Finish();
|
||||
const auto gds = static_cast<uint32_t>(data);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -118,6 +119,13 @@ public:
|
||||
|
||||
class const_iterator final {
|
||||
public:
|
||||
// libstdc++'s algorithms need these. Input, not forward, because operator* returns a copy.
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = OwnerT;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = OwnerT;
|
||||
|
||||
[[nodiscard]] OwnerT operator*() const noexcept { return m_owners->At(m_index); }
|
||||
const_iterator& operator++() noexcept {
|
||||
++m_index;
|
||||
|
||||
@@ -125,6 +125,14 @@ void DynamicState::Commit(const GraphicContext& graphics, vk::CommandBuffer cmdb
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dirty_state.cull_mode) {
|
||||
dirty_state.cull_mode = false;
|
||||
cmdbuf.setCullMode(cull_mode);
|
||||
}
|
||||
if (dirty_state.front_face) {
|
||||
dirty_state.front_face = false;
|
||||
cmdbuf.setFrontFace(front_face);
|
||||
}
|
||||
if (dirty_state.blend_constants) {
|
||||
dirty_state.blend_constants = false;
|
||||
cmdbuf.setBlendConstants(blend_constants.data());
|
||||
|
||||
@@ -52,6 +52,9 @@ struct DynamicState {
|
||||
bool stencil_back_write_mask : 1;
|
||||
bool stencil_back_compare_mask : 1;
|
||||
|
||||
bool cull_mode : 1;
|
||||
bool front_face : 1;
|
||||
|
||||
bool blend_constants : 1;
|
||||
bool color_write_enables : 1;
|
||||
bool line_width : 1;
|
||||
@@ -86,6 +89,9 @@ struct DynamicState {
|
||||
uint32_t stencil_back_write_mask {};
|
||||
uint32_t stencil_back_compare_mask {};
|
||||
|
||||
vk::CullModeFlags cull_mode {};
|
||||
vk::FrontFace front_face {};
|
||||
|
||||
std::array<float, 4> blend_constants {};
|
||||
std::array<vk::Bool32, RENDER_COLOR_ATTACHMENTS_MAX> color_write_enables {};
|
||||
uint32_t num_color_write_enables {};
|
||||
@@ -221,6 +227,20 @@ struct DynamicState {
|
||||
}
|
||||
}
|
||||
|
||||
void SetCullMode(const vk::CullModeFlags cull_mode_) {
|
||||
if (cull_mode != cull_mode_) {
|
||||
cull_mode = cull_mode_;
|
||||
dirty_state.cull_mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetFrontFace(const vk::FrontFace front_face_) {
|
||||
if (front_face != front_face_) {
|
||||
front_face = front_face_;
|
||||
dirty_state.front_face = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetBlendConstants(const std::array<float, 4> blend_constants_) {
|
||||
if (blend_constants != blend_constants_) {
|
||||
blend_constants = blend_constants_;
|
||||
|
||||
@@ -484,7 +484,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||
|
||||
@@ -1003,15 +1003,11 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(const CommandBuffer&
|
||||
}
|
||||
static_params.alpha_to_coverage_enable = ps_active && ctx.GetShaderRegisters().AlphaToMask();
|
||||
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
||||
static_params.cull_back = !rect_list && mc.cull_back;
|
||||
static_params.cull_front = !rect_list && mc.cull_front;
|
||||
static_params.face = mc.face;
|
||||
static_params.provoking_vtx_last = mc.provoking_vtx_last;
|
||||
static_params.polygon_mode =
|
||||
ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back);
|
||||
ResolvePolygonMode(mc, !rect_list && mc.cull_front, !rect_list && mc.cull_back);
|
||||
if (tessellation) {
|
||||
static_params.patch_control_points =
|
||||
static_cast<uint8_t>(vs_input_info.tess.input_control_points);
|
||||
static_params.patch_control_points = vs_input_info.tess.input_control_points;
|
||||
} else if (!mesh && static_params.topology == vk::PrimitiveTopology::ePatchList) {
|
||||
// Rectangle lists expand through generated tessellation stages.
|
||||
static_params.patch_control_points = 3;
|
||||
|
||||
@@ -54,15 +54,12 @@ struct PipelineStaticParameters {
|
||||
bool depth_clip_enable = true;
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
// 0 without tessellation, 3 for a rectangle list, else the input control points.
|
||||
uint8_t patch_control_points = 0;
|
||||
uint32_t patch_control_points = 0;
|
||||
bool primitive_restart_enable = false;
|
||||
uint32_t samples = 1;
|
||||
bool sample_shading_enable = false;
|
||||
bool alpha_to_coverage_enable = false;
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool cull_front = false;
|
||||
bool cull_back = false;
|
||||
bool face = false;
|
||||
bool provoking_vtx_last = false;
|
||||
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
|
||||
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
|
||||
@@ -219,17 +219,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
vk::PipelineViewportStateCreateInfo viewport_state {};
|
||||
viewport_state.pNext = &depth_clip_control;
|
||||
|
||||
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
|
||||
if (static_params.cull_back) {
|
||||
cull_mode |= vk::CullModeFlagBits::eBack;
|
||||
}
|
||||
if (static_params.cull_front) {
|
||||
cull_mode |= vk::CullModeFlagBits::eFront;
|
||||
}
|
||||
|
||||
vk::FrontFace front_face =
|
||||
(static_params.face ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise);
|
||||
|
||||
vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
|
||||
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
@@ -250,8 +239,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
provoking_vertex.pNext = rasterizer.pNext;
|
||||
rasterizer.pNext = &provoking_vertex;
|
||||
}
|
||||
rasterizer.cullMode = cull_mode;
|
||||
rasterizer.frontFace = front_face;
|
||||
rasterizer.polygonMode = static_params.polygon_mode;
|
||||
rasterizer.lineWidth = 1.0f;
|
||||
|
||||
@@ -347,6 +334,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
vk::DynamicState::eViewportWithCount,
|
||||
vk::DynamicState::eScissorWithCount,
|
||||
vk::DynamicState::eLineWidth,
|
||||
vk::DynamicState::eCullMode,
|
||||
vk::DynamicState::eFrontFace,
|
||||
vk::DynamicState::eDepthTestEnable,
|
||||
vk::DynamicState::eDepthWriteEnable,
|
||||
vk::DynamicState::eDepthCompareOp,
|
||||
|
||||
@@ -403,6 +403,20 @@ static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState
|
||||
dynamic_state.SetDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
||||
}
|
||||
|
||||
// Rect lists are drawn without culling.
|
||||
vk::CullModeFlags cull_mode {};
|
||||
if (!Prospero::IsRectList(buffer.GetUserConfig().GetPrimType())) {
|
||||
if (mode.cull_front) {
|
||||
cull_mode |= vk::CullModeFlagBits::eFront;
|
||||
}
|
||||
if (mode.cull_back) {
|
||||
cull_mode |= vk::CullModeFlagBits::eBack;
|
||||
}
|
||||
}
|
||||
dynamic_state.SetCullMode(cull_mode);
|
||||
dynamic_state.SetFrontFace(mode.face ? vk::FrontFace::eClockwise
|
||||
: vk::FrontFace::eCounterClockwise);
|
||||
|
||||
dynamic_state.SetStencilTestEnabled(depth.stencil_test_enable);
|
||||
if (depth.stencil_test_enable) {
|
||||
const auto& front = depth.stencil_front;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#version 450
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
|
||||
layout(constant_id = 0) const uint D32 = 0u;
|
||||
layout(local_size_x = 64) in;
|
||||
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
||||
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
|
||||
layout(set = 0, binding = 1, std430) buffer Output { uint16_t data[]; } output_buffer;
|
||||
|
||||
layout(push_constant, std430) uniform Conversion {
|
||||
uint src_base;
|
||||
@@ -32,9 +33,5 @@ void main() {
|
||||
}
|
||||
uint src = params.src_base + y * params.source_pitch + x * 4u;
|
||||
uint dst = params.dst_base + y * params.target_pitch + x * 2u;
|
||||
uint shift = (dst & 2u) * 8u;
|
||||
uint mask = 0xffffu << shift;
|
||||
uint word = dst >> 2u;
|
||||
atomicAnd(output_buffer.data[word], ~mask);
|
||||
atomicOr(output_buffer.data[word], decode(input_buffer.data[src >> 2u]) << shift);
|
||||
output_buffer.data[dst >> 1u] = uint16_t(decode(input_buffer.data[src >> 2u]));
|
||||
}
|
||||
@@ -118,15 +118,14 @@ ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
|
||||
}
|
||||
|
||||
Program::~Program() {
|
||||
// Planning expressions can refer to block values but outlive block storage in the base class.
|
||||
// Planning expressions can refer to block values but outlive block storage in the base class,
|
||||
// and values cross block boundaries: detach everything before any storage is destroyed.
|
||||
for (auto& inst: value_storage) {
|
||||
inst.Invalidate();
|
||||
inst.DropArgs();
|
||||
}
|
||||
// Values may cross block boundaries. Detach all arguments before any block starts destroying
|
||||
// its instruction storage so reverse-use links always point to live definitions.
|
||||
for (auto* block: blocks) {
|
||||
for (auto& inst: *block) {
|
||||
inst.Invalidate();
|
||||
inst.DropArgs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,22 +25,6 @@ Value Value::F32(float value) {
|
||||
return Value(Type::F32, std::bit_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
bool Value::IsEmpty() const {
|
||||
return type == Type::Void;
|
||||
}
|
||||
|
||||
bool Value::IsImmediate() const {
|
||||
return type != Type::Opaque;
|
||||
}
|
||||
|
||||
bool Value::IsIdentity() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
||||
}
|
||||
|
||||
bool Value::IsPhi() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
||||
}
|
||||
|
||||
Type Value::GetType() const {
|
||||
if (IsPhi()) {
|
||||
return inst->Flags<Type>();
|
||||
@@ -56,19 +40,6 @@ Inst* Value::Instruction() const {
|
||||
return inst;
|
||||
}
|
||||
|
||||
Inst* Value::TryInstruction() const {
|
||||
return type == Type::Opaque ? inst : nullptr;
|
||||
}
|
||||
|
||||
Inst* Value::ResolveInstruction() const {
|
||||
EXIT_IF(type != Type::Opaque);
|
||||
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
||||
}
|
||||
|
||||
Value Value::Resolve() const {
|
||||
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
||||
}
|
||||
|
||||
ScalarReg Value::ScalarRegister() const {
|
||||
EXIT_IF(type != Type::ScalarReg);
|
||||
return scalar_reg;
|
||||
@@ -94,11 +65,6 @@ uint16_t Value::U16() const {
|
||||
return imm_u16;
|
||||
}
|
||||
|
||||
uint32_t Value::U32() const {
|
||||
EXIT_IF(type != Type::U32);
|
||||
return imm_u32;
|
||||
}
|
||||
|
||||
uint64_t Value::U64() const {
|
||||
EXIT_IF(type != Type::U64);
|
||||
return imm_u64;
|
||||
@@ -152,10 +118,6 @@ Inst::~Inst() {
|
||||
ClearArgs();
|
||||
}
|
||||
|
||||
ValueOpcode Inst::GetOpcode() const {
|
||||
return opcode;
|
||||
}
|
||||
|
||||
Type Inst::GetType() const {
|
||||
if (opcode == ValueOpcode::Phi) {
|
||||
return static_cast<Type>(flags);
|
||||
@@ -178,51 +140,25 @@ size_t Inst::UseCount() const {
|
||||
return uses.size();
|
||||
}
|
||||
|
||||
size_t Inst::NumArgs() const {
|
||||
return num_args == PhiArity ? phi_args.size() : num_args;
|
||||
}
|
||||
|
||||
size_t Inst::NumPhiBlocks() const {
|
||||
return num_args == PhiArity ? phi_args.size() : 0;
|
||||
}
|
||||
|
||||
Value Inst::Arg(size_t index) const {
|
||||
EXIT_IF(index >= NumArgs());
|
||||
if (num_args <= InlineArity) {
|
||||
return fixed_args[index];
|
||||
}
|
||||
return num_args == PhiArity ? phi_args[index].second : large_args[index];
|
||||
}
|
||||
|
||||
Block* Inst::PhiBlock(size_t index) const {
|
||||
EXIT_IF(opcode != ValueOpcode::Phi || index >= phi_args.size());
|
||||
return phi_args[index].first;
|
||||
}
|
||||
|
||||
Block* Inst::Parent() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
const std::vector<Use>& Inst::Uses() const {
|
||||
return uses;
|
||||
}
|
||||
|
||||
void Inst::SetParent(Block* block) {
|
||||
parent = block;
|
||||
}
|
||||
|
||||
void Inst::SetArg(size_t index, Value value) {
|
||||
const auto old = Arg(index);
|
||||
if (auto* old_inst = old.TryInstruction(); old_inst != nullptr) {
|
||||
auto& arg = ArgSlot(*this, index);
|
||||
if (auto* old_inst = arg.TryInstruction(); old_inst != nullptr) {
|
||||
RemoveUse(old_inst, index);
|
||||
}
|
||||
if (num_args <= InlineArity) {
|
||||
fixed_args[index] = value;
|
||||
} else if (num_args == PhiArity) {
|
||||
phi_args[index].second = value;
|
||||
} else {
|
||||
large_args[index] = value;
|
||||
}
|
||||
arg = value;
|
||||
if (auto* new_inst = value.TryInstruction(); new_inst != nullptr) {
|
||||
AddUse(new_inst, index);
|
||||
}
|
||||
@@ -238,11 +174,15 @@ void Inst::AddPhiOperand(Block* predecessor, Value value) {
|
||||
}
|
||||
|
||||
void Inst::ReplaceUsesWith(Value replacement, bool preserve) {
|
||||
const auto old_uses = uses;
|
||||
for (const auto& use: old_uses) {
|
||||
use.user->SetArg(use.operand, replacement);
|
||||
}
|
||||
// Detach the arguments first (a phi can use itself), then move every use over in order.
|
||||
Invalidate();
|
||||
const auto old_uses = std::exchange(uses, {});
|
||||
for (const auto& use: old_uses) {
|
||||
ArgSlot(*use.user, use.operand) = replacement;
|
||||
}
|
||||
if (auto* target = replacement.TryInstruction(); target != nullptr) {
|
||||
target->uses.insert(target->uses.end(), old_uses.begin(), old_uses.end());
|
||||
}
|
||||
if (preserve) {
|
||||
opcode = ValueOpcode::Identity;
|
||||
num_args = 1;
|
||||
@@ -272,6 +212,10 @@ void Inst::ClearArgs() {
|
||||
RemoveUse(value_inst, index);
|
||||
}
|
||||
}
|
||||
DropArgs();
|
||||
}
|
||||
|
||||
void Inst::DropArgs() {
|
||||
if (num_args == PhiArity) {
|
||||
std::destroy_at(&phi_args);
|
||||
std::construct_at(&fixed_args);
|
||||
|
||||
@@ -33,14 +33,14 @@ public:
|
||||
static Value F16(uint16_t bits);
|
||||
static Value F32(float value);
|
||||
|
||||
[[nodiscard]] bool IsEmpty() const;
|
||||
[[nodiscard]] bool IsImmediate() const;
|
||||
[[nodiscard]] bool IsEmpty() const { return type == Type::Void; }
|
||||
[[nodiscard]] bool IsImmediate() const { return type != Type::Opaque; }
|
||||
[[nodiscard]] bool IsIdentity() const;
|
||||
[[nodiscard]] bool IsPhi() const;
|
||||
[[nodiscard]] Type GetType() const;
|
||||
|
||||
[[nodiscard]] Inst* Instruction() const;
|
||||
[[nodiscard]] Inst* TryInstruction() const;
|
||||
[[nodiscard]] Inst* TryInstruction() const { return type == Type::Opaque ? inst : nullptr; }
|
||||
[[nodiscard]] Inst* ResolveInstruction() const;
|
||||
[[nodiscard]] Value Resolve() const;
|
||||
[[nodiscard]] ScalarReg ScalarRegister() const;
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
Inst(Inst&&) = delete;
|
||||
Inst& operator=(Inst&&) = delete;
|
||||
|
||||
[[nodiscard]] ValueOpcode GetOpcode() const;
|
||||
[[nodiscard]] ValueOpcode GetOpcode() const { return opcode; }
|
||||
[[nodiscard]] Type GetType() const;
|
||||
[[nodiscard]] bool MayHaveSideEffects() const;
|
||||
[[nodiscard]] bool HasUses() const;
|
||||
@@ -121,8 +121,8 @@ public:
|
||||
[[nodiscard]] size_t NumPhiBlocks() const;
|
||||
[[nodiscard]] Value Arg(size_t index) const;
|
||||
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
||||
[[nodiscard]] Block* Parent() const;
|
||||
[[nodiscard]] const std::vector<Use>& Uses() const;
|
||||
[[nodiscard]] Block* Parent() const { return parent; }
|
||||
[[nodiscard]] const std::vector<Use>& Uses() const { return uses; }
|
||||
// SRT walker output index, assigned by the resource plan that owns this instruction.
|
||||
[[nodiscard]] uint32_t WalkOutput() const { return walk_output; }
|
||||
void SetWalkOutput(uint32_t index) const { walk_output = index; }
|
||||
@@ -138,6 +138,8 @@ public:
|
||||
void AddPhiOperand(Block* predecessor, Value value);
|
||||
void ReplaceUsesWith(Value replacement, bool preserve = true);
|
||||
void Invalidate();
|
||||
// Drops the arguments without updating their use lists, for teardown that destroys both sides.
|
||||
void DropArgs();
|
||||
|
||||
template <typename T>
|
||||
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
|
||||
@@ -157,6 +159,8 @@ public:
|
||||
private:
|
||||
friend void EliminateDeadCode(const std::vector<Block*>& blocks);
|
||||
|
||||
template <typename Self>
|
||||
static auto& ArgSlot(Self& self, size_t index);
|
||||
void AddUse(Inst* used, size_t operand);
|
||||
void RemoveUse(Inst* used, size_t operand);
|
||||
void ClearArgs();
|
||||
@@ -181,4 +185,44 @@ private:
|
||||
|
||||
static_assert(sizeof(Inst) <= 120, "Inst operand and backend storage unintentionally increased");
|
||||
|
||||
// Called per operand by the IR passes and the resource walker; inline, as the build has no LTO.
|
||||
inline bool Value::IsIdentity() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
||||
}
|
||||
|
||||
inline bool Value::IsPhi() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
||||
}
|
||||
|
||||
inline Inst* Value::ResolveInstruction() const {
|
||||
EXIT_IF(type != Type::Opaque);
|
||||
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
||||
}
|
||||
|
||||
inline Value Value::Resolve() const {
|
||||
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
||||
}
|
||||
|
||||
inline uint32_t Value::U32() const {
|
||||
EXIT_IF(type != Type::U32);
|
||||
return imm_u32;
|
||||
}
|
||||
|
||||
inline size_t Inst::NumArgs() const {
|
||||
return num_args == PhiArity ? phi_args.size() : num_args;
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
auto& Inst::ArgSlot(Self& self, size_t index) {
|
||||
EXIT_IF(index >= self.NumArgs());
|
||||
if (self.num_args <= InlineArity) {
|
||||
return self.fixed_args[index];
|
||||
}
|
||||
return self.num_args == PhiArity ? self.phi_args[index].second : self.large_args[index];
|
||||
}
|
||||
|
||||
inline Value Inst::Arg(size_t index) const {
|
||||
return ArgSlot(*this, index);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -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;
|
||||
|
||||
+4
-1
@@ -65,6 +65,8 @@ else()
|
||||
SET(CMAKE_NINJA_FORCE_RESPONSE_FILE 1 CACHE INTERNAL "")
|
||||
endif()
|
||||
|
||||
# Guest code runs natively and needs x86-64-v3 (AVX2, BMI2, FMA), so every host has it.
|
||||
# No FMA contraction: a * b + c keeps both roundings.
|
||||
if(KYTY_CLANG_CL)
|
||||
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
|
||||
string(REGEX REPLACE "/W[0-4]" "/W3" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
@@ -79,7 +81,7 @@ if(KYTY_CLANG_CL)
|
||||
string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
|
||||
|
||||
set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} /utf-8 /Oy- /wd4244 /wd4305 /wd4800 /wd4345")
|
||||
set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} /utf-8 /Oy- /clang:-march=x86-64-v3 /clang:-ffp-contract=off /wd4244 /wd4305 /wd4800 /wd4345")
|
||||
|
||||
if(KYTY_WARNINGS_ARE_ERRORS)
|
||||
#set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} /WX")
|
||||
@@ -103,6 +105,7 @@ elseif(CLANG OR GCC)
|
||||
else()
|
||||
set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} -fno-exceptions -fdiagnostics-color=always -finput-charset=UTF-8 -fexec-charset=UTF-8 -static-libgcc -static-libstdc++ -g -fno-strict-aliasing -fno-omit-frame-pointer -Wall -Wno-unused-value -fmessage-length=0")
|
||||
endif()
|
||||
set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} -march=x86-64-v3 -ffp-contract=off")
|
||||
|
||||
if(KYTY_WARNINGS_ARE_ERRORS)
|
||||
#set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} -Werror")
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <cstdio>
|
||||
|
||||
#if !defined(__AVX2__) || !defined(__BMI2__) || !defined(__FMA__)
|
||||
#error "x86-64 builds target x86-64-v3"
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
// (1 + 2^-23)^2 = 1 + 2^-22 + 2^-46; only a fused multiply-add keeps the 2^-46.
|
||||
volatile float a = 1.0f + 0x1p-23f;
|
||||
const float result = a * a - (1.0f + 0x1p-22f);
|
||||
if (result != 0.0f) {
|
||||
std::fprintf(stderr, "BuildFlagsTests: failed: a * a - b was contracted (%a)\n", result);
|
||||
return 1;
|
||||
}
|
||||
std::puts("BuildFlagsTests: all cases passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -13,6 +15,11 @@ using Table = Libs::Graphics::MultiLevelPageTable<Owners, 20, 44, 14>;
|
||||
using PageOwners = Libs::Graphics::InlinePageOwnerList<uint32_t, 16>;
|
||||
using OwnerTable = Libs::Graphics::MultiLevelPageTable<PageOwners, 20, 44, 14>;
|
||||
|
||||
// MSVC's STL and libc++ don't need these traits for std::any_of, but libstdc++ does, so only a
|
||||
// Linux build would notice them missing.
|
||||
static_assert(std::is_same_v<std::iterator_traits<PageOwners::const_iterator>::iterator_category,
|
||||
std::input_iterator_tag>);
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "ImagePageTableTests: failed: %s\n", text);
|
||||
|
||||
@@ -353,6 +353,35 @@ void TestOperandMutationLifecycle() {
|
||||
"Phi replacement retained incoming uses or changed the consumer value");
|
||||
}
|
||||
|
||||
void TestReplacementKeepsUseOrder() {
|
||||
Fixture fixture(2);
|
||||
const auto base = fixture.Emit(ValueOpcode::IAdd32, {Value(1u), Value(2u)});
|
||||
auto *before = fixture.Emit(ValueOpcode::IAdd32, {base, Value(3u)}).TryInstruction();
|
||||
auto *identity = fixture.Emit(ValueOpcode::Identity, {base}).TryInstruction();
|
||||
auto *pair = fixture.Emit(ValueOpcode::IAdd32, {Value(identity), Value(identity)}).TryInstruction();
|
||||
auto *after = fixture.Emit(ValueOpcode::IAdd32, {Value(4u), base}).TryInstruction();
|
||||
auto &large = fixture.program.value_storage.emplace_back(ValueOpcode::GetImageResource);
|
||||
const auto last = large.NumArgs() - 1;
|
||||
large.SetArg(last, Value(identity));
|
||||
identity->ReplaceUsesWith(base, false);
|
||||
Check(!identity->HasUses() && identity->NumArgs() == 0 && pair->Arg(0) == base &&
|
||||
pair->Arg(1) == base && large.Arg(last) == base &&
|
||||
base.TryInstruction()->Uses() ==
|
||||
std::vector<Use>({{before, 0}, {after, 1}, {pair, 0}, {pair, 1}, {&large, last}}),
|
||||
"identity removal lost or reordered the uses of its argument");
|
||||
|
||||
auto &phi = fixture.BlockAt(1).AppendNewInst(ValueOpcode::Phi);
|
||||
phi.SetFlags(Type::U32);
|
||||
phi.AddPhiOperand(&fixture.BlockAt(0), Value(after));
|
||||
phi.AddPhiOperand(&fixture.BlockAt(1), Value(&phi));
|
||||
auto *user = fixture.Emit(ValueOpcode::IAdd32, {Value(&phi), Value(&phi)}, 0, 1).TryInstruction();
|
||||
phi.ReplaceUsesWith(Value(after));
|
||||
Check(phi.GetOpcode() == ValueOpcode::Identity && phi.Arg(0) == Value(after) && !phi.HasUses() &&
|
||||
user->Arg(0) == Value(after) && user->Arg(1) == Value(after) &&
|
||||
after->Uses() == std::vector<Use>({{user, 0}, {user, 1}, {&phi, 0}}),
|
||||
"self-referencing Phi replacement kept a use of itself or reordered the moved uses");
|
||||
}
|
||||
|
||||
void TestControlDependentStandaloneLoadStaysTyped() {
|
||||
Fixture fixture(3);
|
||||
const auto memory = fixture.AddMemory(ResourceKind::ScalarAddress);
|
||||
@@ -916,6 +945,7 @@ int main() {
|
||||
TestCarryAndBitFields();
|
||||
TestInvariantAndDivergentPhi();
|
||||
TestOperandMutationLifecycle();
|
||||
TestReplacementKeepsUseOrder();
|
||||
TestControlDependentStandaloneLoadStaysTyped();
|
||||
TestRuntime64BitDescriptorOps();
|
||||
TestUniformFirstLaneSamplerLod();
|
||||
|
||||
@@ -3942,6 +3942,62 @@ public:
|
||||
"write invalidation without a remaining copy did not establish CPU "
|
||||
"ownership");
|
||||
|
||||
// A RELEASE_MEM label beside GPU-written bytes lands without downloading
|
||||
// them; one over GPU-written bytes downloads them first, and one on a clean
|
||||
// cached page leaves it CPU-modified for the next upload.
|
||||
constexpr uint64_t label_gpu_address = fault_base + 0xf000;
|
||||
constexpr uint64_t label_beside_address = fault_base + 0xf100;
|
||||
constexpr uint64_t label_clean_address = memory_dst + 0x100;
|
||||
constexpr uint32_t label_gpu_value = 0x7e57ab1eu;
|
||||
constexpr uint64_t label_value = 0x0123456789abcdefull;
|
||||
gpu.SendCommandSync([&] {
|
||||
auto dirty = buffer_cache.ObtainBuffer(
|
||||
label_gpu_address, sizeof(label_gpu_value), true, false);
|
||||
Require("GpuCommandLane", "label page setup",
|
||||
dirty.first != nullptr &&
|
||||
buffer_cache.IsRegionRegistered(label_clean_address,
|
||||
sizeof(label_value)) &&
|
||||
!buffer_cache.IsRegionCpuModified(label_clean_address,
|
||||
sizeof(label_value)) &&
|
||||
!buffer_cache.IsRegionGpuModified(label_clean_address,
|
||||
sizeof(label_value)),
|
||||
"label test has no GPU-written page or clean cached page");
|
||||
buffer_cache.FillBuffer(label_gpu_address, sizeof(label_gpu_value),
|
||||
label_gpu_value, false);
|
||||
});
|
||||
const auto release_label = [&](uint64_t address) {
|
||||
const auto packet = make_interrupt_packet(
|
||||
2, 0, reinterpret_cast<void *>(address), label_value, 0);
|
||||
gpu.Submit(packet);
|
||||
gpu.WaitForIdle();
|
||||
uint64_t label = 0;
|
||||
return LibKernel::Memory::TryReadBacking(address, &label,
|
||||
sizeof(label)) &&
|
||||
label == label_value;
|
||||
};
|
||||
LibKernel::Memory::InstallGpuResources(&resources);
|
||||
const bool label_beside_stored =
|
||||
release_label(label_beside_address) &&
|
||||
buffer_cache.HasGpuDirtyBytes(label_gpu_address,
|
||||
sizeof(label_gpu_value));
|
||||
const bool label_over_stored =
|
||||
release_label(label_gpu_address) &&
|
||||
!buffer_cache.HasGpuDirtyBytes(label_gpu_address, sizeof(label_value));
|
||||
const bool label_clean_stored =
|
||||
release_label(label_clean_address) &&
|
||||
buffer_cache.IsRegionCpuModified(label_clean_address,
|
||||
sizeof(label_value));
|
||||
LibKernel::Memory::InstallGpuResources(nullptr);
|
||||
Require("GpuCommandLane", "RELEASE_MEM label beside GPU-written bytes",
|
||||
label_beside_stored,
|
||||
"a label downloaded the GPU-written bytes beside it");
|
||||
Require("GpuCommandLane", "RELEASE_MEM label over GPU-written bytes",
|
||||
label_over_stored,
|
||||
"a label over GPU-written bytes left them to a later download");
|
||||
Require("GpuCommandLane", "RELEASE_MEM label on a clean cached page",
|
||||
label_clean_stored,
|
||||
"a label left a clean cached copy of its page stale");
|
||||
|
||||
resources.UnmapMemory(fault_base, fault_size);
|
||||
Require("GpuCommandLane", "processor fault unmap",
|
||||
Libs::LibKernel::Memory::KernelMunmap(fault_base, fault_size) == 0,
|
||||
@@ -18770,6 +18826,10 @@ public:
|
||||
cmd.setViewportWithCount(1, &viewport);
|
||||
cmd.setScissorWithCount(1, &scissor);
|
||||
cmd.setLineWidth(1);
|
||||
cmd.setCullMode(vk::CullModeFlagBits::eNone);
|
||||
cmd.setFrontFace(registers.GetModeControl().face
|
||||
? vk::FrontFace::eClockwise
|
||||
: vk::FrontFace::eCounterClockwise);
|
||||
cmd.setDepthTestEnable(depth.depth_test_enable);
|
||||
cmd.setDepthWriteEnable(depth.depth_write_enable);
|
||||
cmd.setDepthCompareOp(depth.depth_compare_op);
|
||||
@@ -18967,6 +19027,9 @@ public:
|
||||
const std::array stencil_pipelines{
|
||||
pipeline(true, 2, 2, false, true).pipeline,
|
||||
pipeline(true, 2, 2, false, false).pipeline};
|
||||
Require(name, "dynamic winding pipeline reuse",
|
||||
stencil_pipelines[0] == stencil_pipelines[1],
|
||||
"the winding changed the graphics pipeline");
|
||||
const auto check_stencil = [&](const char *label, bool back_face,
|
||||
uint8_t initial, uint8_t written) {
|
||||
vk::ClearValue clear{};
|
||||
@@ -19599,6 +19662,22 @@ public:
|
||||
"native vertex or instance offset was applied twice");
|
||||
}
|
||||
}
|
||||
// With back faces culled, the triangle draws for exactly one winding.
|
||||
const auto draws_with_back_cull = [&](bool clockwise) {
|
||||
registers.SetModeControl({.cull_back = true, .face = clockwise});
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {});
|
||||
RenderExecutorTestAccess::DrawAuto(executor, scheduler.Current(),
|
||||
{.vertex_count = 3, .instance_count = 1, .first_vertex = 7, .first_instance = 5,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
const auto pixels = ReadCachedTexel(name, context, sparse_color.image_id,
|
||||
{}, {extent, extent, 1});
|
||||
return std::ranges::any_of(pixels, [](u32 word) { return word != 0; });
|
||||
};
|
||||
Require("DynamicCullMode", "back faces culled by winding",
|
||||
draws_with_back_cull(false) != draws_with_back_cull(true),
|
||||
"the draw's cull mode or front face did not reach the host GPU");
|
||||
registers.SetModeControl({});
|
||||
|
||||
// Indirect records come from the GPU copy; the CPU backing holds zeros. In each pair the
|
||||
// first record draws nothing and the second covers the target, through vertex offset 7
|
||||
@@ -19744,9 +19823,10 @@ public:
|
||||
registers.SetPsInputEna(2);
|
||||
registers.SetPsInputAddr(2);
|
||||
registers.SetPsInputSettings(0, 0);
|
||||
// Rect lists are drawn without culling.
|
||||
const auto check_rect = [&](Prospero::PrimitiveType primitive, const char *rect_name) {
|
||||
user_config.SetPrimitiveType(primitive);
|
||||
registers.SetModeControl({});
|
||||
registers.SetModeControl({.cull_front = true, .cull_back = true});
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {});
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
|
||||
+31
-11
@@ -6,12 +6,16 @@
|
||||
#include <xbyak/xbyak.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <ucontext.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <sys/ucontext.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
@@ -23,18 +27,20 @@ public:
|
||||
explicit FakeGuestMemory(Libs::Graphics::ShaderRecompiler::IR::SrtMemoryReader reader = nullptr,
|
||||
void *userdata = nullptr) {
|
||||
Current() = {reader, userdata};
|
||||
#if defined(__linux__)
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
struct sigaction action {};
|
||||
action.sa_sigaction = Handle;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGSEGV, &action, &m_previous);
|
||||
for (size_t index = 0; index < std::size(kSignals); ++index)
|
||||
sigaction(kSignals[index], &action, &m_previous[index]);
|
||||
#elif defined(_WIN32)
|
||||
m_handler = AddVectoredExceptionHandler(1, Handle);
|
||||
#endif
|
||||
}
|
||||
~FakeGuestMemory() {
|
||||
#if defined(__linux__)
|
||||
sigaction(SIGSEGV, &m_previous, nullptr);
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
for (size_t index = 0; index < std::size(kSignals); ++index)
|
||||
sigaction(kSignals[index], &m_previous[index], nullptr);
|
||||
#elif defined(_WIN32)
|
||||
RemoveVectoredExceptionHandler(m_handler);
|
||||
#endif
|
||||
@@ -54,16 +60,30 @@ private:
|
||||
const auto [reader, userdata] = Current();
|
||||
return reader != nullptr && reader(userdata, address, {&word, 1}) ? word : 0u;
|
||||
}
|
||||
#if defined(__linux__)
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#if defined(__APPLE__)
|
||||
// macOS raises SIGBUS for some protection faults and SIGSEGV for others.
|
||||
static constexpr int kSignals[] = {SIGSEGV, SIGBUS};
|
||||
#else
|
||||
static constexpr int kSignals[] = {SIGSEGV};
|
||||
#endif
|
||||
static void Handle(int, siginfo_t *, void *context) {
|
||||
auto ®isters = static_cast<ucontext_t *>(context)->uc_mcontext.gregs;
|
||||
const auto length =
|
||||
Libs::Graphics::ShaderRecompiler::IR::SrtWalkerLoadLength(registers[REG_RIP]);
|
||||
auto *native = static_cast<ucontext_t *>(context);
|
||||
#if defined(__APPLE__)
|
||||
auto &rip = native->uc_mcontext->__ss.__rip;
|
||||
auto &rax = native->uc_mcontext->__ss.__rax;
|
||||
const auto r10 = native->uc_mcontext->__ss.__r10;
|
||||
#else
|
||||
auto &rip = native->uc_mcontext.gregs[REG_RIP];
|
||||
auto &rax = native->uc_mcontext.gregs[REG_RAX];
|
||||
const auto r10 = native->uc_mcontext.gregs[REG_R10];
|
||||
#endif
|
||||
const auto length = Libs::Graphics::ShaderRecompiler::IR::SrtWalkerLoadLength(rip);
|
||||
if (length == 0) std::abort();
|
||||
registers[REG_RAX] = Read(registers[REG_R10]);
|
||||
registers[REG_RIP] += length;
|
||||
rax = Read(r10);
|
||||
rip += length;
|
||||
}
|
||||
struct sigaction m_previous {};
|
||||
struct sigaction m_previous[std::size(kSignals)] {};
|
||||
#elif defined(_WIN32)
|
||||
static LONG CALLBACK Handle(EXCEPTION_POINTERS *info) {
|
||||
auto &context = *info->ContextRecord;
|
||||
|
||||
Reference in new issue
Block a user