Files
KytyPS5/src/graphics/host_gpu/renderer/debug.cpp
T

1299 lines
55 KiB
C++

#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 "common/stringUtils.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <fmt/format.h>
namespace Libs::Graphics {
static std::atomic<uint32_t> g_scissor_default_log_count = 0;
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 RenderCommandBuffer& 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 RenderCommandBuffer& 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::OutputDirection::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_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",
uc.GetPrimType(), uc.GetIndexOffset(), uc.GetObjectId(), 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& ge_cntl = uc.GetGeControl();
const auto& user_en = uc.GetGeUserVgprEn();
if (ge_cntl.primitive_group_size > 0x0040) {
LOGF("\t warning: unsupported GE_CNTL primitive_group_size = 0x%04" PRIx16 "\n",
ge_cntl.primitive_group_size);
}
if (ge_cntl.vertex_group_size > 0x0040) {
LOGF("\t warning: unsupported GE_CNTL vertex_group_size = 0x%04" PRIx16 "\n",
ge_cntl.vertex_group_size);
}
EXIT_NOT_IMPLEMENTED(user_en.vgpr1 != false);
EXIT_NOT_IMPLEMENTED(user_en.vgpr2 != false);
EXIT_NOT_IMPLEMENTED(user_en.vgpr3 != false);
}
void sh_print(const char* func, const HW::Shader& /*uc*/) {
LOGF("%s\n", func);
}
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) {
std::vector<std::string> dst;
dst.reserve(53);
dst.push_back(fmt::format("{}\n", func));
dst.push_back(fmt::format("\t base.addr = 0x{:016x}\n", rt.base.addr));
dst.push_back(
fmt::format("\t pitch.pitch_div8_minus1 = 0x{:08x}\n", rt.pitch.pitch_div8_minus1));
dst.push_back(fmt::format("\t pitch.fmask_pitch_div8_minus1 = 0x{:08x}\n",
rt.pitch.fmask_pitch_div8_minus1));
dst.push_back(fmt::format("\t slice.slice_div64_minus1 = 0x{:08x}\n",
rt.slice.slice_div64_minus1));
dst.push_back(fmt::format("\t view.base_array_slice_index = 0x{:08x}\n",
rt.view.base_array_slice_index));
dst.push_back(fmt::format("\t view.last_array_slice_index = 0x{:08x}\n",
rt.view.last_array_slice_index));
dst.push_back(
fmt::format("\t view.current_mip_level = 0x{:08x}\n", rt.view.current_mip_level));
dst.push_back(fmt::format("\t info.fmask_compression_enable = {}\n",
rt.info.fmask_compression_enable ? "true" : "false"));
// dst.push_back(fmt::format("\t info.fmask_compression_mode = 0x{:08x}\n", //
// rt.info.fmask_compression_mode));
dst.push_back(fmt::format("\t info.fmask_data_compression_disable = {}\n",
rt.info.fmask_data_compression_disable ? "true" : "false"));
dst.push_back(fmt::format("\t info.fmask_one_frag_mode = {}\n",
rt.info.fmask_one_frag_mode ? "true" : "false"));
dst.push_back(fmt::format("\t info.cmask_fast_clear_enable = {}\n",
rt.info.cmask_fast_clear_enable ? "true" : "false"));
dst.push_back(fmt::format("\t info.dcc_compression_enable = {}\n",
rt.info.dcc_compression_enable ? "true" : "false"));
dst.push_back(
fmt::format("\t info.cmask_is_linear = 0x{:08x}\n", rt.info.cmask_is_linear));
dst.push_back(
fmt::format("\t info.cmask_addr_type = 0x{:08x}\n", rt.info.cmask_addr_type));
dst.push_back(fmt::format("\t info.alt_tile_mode = {}\n",
rt.info.alt_tile_mode ? "true" : "false"));
dst.push_back(fmt::format("\t info.format = 0x{:08x}\n", rt.info.format));
dst.push_back(
fmt::format("\t info.channel_type = 0x{:08x}\n", rt.info.channel_type));
dst.push_back(
fmt::format("\t info.channel_order = 0x{:08x}\n", rt.info.channel_order));
dst.push_back(fmt::format("\t info.blend_bypa = {}\n",
rt.info.blend_bypass ? "true" : "false"));
dst.push_back(fmt::format("\t info.blend_clamp = {}\n",
rt.info.blend_clamp ? "true" : "false"));
dst.push_back(fmt::format("\t info.round_mode = {}\n",
rt.info.round_mode ? "true" : "false"));
dst.push_back(fmt::format("\t attrib.force_dest_alpha_to_one = {}\n",
rt.attrib.force_dest_alpha_to_one ? "true" : "false"));
dst.push_back(
fmt::format("\t attrib.tile_mode = 0x{:08x}\n", rt.attrib.tile_mode));
dst.push_back(
fmt::format("\t attrib.fmask_tile_mode = 0x{:08x}\n", rt.attrib.fmask_tile_mode));
dst.push_back(
fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples));
dst.push_back(
fmt::format("\t attrib.num_fragments = 0x{:08x}\n", rt.attrib.num_fragments));
dst.push_back(fmt::format("\t attrib2.width = 0x{:08x}\n", rt.attrib2.width));
dst.push_back(
fmt::format("\t attrib2.height = 0x{:08x}\n", rt.attrib2.height));
dst.push_back(
fmt::format("\t attrib2.num_mip_levels = 0x{:08x}\n", rt.attrib2.num_mip_levels));
dst.push_back(fmt::format("\t attrib3.depth = 0x{:08x}\n", rt.attrib3.depth));
dst.push_back(
fmt::format("\t attrib3.tile_mode = 0x{:08x}\n", rt.attrib3.tile_mode));
dst.push_back(
fmt::format("\t attrib3.dimension = 0x{:08x}\n", rt.attrib3.dimension));
dst.push_back(fmt::format("\t attrib3.cmask_pipe_aligned = {}\n",
rt.attrib3.cmask_pipe_aligned ? "true" : "false"));
dst.push_back(fmt::format("\t attrib3.dcc_pipe_aligned = {}\n",
rt.attrib3.dcc_pipe_aligned ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.max_uncompressed_block_size = 0x{:08x}\n",
rt.dcc.max_uncompressed_block_size));
dst.push_back(fmt::format("\t dcc.max_compressed_block_size = 0x{:08x}\n",
rt.dcc.max_compressed_block_size));
dst.push_back(fmt::format("\t dcc.min_compressed_block_size = 0x{:08x}\n",
rt.dcc.min_compressed_block_size));
dst.push_back(
fmt::format("\t dcc.color_transform = 0x{:08x}\n", rt.dcc.color_transform));
dst.push_back(fmt::format("\t dcc.overwrite_combiner_disable = {}\n",
rt.dcc.overwrite_combiner_disable ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.independent_64b_blocks = {}\n",
rt.dcc.independent_64b_blocks ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.independent_128b_blocks = {}\n",
rt.dcc.independent_128b_blocks ? "true" : "false"));
dst.push_back(fmt::format("\t data_write_on_dcc_clear_to_reg = {}\n",
rt.dcc.data_write_on_dcc_clear_to_reg ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.dcc_clear_key_enable = {}\n",
rt.dcc.dcc_clear_key_enable ? "true" : "false"));
dst.push_back(fmt::format("\t cmask.addr = 0x{:016x}\n", rt.cmask.addr));
dst.push_back(fmt::format("\t cmask_slice.slice_minus1 = 0x{:08x}\n",
rt.cmask_slice.slice_minus1));
dst.push_back(fmt::format("\t fmask.addr = 0x{:016x}\n", rt.fmask.addr));
dst.push_back(fmt::format("\t fmask_slice.slice_minus1 = 0x{:08x}\n",
rt.fmask_slice.slice_minus1));
dst.push_back(
fmt::format("\t clear_word0.word0 = 0x{:08x}\n", rt.clear_word0.word0));
dst.push_back(
fmt::format("\t clear_word1.word1 = 0x{:08x}\n", rt.clear_word1.word1));
dst.push_back(
fmt::format("\t dcc_addr.addr = 0x{:016x}\n", rt.dcc_addr.addr));
dst.push_back(fmt::format("\t size.width = 0x{:08x}\n", rt.size.width));
dst.push_back(fmt::format("\t size.height = 0x{:08x}\n", rt.size.height));
return dst;
}
static bool RenderIsColorTileMode(uint32_t tile_mode) {
// AGC CxRenderTarget::TileMode shifted by CB_COLOR*_ATTRIB3.COLOR_SW_MODE.
switch (tile_mode) {
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard256B):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kPrt):
case Prospero::GpuEnumValue(Prospero::TileMode::kDepth):
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget): return true;
default: return false;
}
}
bool RenderIsColorTileModeLinear(uint32_t tile_mode) {
return tile_mode == Prospero::GpuEnumValue(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) {
// bool render_to_texture = (rt.attrib.tile_mode == 0x0d);
// EXIT_NOT_IMPLEMENTED(rt.base_addr == 0);
EXIT_NOT_IMPLEMENTED(rt.pitch.pitch_div8_minus1 != 0);
EXIT_NOT_IMPLEMENTED(rt.pitch.fmask_pitch_div8_minus1 != 0);
EXIT_NOT_IMPLEMENTED(rt.slice.slice_div64_minus1 != 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);
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 FMASK metadata for single-sample MSAA "
"fallback, fmask=0x%016" PRIx64 "\n",
rt.fmask.addr);
logged = true;
}
}
// EXIT_NOT_IMPLEMENTED(rt.info.fmask_compression_mode != 0x00000000);
EXIT_NOT_IMPLEMENTED(rt.info.fmask_data_compression_disable != false);
EXIT_NOT_IMPLEMENTED(rt.info.fmask_one_frag_mode != false);
if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 color metadata fast_clear=%s dcc=%s "
"cmask=0x%016" PRIx64 " dcc_addr=0x%016" PRIx64 "\n",
rt.info.cmask_fast_clear_enable ? "true" : "false",
rt.info.dcc_compression_enable ? "true" : "false", rt.cmask.addr,
rt.dcc_addr.addr);
logged = true;
}
}
if (rt.info.cmask_is_linear != 0x00000000 || rt.info.cmask_addr_type != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 CMASK metadata fields "
"is_linear=%u addr_type=%u\n",
rt.info.cmask_is_linear, rt.info.cmask_addr_type);
logged = true;
}
}
if (rt.info.alt_tile_mode) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring alternate tile mode flag\n");
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;
}
}
// EXIT_NOT_IMPLEMENTED(rt.tile_mode != 0x0000000a);
// EXIT_NOT_IMPLEMENTED(rt.fmask_tile_mode != 0x0000000a);
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 (rt.attrib3.depth != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 color target depth_minus1=0x%08" PRIx32
"\n",
rt.attrib3.depth);
logged = true;
}
}
if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) {
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", 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.dimension != 0x00000001) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: using 2D fallback for PS5 color "
"dimension=0x%08" PRIx32 "\n",
rt.attrib3.dimension);
logged = true;
}
}
if (!rt.attrib3.cmask_pipe_aligned) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: accepting unaligned PS5 CMASK pipe flag\n");
logged = true;
}
}
if (!rt.attrib3.dcc_pipe_aligned) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: accepting unaligned PS5 DCC pipe flag\n");
logged = true;
}
}
// 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.min_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.force_independent_blocks != false);
// EXIT_NOT_IMPLEMENTED(rt.dcc.independent_128b_blocks != false);
// EXIT_NOT_IMPLEMENTED(rt.dcc.data_write_on_dcc_clear_to_reg != false);
EXIT_NOT_IMPLEMENTED(rt.dcc.dcc_clear_key_enable != false);
if (rt.cmask.addr != 0x0000000000000000 || rt.cmask_slice.slice_minus1 != 0x00000000 ||
rt.fmask.addr != 0x0000000000000000 ||
(rt.fmask_slice.slice_minus1 != 0x00000000 &&
rt.fmask_slice.slice_minus1 != rt.slice.slice_div64_minus1) ||
rt.dcc_addr.addr != 0x0000000000000000) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 metadata addresses cmask=0x%016" PRIx64
" fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
rt.cmask.addr, rt.fmask.addr, rt.dcc_addr.addr);
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(rt.size.width != 0);
EXIT_NOT_IMPLEMENTED(rt.size.height != 0);
}
}
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_info.addr5_swizzle_mask = 0x%08" PRIx32 "\n"
"\t depth_info.array_mode = 0x%08" PRIx32 "\n"
"\t depth_info.pipe_config = 0x%08" PRIx32 "\n"
"\t depth_info.bank_width = 0x%08" PRIx32 "\n"
"\t depth_info.bank_height = 0x%08" PRIx32 "\n"
"\t depth_info.macro_tile_aspect = 0x%08" PRIx32 "\n"
"\t depth_info.num_banks = 0x%08" PRIx32 "\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 htile_surface.linear = 0x%08" PRIx32 "\n"
"\t htile_surface.full_cache = 0x%08" PRIx32 "\n"
"\t htile_surface.htile_uses_preload_win = 0x%08" PRIx32 "\n"
"\t htile_surface.preload = 0x%08" PRIx32 "\n"
"\t htile_surface.prefetch_width = 0x%08" PRIx32 "\n"
"\t htile_surface.prefetch_height = 0x%08" PRIx32 "\n"
"\t htile_surface.dst_outside_zero_to_one = 0x%08" PRIx32 "\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 pitch_div8_minus1 = 0x%08" PRIx32 "\n"
"\t height_div8_minus1 = 0x%08" PRIx32 "\n"
"\t slice_div64_minus1 = 0x%08" PRIx32 "\n"
"\t htile_data_base_addr = 0x%016" PRIx64 "\n"
"\t width = 0x%08" PRIx32 "\n"
"\t height = 0x%08" PRIx32 "\n"
"\t size.x_max = 0x%04" PRIx16 "\n"
"\t size.y_max = 0x%04" PRIx16 "\n",
z.z_info.format, z.z_info.num_samples,
Prospero::GpuEnumValue(z.z_info.texture_compatibility),
z.z_info.htile_acceleration ? "true" : "false",
z.z_info.expclear_enabled ? "true" : "false",
Prospero::GpuEnumValue(z.z_info.z_compare_base),
z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level,
z.stencil_info.format, Prospero::GpuEnumValue(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_info.addr5_swizzle_mask,
z.depth_info.array_mode, z.depth_info.pipe_config, z.depth_info.bank_width,
z.depth_info.bank_height, z.depth_info.macro_tile_aspect, z.depth_info.num_banks,
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.htile_surface.linear,
z.htile_surface.full_cache, z.htile_surface.htile_uses_preload_win,
z.htile_surface.preload, z.htile_surface.prefetch_width, z.htile_surface.prefetch_height,
z.htile_surface.dst_outside_zero_to_one, z.z_read_base_addr, z.stencil_read_base_addr,
z.z_write_base_addr, z.stencil_write_base_addr, z.pitch_div8_minus1, z.height_div8_minus1,
z.slice_div64_minus1, z.htile_data_base_addr, z.width, z.height, z.size.x_max,
z.size.y_max);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
const HW::RenderControl& rc) {
const bool depth_active =
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
if (!depth_active && !stencil_active) {
return;
}
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
if (z.z_info.format == 0) {
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0);
EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0);
EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != false);
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
} else {
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0x00000001 && z.z_info.format != 0x00000003);
if (z.z_info.num_samples != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: using native num_samples=0x%08" PRIx32 "\n",
z.z_info.num_samples);
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
}
if (z.stencil_info.format == 0) {
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
} else {
EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001);
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
}
if (z.z_info.format != 0 || z.stencil_info.format != 0) {
EXIT_NOT_IMPLEMENTED(z.depth_info.addr5_swizzle_mask != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.array_mode != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.pipe_config != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.bank_width != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.bank_height != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.macro_tile_aspect != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.num_banks != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.linear != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.full_cache != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.htile_uses_preload_win != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.preload != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_width != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_height != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.dst_outside_zero_to_one != 0x00000000);
if (z.depth_view.current_mip_level != 0x00000000) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("DepthTarget: temporary: ignoring PS5 current mip level=0x%08" PRIx32 "\n",
z.depth_view.current_mip_level);
}
}
if (z.depth_view.depth_write_disable || z.depth_view.stencil_write_disable) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("DepthTarget: honoring write disable depth=%s, stencil=%s\n",
z.depth_view.depth_write_disable ? "true" : "false",
z.depth_view.stencil_write_disable ? "true" : "false");
}
}
EXIT_NOT_IMPLEMENTED(z.z_read_base_addr != z.z_write_base_addr);
EXIT_NOT_IMPLEMENTED(z.stencil_read_base_addr != z.stencil_write_base_addr);
EXIT_NOT_IMPLEMENTED(z.z_write_base_addr == 0);
if (z.stencil_info.format != 0 && z.stencil_write_base_addr == 0) {
LOGF("\t warning: stencil format is set without a stencil base address, continuing "
"without stencil attachment\n");
}
// EXIT_NOT_IMPLEMENTED(z.pitch_div8_minus1 != 0x000000ff);
// EXIT_NOT_IMPLEMENTED(z.height_div8_minus1 != 0x0000008f);
// EXIT_NOT_IMPLEMENTED(z.slice_div64_minus1 != 0x00008fff);
// EXIT_NOT_IMPLEMENTED(z.htile_data_base_addr == 0);
// EXIT_NOT_IMPLEMENTED(z.width != 0x00000780);
// EXIT_NOT_IMPLEMENTED(z.height != 0x00000438);
EXIT_NOT_IMPLEMENTED(z.width != 0);
EXIT_NOT_IMPLEMENTED(z.height != 0);
if (z.size.x_max == 0 || z.size.y_max == 0) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) {
LOGF("DepthTarget: temporary: inferring PS5 default depth extent x_max=0x%04" PRIx16
", y_max=0x%04" PRIx16 "\n",
z.size.x_max, z.size.y_max);
}
}
}
}
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.IsZClipModeRepresentable() ||
c.user_clip_plane_negate_y || c.clip_disable ||
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 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.resummarize_enable ? "true" : "false", c.stencil_compress_disable ? "true" : "false",
c.depth_compress_disable ? "true" : "false", c.copy_centroid ? "true" : "false",
c.copy_sample);
}
static void RcCheck(const HW::RenderControl& c) {
// EXIT_NOT_IMPLEMENTED(c.depth_clear_enable != false);
// EXIT_NOT_IMPLEMENTED(c.stencil_clear_enable != false);
if ((c.resummarize_enable || c.copy_centroid || c.copy_sample != 0)) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 render-control metadata state, continuing: "
"resummarize=%s copy_centroid=%s copy_sample=%" PRIu8 "\n",
c.resummarize_enable ? "true" : "false", c.copy_centroid ? "true" : "false",
c.copy_sample);
}
return;
}
EXIT_NOT_IMPLEMENTED(c.resummarize_enable != false);
// EXIT_NOT_IMPLEMENTED(c.stencil_compress_disable != false);
// EXIT_NOT_IMPLEMENTED(c.depth_compress_disable != false);
EXIT_NOT_IMPLEMENTED(c.copy_centroid != false);
EXIT_NOT_IMPLEMENTED(c.copy_sample != 0);
}
static void McPrint(const char* func, const HW::ModeControl& c) {
LOGF("%s\n", func);
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.poly_mode != 0) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: PA_SU_SC_MODE_CNTL.POLY_MODE is not fully implemented; continuing "
"with filled polygons\n");
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(c.polymode_front_ptype != 0 && c.polymode_front_ptype != 2);
EXIT_NOT_IMPLEMENTED(c.polymode_back_ptype != 0 && c.polymode_back_ptype != 2);
if (c.poly_offset_front_enable || c.poly_offset_back_enable) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: PA_SU_SC_MODE_CNTL.POLY_OFFSET_*_ENABLE is not implemented; "
"continuing without depth bias\n");
logged = true;
}
}
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.provoking_vtx_last != false);
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::BlendControl& /*c*/, const HW::BlendColor& color,
const HW::ColorControl& cc) {
// EXIT_NOT_IMPLEMENTED(c.color_srcblend != 0);
// EXIT_NOT_IMPLEMENTED(c.color_comb_fcn != 0);
// EXIT_NOT_IMPLEMENTED(c.color_destblend != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_srcblend != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_comb_fcn != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_destblend != 0);
// EXIT_NOT_IMPLEMENTED(c.separate_alpha_blend != false);
// EXIT_NOT_IMPLEMENTED(c.enable != false);
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 color_writes_on_depth_fail_enable = %s\n"
"\t color_writes_on_depth_pass_disable = %s\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,
c.color_writes_on_depth_fail_enable ? "true" : "false",
c.color_writes_on_depth_pass_disable ? "true" : "false", 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 DCheck(const HW::DepthControl& c, const HW::StencilControl& s,
const HW::StencilMask& /*sm*/) {
// EXIT_NOT_IMPLEMENTED(c.stencil_enable != false);
// EXIT_NOT_IMPLEMENTED(c.z_enable != false);
// EXIT_NOT_IMPLEMENTED(c.z_write_enable != false);
// EXIT_NOT_IMPLEMENTED(c.zfunc != 0);
// Back-face stencil state is handled separately when enabled.
// EXIT_NOT_IMPLEMENTED(c.stencilfunc != 0);
// EXIT_NOT_IMPLEMENTED(c.stencilfunc_bf != 0);
EXIT_NOT_IMPLEMENTED(c.color_writes_on_depth_fail_enable != false);
EXIT_NOT_IMPLEMENTED(c.color_writes_on_depth_pass_disable != false);
// EXIT_NOT_IMPLEMENTED(s.stencil_fail != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_zpass != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_zfail != 0);
// Back-face stencil ops may legitimately differ from the front-face ops.
// EXIT_NOT_IMPLEMENTED(sm.stencil_testval != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_mask != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_writemask != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_opval != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_testval_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_mask_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_writemask_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_opval_bf != 0);
}
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) {
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<uint32_t> 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<uint32_t> 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<uint32_t> 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, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32) {
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: %s VS=0x%08" PRIx32 "/0x%08" PRIx32 " PS=0x%08" PRIx32 "/0x%08" PRIx32
"\n",
phase, vs_hash0, vs_crc32, ps_hash0, ps_crc32);
}
}
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<uint32_t> 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);
if (vp.viewports[0].zmin > 0.000000 || vp.viewports[0].zmax != 1.000000) {
static bool logged = false;
if (!logged) {
LOGF("\t warning: non-default viewport depth clamp zmin = %f, zmax = %f; using "
"viewport scale/offset for Vulkan depth range\n",
vp.viewports[0].zmin, vp.viewports[0].zmax);
logged = true;
}
}
// 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<uint32_t> 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 != 0.0f || vp.guard_band_vert_discard != 0.0f) {
static std::atomic<uint32_t> 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 bool ScissorRectSet(const ScissorRect& r) {
return r.left != 0 || r.top != 0 || r.right != 0 || r.bottom != 0;
}
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 {a.left > b.left ? a.left : b.left, a.top > b.top ? a.top : b.top,
a.right < b.right ? a.right : b.right, a.bottom < b.bottom ? a.bottom : b.bottom};
}
static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t height) {
int max_right = static_cast<int>(width);
int max_bottom = static_cast<int>(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 bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) {
EXIT_IF(mask == nullptr);
for (uint32_t candidate = 0; candidate < 16; candidate++) {
uint16_t expected = 0;
for (uint32_t combination = 0; combination < 16; combination++) {
if ((combination & candidate) == candidate) {
expected |= static_cast<uint16_t>(1u << combination);
}
}
if (expected == rule) {
*mask = static_cast<uint8_t>(candidate);
return true;
}
}
return false;
}
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent) {
ScissorRect screen {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
vp.screen_scissor_bottom};
ScissorRect final = screen;
if (!ScissorRectSet(screen)) {
final = {0, 0, static_cast<int>(extent.width), static_cast<int>(extent.height)};
auto log_id = g_scissor_default_log_count.fetch_add(1);
if (log_id < 32) {
LOGF("temporary: default unset screen scissor to framebuffer extent %ux%u\n",
extent.width, extent.height);
}
}
ScissorRect window {vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right,
vp.window_scissor_bottom};
if (ScissorRectSet(window)) {
if (vp.window_scissor_window_offset_enable) {
window = ScissorRectOffset(window, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, window);
}
ScissorRect generic {vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right,
vp.generic_scissor_bottom};
if (ScissorRectSet(generic)) {
if (vp.generic_scissor_window_offset_enable) {
generic = ScissorRectOffset(generic, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, generic);
}
const auto& viewport = vp.viewports[0];
ScissorRect viewport_scissor {viewport.viewport_scissor_left, viewport.viewport_scissor_top,
viewport.viewport_scissor_right,
viewport.viewport_scissor_bottom};
if (smc.vport_scissor_enable && ScissorRectSet(viewport_scissor)) {
if (viewport.viewport_scissor_window_offset_enable) {
viewport_scissor =
ScissorRectOffset(viewport_scissor, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, viewport_scissor);
}
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;
}
ScissorRect clip {vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i],
vp.clip_rect_bottom[i]};
if (vp.clip_rect_window_offset_enable[i]) {
clip = ScissorRectOffset(clip, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, clip);
}
} else {
static std::atomic<uint32_t> 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 RenderCommandBuffer& 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();
auto log_phase = [](const char* phase) {
if (graphics_debug_dump_enabled()) {
static std::atomic<uint32_t> 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("z");
ZCheck(z, d, rc);
log_phase("clip");
ClipCheck(c);
log_phase("rc");
RcCheck(rc);
log_phase("depth");
DCheck(d, s, sm);
log_phase("mode");
McCheck(mc);
log_phase("blend");
BcCheck(bc, bclr, cc);
log_phase("eqaa");
EqaaCheck(eqaa);
log_phase("aa");
AaCheck(aa, ac);
log_phase("done");
if (RenderTargetMaskHasBoundMrt(buffer)) {
LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask());
for (uint32_t i = 0; i < 8; i++) {
const auto& mrt = hw.GetRenderTarget(i);
LOGF("\t RT%u addr=0x%010" PRIx64 " mask=0x%x fmt=0x%08" PRIx32
" width=%u height=%u tile=%u\n",
i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu, mrt.info.format,
mrt.attrib2.width + 1, mrt.attrib2.height + 1, mrt.attrib3.tile_mode);
}
}
if (rc.depth_clear_enable && hw.GetDepthClearValue() != 0.0f &&
hw.GetDepthClearValue() != 1.0f) {
static std::atomic<uint32_t> 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 RenderCommandBuffer& 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",
hw.GetRenderTargetMask(), hw.GetDepthClearValue(), hw.GetStencilClearValue(),
hw.GetLineWidth());
LOGF("%s", Common::Concat(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