#include "graphics/host_gpu/renderer/debug.h" #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" #include "common/logging/log.h" #include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/host_gpu/renderer/render.h" #include #include #include #include #include namespace Libs::Graphics { uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot) { return (mask >> (slot * 4u)) & 0x0fu; } static bool RenderTargetMaskHasMrt(uint32_t mask) { return (mask & ~0x0fu) != 0; } static bool RenderTargetMaskHasBoundMrt(const CommandBuffer& buffer) { const auto& hw = buffer.GetRegisters(); const auto mask = hw.GetRenderTargetMask(); if (!RenderTargetMaskHasMrt(mask)) { return false; } uint32_t bound_targets = 0; for (uint32_t i = 0; i < 8; i++) { if (render_target_mask_slot(mask, i) != 0 && hw.GetRenderTarget(i).base.addr != 0) { bound_targets++; } } return bound_targets > 1; } uint32_t render_target_first_bound_slot(const CommandBuffer& buffer) { const auto& hw = buffer.GetRegisters(); const auto mask = hw.GetRenderTargetMask(); for (uint32_t i = 0; i < 8; i++) { if (render_target_mask_slot(mask, i) != 0 && hw.GetRenderTarget(i).base.addr != 0) { return i; } } return 0; } bool graphics_debug_dump_enabled() { return Config::GraphicsDebugDumpEnabled() && Config::GetPrintfDirection() != Config::LogDirection::Silent; } void uc_print(const char* func, const HW::UserConfig& uc) { LOGF("%s\n", func); const auto& ge_cntl = uc.GetGeControl(); const auto& user_en = uc.GetGeUserVgprEn(); LOGF("\t GetPrimType() = 0x%08" PRIx32 "\n" "\t GetIndexOffset() = 0x%08" PRIx32 "\n" "\t GetObjectId() = 0x%08" PRIx32 "\n" "\t primitive_reset = 0x%08" PRIx32 "\n" "\t primitive_group_size = 0x%04" PRIx16 "\n" "\t vertex_group_size = 0x%04" PRIx16 "\n" "\t en_user_vgpr1 = %s\n" "\t en_user_vgpr2 = %s\n" "\t en_user_vgpr3 = %s\n", static_cast(uc.GetPrimType()), uc.GetIndexOffset(), uc.GetObjectId(), uc.GetPrimitiveResetControl(), ge_cntl.primitive_group_size, ge_cntl.vertex_group_size, user_en.vgpr1 ? "true" : "false", user_en.vgpr2 ? "true" : "false", user_en.vgpr3 ? "true" : "false"); } void uc_check(const HW::UserConfig& uc) { const auto& user_en = uc.GetGeUserVgprEn(); EXIT_NOT_IMPLEMENTED(user_en.vgpr1 != false); EXIT_NOT_IMPLEMENTED(user_en.vgpr2 != false); EXIT_NOT_IMPLEMENTED(user_en.vgpr3 != false); } std::string rt_print(const char* func, const HW::RenderTarget& rt) { std::string dst; dst.reserve(4096); dst += fmt::format("{}\n", func); dst += fmt::format("\t base.addr = 0x{:016x}\n", rt.base.addr); dst += fmt::format("\t view.base_array_slice_index = 0x{:08x}\n", rt.view.base_array_slice_index); dst += fmt::format("\t view.last_array_slice_index = 0x{:08x}\n", rt.view.last_array_slice_index); dst += fmt::format("\t view.current_mip_level = 0x{:08x}\n", rt.view.current_mip_level); dst += fmt::format("\t info.fmask_compression_enable = {}\n", rt.info.fmask_compression_enable ? "true" : "false"); dst += fmt::format("\t info.fmask_data_compression_disable = {}\n", rt.info.fmask_data_compression_disable ? "true" : "false"); dst += fmt::format("\t info.fmask_one_frag_mode = {}\n", rt.info.fmask_one_frag_mode ? "true" : "false"); dst += fmt::format("\t info.cmask_fast_clear_enable = {}\n", rt.info.cmask_fast_clear_enable ? "true" : "false"); dst += fmt::format("\t info.dcc_compression_enable = {}\n", rt.info.dcc_compression_enable ? "true" : "false"); dst += fmt::format("\t info.format = 0x{:08x}\n", static_cast(rt.info.format)); dst += fmt::format("\t info.channel_type = 0x{:08x}\n", static_cast(rt.info.channel_type)); dst += fmt::format("\t info.channel_order = 0x{:08x}\n", static_cast(rt.info.channel_order)); dst += fmt::format("\t info.blend_bypa = {}\n", rt.info.blend_bypass ? "true" : "false"); dst += fmt::format("\t info.blend_clamp = {}\n", rt.info.blend_clamp ? "true" : "false"); dst += fmt::format("\t info.round_mode = {}\n", rt.info.round_mode ? "true" : "false"); dst += fmt::format("\t attrib.force_dest_alpha_to_one = {}\n", rt.attrib.force_dest_alpha_to_one ? "true" : "false"); dst += fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples); dst += fmt::format("\t attrib.num_fragments = 0x{:08x}\n", rt.attrib.num_fragments); dst += fmt::format("\t attrib2.width = 0x{:08x}\n", rt.attrib2.width); dst += fmt::format("\t attrib2.height = 0x{:08x}\n", rt.attrib2.height); dst += fmt::format("\t attrib2.num_mip_levels = 0x{:08x}\n", rt.attrib2.num_mip_levels); dst += fmt::format("\t attrib3.depth = 0x{:08x}\n", rt.attrib3.depth); dst += fmt::format("\t attrib3.tile_mode = 0x{:08x}\n", static_cast(rt.attrib3.tile_mode)); dst += fmt::format("\t attrib3.dimension = 0x{:08x}\n", rt.attrib3.dimension); dst += fmt::format("\t attrib3.metadata_pipe_aligned = {}\n", rt.attrib3.metadata_pipe_aligned ? "true" : "false"); dst += fmt::format("\t attrib3.write_vrs_rate_hint_to_cmask = {}\n", rt.attrib3.write_vrs_rate_hint_to_cmask ? "true" : "false"); dst += fmt::format("\t dcc.max_uncompressed_block_size = 0x{:08x}\n", rt.dcc.max_uncompressed_block_size); dst += fmt::format("\t dcc.max_compressed_block_size = 0x{:08x}\n", rt.dcc.max_compressed_block_size); dst += fmt::format("\t dcc.color_transform = 0x{:08x}\n", rt.dcc.color_transform); dst += fmt::format("\t dcc.overwrite_combiner_disable = {}\n", rt.dcc.overwrite_combiner_disable ? "true" : "false"); dst += fmt::format("\t dcc.independent_block_size = 0x{:02x}\n", static_cast(rt.dcc.independent_block_size)); dst += fmt::format("\t data_write_on_dcc_clear_to_reg = {}\n", rt.dcc.data_write_on_dcc_clear_to_reg ? "true" : "false"); dst += fmt::format("\t dcc.dcc_clear_key_enable = {}\n", rt.dcc.dcc_clear_key_enable ? "true" : "false"); dst += fmt::format("\t cmask.addr = 0x{:016x}\n", rt.cmask.addr); dst += fmt::format("\t fmask.addr = 0x{:016x}\n", rt.fmask.addr); dst += fmt::format("\t clear_word0.word0 = 0x{:08x}\n", rt.clear_word0.word0); dst += fmt::format("\t clear_word1.word1 = 0x{:08x}\n", rt.clear_word1.word1); dst += fmt::format("\t dcc_addr.addr = 0x{:016x}\n", rt.dcc_addr.addr); return dst; } static bool RenderIsColorTileMode(Prospero::TileMode tile_mode) { // AGC CxRenderTarget::TileMode shifted by CB_COLOR*_ATTRIB3.COLOR_SW_MODE. switch (tile_mode) { case Prospero::TileMode::kLinear: case Prospero::TileMode::kStandard256B: case Prospero::TileMode::kStandard4KB: case Prospero::TileMode::kStandard64KB: case Prospero::TileMode::kPrt: case Prospero::TileMode::kDepth: case Prospero::TileMode::kRenderTarget: return true; default: return false; } } bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode) { return tile_mode == Prospero::TileMode::kLinear; } static bool RenderIsColorDimension(uint32_t dimension) { return dimension <= 0x02; } // NOLINTNEXTLINE(readability-function-cognitive-complexity) static void RtCheck(const HW::RenderTarget& rt) { if (rt.base.addr != 0) { // EXIT_NOT_IMPLEMENTED(rt.base_addr == 0); EXIT_NOT_IMPLEMENTED(rt.view.base_array_slice_index > rt.view.last_array_slice_index); if (rt.view.base_array_slice_index != 0x00000000 || rt.view.last_array_slice_index != 0x00000000) { static bool logged = false; if (!logged) { LOGF("RenderTarget: using color target array slice range %" PRIu32 "..%" PRIu32 "\n", rt.view.base_array_slice_index, rt.view.last_array_slice_index); logged = true; } } if (rt.view.current_mip_level != 0x00000000) { static bool logged = false; if (!logged) { LOGF("RenderTarget: using PS5 color target mip level %" PRIu32 "\n", rt.view.current_mip_level); logged = true; } } if (rt.info.fmask_compression_enable) { EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0); // Native MSAA stores expanded samples, independent of FMASK metadata compression. static bool logged = false; if (!logged) { LOGF("RenderTarget: using expanded native Vulkan MSAA samples, " "fmask=0x%016" PRIx64 "\n", rt.fmask.addr); logged = true; } } if (rt.info.blend_bypass) { static bool logged = false; if (!logged) { LOGF("RenderTarget: temporary: using PS5 blend bypass as disabled Vulkan " "blending\n"); logged = true; } } // EXIT_NOT_IMPLEMENTED(rt.info.blend_clamp != false); if (rt.info.round_mode) { static bool logged = false; if (!logged) { LOGF("RenderTarget: temporary: ignoring PS5 color round mode\n"); logged = true; } } // EXIT_NOT_IMPLEMENTED(rt.format != 0x0000000a); // EXIT_NOT_IMPLEMENTED(rt.channel_type != 0x00000006); // EXIT_NOT_IMPLEMENTED(rt.channel_order != 0x00000001); if (rt.attrib.force_dest_alpha_to_one) { static bool logged = false; if (!logged) { LOGF("RenderTarget: temporary: accepting PS5 force destination alpha-to-one\n"); logged = true; } } if (rt.attrib.num_samples != 0x00000000 || rt.attrib.num_fragments != 0x00000000) { static bool logged = false; if (!logged) { LOGF("RenderTarget: using native PS5 MSAA color target, " "samples=0x%08" PRIx32 " fragments=0x%08" PRIx32 "\n", rt.attrib.num_samples, rt.attrib.num_fragments); logged = true; } } if (rt.attrib2.width == 0x00000000 || rt.attrib2.height == 0x00000000) { static bool logged = false; if (!logged) { LOGF("RenderTarget: temporary: accepting PS5 raw 1-pixel color target extent " "fields width_minus1=0x%08" PRIx32 " height_minus1=0x%08" PRIx32 "\n", rt.attrib2.width, rt.attrib2.height); logged = true; } } if (rt.attrib2.num_mip_levels != 0x00000000) { static bool logged = false; if (!logged) { LOGF("RenderTarget: using PS5 color target mip count field 0x%08" PRIx32 "\n", rt.attrib2.num_mip_levels); logged = true; } } if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) { EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", static_cast(rt.attrib3.tile_mode)); } if (!RenderIsColorDimension(rt.attrib3.dimension)) { EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension); } if (!rt.attrib3.metadata_pipe_aligned) { static bool logged = false; if (!logged) { LOGF("RenderTarget: temporary: accepting unaligned PS5 metadata pipe flag\n"); logged = true; } } EXIT_NOT_IMPLEMENTED(rt.attrib3.write_vrs_rate_hint_to_cmask); // EXIT_NOT_IMPLEMENTED(rt.dcc_max_uncompressed_block_size != 0x00000002); // EXIT_NOT_IMPLEMENTED(rt.dcc.max_compressed_block_size != 0x00000000); // EXIT_NOT_IMPLEMENTED(rt.dcc.color_transform != 0x00000000); EXIT_NOT_IMPLEMENTED(rt.dcc.overwrite_combiner_disable != false); // EXIT_NOT_IMPLEMENTED(rt.dcc.data_write_on_dcc_clear_to_reg != false); EXIT_NOT_IMPLEMENTED(rt.dcc.dcc_clear_key_enable != false); } } static void ZPrint(const char* func, const HW::DepthRenderTarget& z) { LOGF("%s\n", func); LOGF("\t z_info.format = 0x%08" PRIx32 "\n" "\t z_info.num_samples = 0x%08" PRIx32 "\n" "\t z_info.texture_compatibility = 0x%08" PRIx32 "\n" "\t z_info.htile_acceleration = %s\n" "\t z_info.expclear_enabled = %s\n" "\t z_info.z_compare_base = 0x%08" PRIx32 "\n" "\t z_info.partially_resident = %s\n" "\t z_info.max_mip_level = 0x%02" PRIx8 "\n" "\t stencil_info.format = 0x%08" PRIx32 "\n" "\t stencil_info.texture_compatibility = 0x%08" PRIx32 "\n" "\t stencil_info.htile_stencil_disabled = %s\n" "\t stencil_info.expclear_enabled = %s\n" "\t stencil_info.partially_resident = %s\n" "\t depth_view.slice_start = 0x%08" PRIx32 "\n" "\t depth_view.slice_max = 0x%08" PRIx32 "\n" "\t depth_view.current_mip_level = 0x%02" PRIx8 "\n" "\t depth_view.depth_write_disable = %s\n" "\t depth_view.stencil_write_disable = %s\n" "\t z_read_base_addr = 0x%016" PRIx64 "\n" "\t stencil_read_base_addr = 0x%016" PRIx64 "\n" "\t z_write_base_addr = 0x%016" PRIx64 "\n" "\t stencil_write_base_addr = 0x%016" PRIx64 "\n" "\t htile_data_base_addr = 0x%016" PRIx64 "\n" "\t shading_rate_encoding = 0x%02" PRIx8 "\n" "\t size.x_max = 0x%04" PRIx16 "\n" "\t size.y_max = 0x%04" PRIx16 "\n" "\t size.valid = %s\n", static_cast(z.z_info.format), z.z_info.num_samples, static_cast(z.z_info.texture_compatibility), z.z_info.htile_acceleration ? "true" : "false", z.z_info.expclear_enabled ? "true" : "false", static_cast(z.z_info.z_compare_base), z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level, static_cast(z.stencil_info.format), static_cast(z.stencil_info.texture_compatibility), z.stencil_info.htile_stencil_disabled ? "true" : "false", z.stencil_info.expclear_enabled ? "true" : "false", z.stencil_info.partially_resident ? "true" : "false", z.depth_view.slice_start, z.depth_view.slice_max, z.depth_view.current_mip_level, z.depth_view.depth_write_disable ? "true" : "false", z.depth_view.stencil_write_disable ? "true" : "false", z.z_read_base_addr, z.stencil_read_base_addr, z.z_write_base_addr, z.stencil_write_base_addr, z.htile_data_base_addr, z.shading_rate_encoding, z.size.x_max, z.size.y_max, z.size.valid ? "true" : "false"); } static void ClipPrint(const char* func, const HW::ClipControl& c) { LOGF("%s\n", func); LOGF("\t user_clip_planes = 0x%02" PRIx8 "\n" "\t user_clip_plane_mode = 0x%02" PRIx8 "\n" "\t dx_clip_space = %s\n" "\t vertex_kill_any = %s\n" "\t min_z_clip_disable = %s\n" "\t max_z_clip_disable = %s\n" "\t user_clip_plane_negate_y = %s\n" "\t clip_disable = %s\n" "\t user_clip_plane_cull_only = %s\n" "\t cull_on_clipping_error_disable = %s\n" "\t linear_attribute_clip_enable = %s\n" "\t force_viewport_index_from_vs_enable = %s\n", c.user_clip_planes, c.user_clip_plane_mode, c.dx_clip_space ? "true" : "false", c.vertex_kill_any ? "true" : "false", c.min_z_clip_disable ? "true" : "false", c.max_z_clip_disable ? "true" : "false", c.user_clip_plane_negate_y ? "true" : "false", c.clip_disable ? "true" : "false", c.user_clip_plane_cull_only ? "true" : "false", c.cull_on_clipping_error_disable ? "true" : "false", c.linear_attribute_clip_enable ? "true" : "false", c.force_viewport_index_from_vs_enable ? "true" : "false"); } static void ClipCheck(const HW::ClipControl& c) { // dx_linear_attr_clip_enable preserves linear (noperspective) attributes at clip-generated // vertices, which Vulkan provides as part of clipping and interpolation. EXIT_NOT_IMPLEMENTED(c.user_clip_planes != 0 || c.user_clip_plane_mode != 0 || c.vertex_kill_any || c.user_clip_plane_negate_y || c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable || c.force_viewport_index_from_vs_enable); } static void RcPrint(const char* func, const HW::RenderControl& c) { LOGF("%s\n", func); LOGF("\t depth_clear_enable = %s\n" "\t stencil_clear_enable = %s\n" "\t copy_depth_to_color = %s\n" "\t copy_stencil_to_color = %s\n" "\t resummarize_enable = %s\n" "\t stencil_compress_disable = %s\n" "\t depth_compress_disable = %s\n" "\t copy_centroid = %s\n" "\t copy_sample = %" PRIu8 "\n", c.depth_clear_enable ? "true" : "false", c.stencil_clear_enable ? "true" : "false", c.copy_depth_to_color ? "true" : "false", c.copy_stencil_to_color ? "true" : "false", c.resummarize_enable ? "true" : "false", c.stencil_compress_disable ? "true" : "false", c.depth_compress_disable ? "true" : "false", c.copy_centroid ? "true" : "false", c.copy_sample); } static void McPrint(const char* func, const HW::ModeControl& c) { LOGF("%s\n", func); LOGF("\t cull_front = %s\n" "\t cull_back = %s\n" "\t face = %s\n" "\t poly_mode = %" PRIu8 "\n" "\t polymode_front_ptype = %" PRIu8 "\n" "\t polymode_back_ptype = %" PRIu8 "\n" "\t poly_offset_front_enable = %s\n" "\t poly_offset_back_enable = %s\n" "\t vtx_window_offset_enable = %s\n" "\t provoking_vtx_last = %s\n" "\t persp_corr_dis = %s\n", c.cull_front ? "true" : "false", c.cull_back ? "true" : "false", c.face ? "true" : "false", c.poly_mode, c.polymode_front_ptype, c.polymode_back_ptype, c.poly_offset_front_enable ? "true" : "false", c.poly_offset_back_enable ? "true" : "false", c.vtx_window_offset_enable ? "true" : "false", c.provoking_vtx_last ? "true" : "false", c.persp_corr_dis ? "true" : "false"); } static void McCheck(const HW::ModeControl& c) { // EXIT_NOT_IMPLEMENTED(c.cull_front != false); // EXIT_NOT_IMPLEMENTED(c.cull_back != false); // EXIT_NOT_IMPLEMENTED(c.face != false); if (c.vtx_window_offset_enable) { static bool logged = false; if (!logged) { LOGF("\t temporary: PA_SU_SC_MODE_CNTL.VTX_WINDOW_OFFSET_ENABLE is not fully " "implemented; continuing without vertex window " "offset adjustment\n"); logged = true; } } EXIT_NOT_IMPLEMENTED(c.persp_corr_dis != false); } static void BcPrint(const char* func, const HW::BlendControl& c, const HW::BlendColor& color, const HW::ColorControl& cc) { LOGF("%s\n", func); LOGF("\t color_srcblend = %" PRIu8 "\n" "\t color_comb_fcn = %" PRIu8 "\n" "\t color_destblend = %" PRIu8 "\n" "\t alpha_srcblend = %" PRIu8 "\n" "\t alpha_comb_fcn = %" PRIu8 "\n" "\t alpha_destblend = %" PRIu8 "\n" "\t separate_alpha_blend = %s\n" "\t enable = %s\n" "\t red = %f\n" "\t green = %f\n" "\t blue = %f\n" "\t alpha = %f\n" "\t cc.mode = %" PRIu8 "\n" "\t cc.op = %" PRIu8 "\n", c.color_srcblend, c.color_comb_fcn, c.color_destblend, c.alpha_srcblend, c.alpha_comb_fcn, c.alpha_destblend, c.separate_alpha_blend ? "true" : "false", c.enable ? "true" : "false", color.red, color.green, color.blue, color.alpha, cc.mode, cc.op); } static void BcCheck(const HW::BlendColor& color, const HW::ColorControl& cc) { if (color.red != 0.0f || color.green != 0.0f || color.blue != 0.0f || color.alpha != 0.0f) { static bool logged = false; if (!logged) { LOGF("BlendControl: temporary: accepting nonzero blend constants (%f, %f, %f, %f)\n", color.red, color.green, color.blue, color.alpha); logged = true; } } if (cc.mode != 1 && cc.mode != 0 && cc.mode != 2 && cc.mode != 3 && cc.mode != 5 && cc.mode != 6) { static bool logged = false; if (!logged) { LOGF("BlendControl: temporary: accepting unsupported color-control mode %" PRIu8 "\n", cc.mode); logged = true; } } if (cc.op != 0xCC) { static bool logged = false; if (!logged) { LOGF("BlendControl: temporary: accepting unsupported raster op 0x%02" PRIx8 "\n", cc.op); logged = true; } } } static void DPrint(const char* func, const HW::DepthControl& c, const HW::StencilControl& s, const HW::StencilMask& sm) { LOGF("%s\n", func); LOGF("\t stencil_enable = %s\n" "\t z_enable = %s\n" "\t z_write_enable = %s\n" "\t depth_bounds_enable = %s\n" "\t zfunc = %" PRIu8 "\n" "\t backface_enable = %s\n" "\t stencilfunc = %" PRIu8 "\n" "\t stencilfunc_bf = %" PRIu8 "\n" "\t stencil_fail = %" PRIu8 "\n" "\t stencil_zpass = %" PRIu8 "\n" "\t stencil_zfail = %" PRIu8 "\n" "\t stencil_fail_bf = %" PRIu8 "\n" "\t stencil_zpass_bf = %" PRIu8 "\n" "\t stencil_zfail_bf = %" PRIu8 "\n" "\t stencil_testval = %" PRIu8 "\n" "\t stencil_mask = %" PRIu8 "\n" "\t stencil_writemask = %" PRIu8 "\n" "\t stencil_opval = %" PRIu8 "\n" "\t stencil_testval_bf = %" PRIu8 "\n" "\t stencil_mask_bf = %" PRIu8 "\n" "\t stencil_writemask_bf = %" PRIu8 "\n" "\t stencil_opval_bf = %" PRIu8 "\n", c.stencil_enable ? "true" : "false", c.z_enable ? "true" : "false", c.z_write_enable ? "true" : "false", c.depth_bounds_enable ? "true" : "false", c.zfunc, c.backface_enable ? "true" : "false", c.stencilfunc, c.stencilfunc_bf, s.stencil_fail, s.stencil_zpass, s.stencil_zfail, s.stencil_fail_bf, s.stencil_zpass_bf, s.stencil_zfail_bf, sm.stencil_testval, sm.stencil_mask, sm.stencil_writemask, sm.stencil_opval, sm.stencil_testval_bf, sm.stencil_mask_bf, sm.stencil_writemask_bf, sm.stencil_opval_bf); } static void EqaaPrint(const char* func, const HW::EqaaControl& c) { LOGF("%s\n", func); LOGF("\t max_anchor_samples = %" PRIu8 "\n" "\t ps_iter_samples = %" PRIu8 "\n" "\t mask_export_num_samples = %" PRIu8 "\n" "\t alpha_to_mask_num_samples = %" PRIu8 "\n" "\t high_quality_intersections = %s\n" "\t incoherent_eqaa_reads = %s\n" "\t interpolate_comp_z = %s\n" "\t static_anchor_associations = %s\n", c.max_anchor_samples, c.ps_iter_samples, c.mask_export_num_samples, c.alpha_to_mask_num_samples, c.high_quality_intersections ? "true" : "false", c.incoherent_eqaa_reads ? "true" : "false", c.interpolate_comp_z ? "true" : "false", c.static_anchor_associations ? "true" : "false"); } static void EqaaCheck(const HW::EqaaControl& c, const HW::AaConfig& cf) { // The EQAA controls only take effect while multisampling is on, so a single-sample // target leaves them inert and has nothing to warn about. if (cf.msaa_num_samples == 0) { return; } if (c.max_anchor_samples != 0 || c.ps_iter_samples != 0 || c.mask_export_num_samples != 0 || c.alpha_to_mask_num_samples != 0 || c.high_quality_intersections || c.incoherent_eqaa_reads || c.interpolate_comp_z || c.static_anchor_associations) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 16) { LOGF("\t warning: unsupported PS5 EQAA controls use native Vulkan MSAA defaults\n"); } } } static void AaPrint(const char* func, const HW::AaSampleControl& c, const HW::AaConfig& cf) { LOGF("%s\n", func); LOGF("\t centroid_priority = %016" PRIx64 "\n", c.centroid_priority); for (int i = 0; i < 16; i++) { LOGF("\t locations[%d] = %08" PRIx32 "\n", i, c.locations[i]); } LOGF("\t msaa_num_samples = %" PRIu8 "\n" "\t aa_mask_centroid_dtmn = %s\n" "\t max_sample_dist = %" PRIu8 "\n" "\t msaa_exposed_samples = %" PRIu8 "\n", cf.msaa_num_samples, cf.aa_mask_centroid_dtmn ? "true" : "false", cf.max_sample_dist, cf.msaa_exposed_samples); } static void AaCheck(const HW::AaSampleControl& c, const HW::AaConfig& cf) { bool non_default_locations = (c.centroid_priority != 0); for (uint32_t l: c.locations) { non_default_locations |= (l != 0); } if (non_default_locations || cf.msaa_num_samples != 0 || cf.aa_mask_centroid_dtmn || cf.max_sample_dist != 0 || cf.msaa_exposed_samples != 0) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 16) { LOGF("\t warning: unsupported PS5 sample locations use native Vulkan locations: " "samples=%" PRIu8 ", exposed=%" PRIu8 ", max_dist=%" PRIu8 "\n", cf.msaa_num_samples, cf.msaa_exposed_samples, cf.max_sample_dist); } } } void LogDrawPhase(const char* draw_name, const char* phase) { if (graphics_debug_dump_enabled()) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 1024) { LOGF("DrawPhase: %s %s\n", draw_name, phase); } } } void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id) { if (graphics_debug_dump_enabled()) { LOGF("PipelineTrace: %s VS=%" PRIu64 " PS=%" PRIu64 "\n", phase, vertex_program_id, pixel_program_id); } } static void VpPrint(const char* func, const HW::ScreenViewport& vp, const HW::ScanModeControl& smc) { LOGF("%s\n", func); LOGF("\t msaa_enable = %s\n" "\t vport_scissor_enable = %s\n" "\t line_stipple_enable = %s\n" "\t vp[0].zmin = %f\n" "\t vp[0].zmax = %f\n" "\t vp[0].xscale = %f\n" "\t vp[0].xoffset = %f\n" "\t vp[0].yscale = %f\n" "\t vp[0].yoffset = %f\n" "\t vp[0].zscale = %f\n" "\t vp[0].zoffset = %f\n" "\t vp[0].viewport_scissor_left = %d\n" "\t vp[0].viewport_scissor_top = %d\n" "\t vp[0].viewport_scissor_right = %d\n" "\t vp[0].viewport_scissor_bottom = %d\n" "\t transform_control = 0x%08" PRIx32 "\n" "\t screen_scissor_left = %d\n" "\t screen_scissor_top = %d\n" "\t screen_scissor_right = %d\n" "\t screen_scissor_bottom = %d\n" "\t window_scissor_left = %d\n" "\t window_scissor_top = %d\n" "\t window_scissor_right = %d\n" "\t window_scissor_bottom = %d\n" "\t generic_scissor_left = %d\n" "\t generic_scissor_top = %d\n" "\t generic_scissor_right = %d\n" "\t generic_scissor_bottom = %d\n" "\t window_offset_x = %d\n" "\t window_offset_y = %d\n" "\t hw_offset_x = %u\n" "\t hw_offset_y = %u\n" "\t guard_band_horz_clip = %f\n" "\t guard_band_vert_clip = %f\n" "\t guard_band_horz_discard = %f\n" "\t guard_band_vert_discard = %f\n" "\t clip_rect_rule = 0x%04" PRIx16 "\n" "\t clip_rect_0 = (%d,%d)-(%d,%d), window_offset = %s\n" "\t window_scissor_window_offset_enable = %s\n" "\t generic_scissor_window_offset_enable = %s\n", smc.msaa_enable ? "true" : "false", smc.vport_scissor_enable ? "true" : "false", smc.line_stipple_enable ? "true" : "false", vp.viewports[0].zmin, vp.viewports[0].zmax, vp.viewports[0].xscale, vp.viewports[0].xoffset, vp.viewports[0].yscale, vp.viewports[0].yoffset, vp.viewports[0].zscale, vp.viewports[0].zoffset, vp.viewports[0].viewport_scissor_left, vp.viewports[0].viewport_scissor_top, vp.viewports[0].viewport_scissor_right, vp.viewports[0].viewport_scissor_bottom, vp.transform_control, vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right, vp.screen_scissor_bottom, vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right, vp.window_scissor_bottom, vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right, vp.generic_scissor_bottom, vp.window_offset_x, vp.window_offset_y, vp.hw_offset_x, vp.hw_offset_y, vp.guard_band_horz_clip, vp.guard_band_vert_clip, vp.guard_band_horz_discard, vp.guard_band_vert_discard, vp.clip_rect_rule, vp.clip_rect_left[0], vp.clip_rect_top[0], vp.clip_rect_right[0], vp.clip_rect_bottom[0], vp.clip_rect_window_offset_enable[0] ? "true" : "false", vp.window_scissor_window_offset_enable ? "true" : "false", vp.generic_scissor_window_offset_enable ? "true" : "false"); LOGF("\t viewports[0].viewport_scissor_window_offset_enable = %s\n", vp.viewports[0].viewport_scissor_window_offset_enable ? "true" : "false"); } static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc) { if (smc.msaa_enable) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 16) { LOGF("\t warning: unsupported PS5 MSAA raster controls use native Vulkan defaults\n"); } } // EXIT_NOT_IMPLEMENTED(smc.vport_scissor_enable); EXIT_NOT_IMPLEMENTED(smc.line_stipple_enable); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].xscale != 960.000000); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].xoffset != 960.000000); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].yscale != -540.000000); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].yoffset != 540.000000); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].zscale != 0.500000); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].zoffset != 0.500000); if (vp.transform_control != 1087) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) { LOGF("\t warning: non-default viewport transform control = 0x%08" PRIx32 ", applying enabled scale/offset bits\n", vp.transform_control); } } // EXIT_NOT_IMPLEMENTED(vp.hw_offset_x != 60); // EXIT_NOT_IMPLEMENTED(vp.hw_offset_y != 32); // EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_horz_clip - 33.133327f) > 0.001f); // EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_vert_clip - 59.629623f) > 0.001f); if (vp.guard_band_horz_discard != 1.0f || vp.guard_band_vert_discard != 1.0f) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 16) { LOGF("\t warning: unsupported PS5 guard band discard = %f, %f, continuing\n", vp.guard_band_horz_discard, vp.guard_band_vert_discard); } } // EXIT_NOT_IMPLEMENTED(vp.viewports[0].viewport_scissor_left != 0); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].viewport_scissor_top != 0); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].viewport_scissor_right != 0); // EXIT_NOT_IMPLEMENTED(vp.viewports[0].viewport_scissor_bottom != 0); // EXIT_NOT_IMPLEMENTED(viewport_scissor && // vp.viewports[0].viewport_scissor_window_offset_enable != true); } static bool ScissorRectValid(const ScissorRect& r) { return r.right > r.left && r.bottom > r.top; } static ScissorRect ScissorRectOffset(ScissorRect r, int x, int y) { r.left += x; r.right += x; r.top += y; r.bottom += y; return r; } static ScissorRect ScissorRectIntersect(const ScissorRect& a, const ScissorRect& b) { return {std::max(a.left, b.left), std::max(a.top, b.top), std::min(a.right, b.right), std::min(a.bottom, b.bottom)}; } static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t height) { int max_right = static_cast(width); int max_bottom = static_cast(height); r.left = std::clamp(r.left, 0, max_right); r.right = std::clamp(r.right, 0, max_right); r.top = std::clamp(r.top, 0, max_bottom); r.bottom = std::clamp(r.bottom, 0, max_bottom); if (!ScissorRectValid(r)) { r.right = r.left; r.bottom = r.top; } return r; } static constexpr std::array MakeScissorIntersectionRules() { std::array rules {}; for (uint32_t candidate = 0; candidate < rules.size(); candidate++) { for (uint32_t combination = 0; combination < 16; combination++) { if ((combination & candidate) == candidate) { rules[candidate] |= static_cast(1u << combination); } } } return rules; } static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) { EXIT_IF(mask == nullptr); static constexpr auto rules = MakeScissorIntersectionRules(); for (uint32_t candidate = 0; candidate < rules.size(); candidate++) { if (rules[candidate] == rule) { *mask = static_cast(candidate); return true; } } return false; } ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc, vk::Extent2D extent, uint32_t viewport_index) { EXIT_IF(viewport_index >= std::size(vp.viewports)); ScissorRect final {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right, vp.screen_scissor_bottom}; const auto intersect = [&](ScissorRect rect, bool window_offset) { if (window_offset) { rect = ScissorRectOffset(rect, vp.window_offset_x, vp.window_offset_y); } final = ScissorRectIntersect(final, rect); }; intersect({vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right, vp.window_scissor_bottom}, vp.window_scissor_window_offset_enable); intersect({vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right, vp.generic_scissor_bottom}, vp.generic_scissor_window_offset_enable); const auto& viewport = vp.viewports[viewport_index]; if (smc.vport_scissor_enable) { intersect({viewport.viewport_scissor_left, viewport.viewport_scissor_top, viewport.viewport_scissor_right, viewport.viewport_scissor_bottom}, viewport.viewport_scissor_window_offset_enable); } if (vp.clip_rect_rule == 0) { final = {0, 0, 0, 0}; } else if (vp.clip_rect_rule != 0xffffu) { uint8_t clip_rect_mask = 0; if (ScissorClipRuleToIntersectionMask(vp.clip_rect_rule, &clip_rect_mask)) { for (uint32_t i = 0; i < 4; i++) { if ((clip_rect_mask & (1u << i)) == 0) { continue; } intersect({vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i], vp.clip_rect_bottom[i]}, vp.clip_rect_window_offset_enable[i]); } } else { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("unsupported clip-rect rule 0x%04" PRIx16 ", leaving scissor unchanged\n", vp.clip_rect_rule); } } } return ScissorRectClamp(final, extent.width, extent.height); } void hw_check(const CommandBuffer& buffer) { const auto& hw = buffer.GetRegisters(); const auto rt_slot = render_target_first_bound_slot(buffer); const auto& rt = hw.GetRenderTarget(rt_slot); const auto& bclr = hw.GetBlendColor(); const auto& vp = hw.GetScreenViewport(); const auto& c = hw.GetClipControl(); const auto& rc = hw.GetRenderControl(); const auto& mc = hw.GetModeControl(); const auto& eqaa = hw.GetEqaaControl(); const auto& cc = hw.GetColorControl(); const auto& smc = hw.GetScanModeControl(); const auto& aa = hw.GetAaSampleControl(); const auto& ac = hw.GetAaConfig(); auto log_phase = [](const char* phase) { if (graphics_debug_dump_enabled()) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 512) { LOGF("HwCheckPhase: %s\n", phase); } } }; log_phase("rt"); RtCheck(rt); log_phase("vp"); VpCheck(vp, smc); log_phase("clip"); ClipCheck(c); log_phase("mode"); McCheck(mc); log_phase("blend"); BcCheck(bclr, cc); log_phase("eqaa"); EqaaCheck(eqaa, ac); log_phase("aa"); AaCheck(aa, ac); log_phase("done"); if (graphics_debug_dump_enabled() && RenderTargetMaskHasBoundMrt(buffer)) { const auto& sh = hw.GetShaderRegisters(); LOGF("MRT render target mask: 0x%08" PRIx32 " export_modes={%u,%u,%u,%u,%u,%u,%u,%u}\n", hw.GetRenderTargetMask(), sh.target_output_mode[0], sh.target_output_mode[1], sh.target_output_mode[2], sh.target_output_mode[3], sh.target_output_mode[4], sh.target_output_mode[5], sh.target_output_mode[6], sh.target_output_mode[7]); for (uint32_t i = 0; i < 8; i++) { const auto& mrt = hw.GetRenderTarget(i); const auto& bc = hw.GetBlendControl(i); LOGF("\t RT%u addr=0x%010" PRIx64 " mask=0x%x fmt=0x%08" PRIx32 " type=%u order=%u shader_mask=0x%x" " width=%u height=%u tile=%u blend=%u bypass=%u clamp=%u round_mode=%u" " samples=%u fragments=%u dcc=%u dcc_addr=0x%010" PRIx64 " cmask_clear=%u cmask_addr=0x%010" PRIx64 " dcc_clear_key=%u clear={%08" PRIx32 ",%08" PRIx32 "}\n", i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu, static_cast(mrt.info.format), static_cast(mrt.info.channel_type), static_cast(mrt.info.channel_order), (sh.m_cbShaderMask >> (i * 4u)) & 0x0fu, mrt.attrib2.width + 1, mrt.attrib2.height + 1, static_cast(mrt.attrib3.tile_mode), static_cast(bc.enable), static_cast(mrt.info.blend_bypass), static_cast(mrt.info.blend_clamp), static_cast(mrt.info.round_mode), mrt.attrib.num_samples, mrt.attrib.num_fragments, static_cast(mrt.info.dcc_compression_enable), mrt.dcc_addr.addr, static_cast(mrt.info.cmask_fast_clear_enable), mrt.cmask.addr, static_cast(mrt.dcc.dcc_clear_key_enable), mrt.clear_word0.word0, mrt.clear_word1.word1); } } if (rc.depth_clear_enable && hw.GetDepthClearValue() != 0.0f && hw.GetDepthClearValue() != 1.0f) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 16) { LOGF("\t temporary: accepting non-default depth clear value %f\n", hw.GetDepthClearValue()); } } // EXIT_NOT_IMPLEMENTED(hw.GetStencilClearValue() != 0); } void hw_print(const CommandBuffer& buffer) { const auto& hw = buffer.GetRegisters(); const auto rt_slot = render_target_first_bound_slot(buffer); const auto& rt = hw.GetRenderTarget(rt_slot); const auto& bc = hw.GetBlendControl(rt_slot); const auto& bclr = hw.GetBlendColor(); const auto& vp = hw.GetScreenViewport(); const auto& z = hw.GetDepthRenderTarget(); const auto& c = hw.GetClipControl(); const auto& rc = hw.GetRenderControl(); const auto& d = hw.GetDepthControl(); const auto& s = hw.GetStencilControl(); const auto& sm = hw.GetStencilMask(); const auto& mc = hw.GetModeControl(); const auto& eqaa = hw.GetEqaaControl(); const auto& cc = hw.GetColorControl(); const auto& smc = hw.GetScanModeControl(); const auto& aa = hw.GetAaSampleControl(); const auto& ac = hw.GetAaConfig(); if (graphics_debug_dump_enabled()) { LOGF("Context\n" "\t GetRenderTargetMask() = 0x%08" PRIx32 "\n" "\t GetDepthClearValue() = %f\n" "\t GetStencilClearValue() = %" PRIu8 "\n" "\t GetLineWidth() = %f\n" "\t primitive_reset_index = 0x%08" PRIx32 "\n", hw.GetRenderTargetMask(), hw.GetDepthClearValue(), hw.GetStencilClearValue(), hw.GetLineWidth(), hw.GetPrimitiveResetIndex()); LOGF("%s", rt_print("RenderTraget:", rt).c_str()); ZPrint("DepthRenderTraget:", z); VpPrint("ScreenViewport:", vp, smc); ClipPrint("ClipControl:", c); RcPrint("RenderControl:", rc); DPrint("DepthStencilControlMask:", d, s, sm); McPrint("ModeControl:", mc); BcPrint("BlendColorControl:", bc, bclr, cc); EqaaPrint("EqaaControl:", eqaa); AaPrint("AaSampleControl:", aa, ac); } } } // namespace Libs::Graphics