Support Standard64KB color render targets

This commit is contained in:
nmzik 2026-07-17 12:57:40 +02:00
parent 1416131a0a
commit a290a06ea5
8 changed files with 359 additions and 29 deletions

View File

@ -1476,6 +1476,67 @@ void TileConvertTiledToLinearStandard64KB32(void* dst, const void* src, uint32_t
}
}
void TileConvertLinearToTiledStandard64KB32(void* dst, const void* src, uint32_t width,
uint32_t height, uint32_t pitch, uint64_t size) {
KYTY_PROFILER_FUNCTION();
const auto rows = static_cast<uint64_t>(height == 0 ? 0 : height - 1u);
const auto expected_pitch = (static_cast<uint64_t>(width) + 127u) & ~uint64_t {127u};
if (pitch != expected_pitch || size < (rows * pitch + width) * sizeof(uint32_t)) {
EXIT("invalid linear-to-Standard64KB32 conversion, dst=%p src=%p extent=%ux%u "
"pitch=%u size=0x%016" PRIx64 "\n",
dst, src, width, height, pitch, size);
}
auto* dst32 = static_cast<uint32_t*>(dst);
const auto* src32 = static_cast<const uint32_t*>(src);
const auto& tables = GetStandard64KB32Tables();
const auto blocks_per_row = (static_cast<uint64_t>(pitch) + 127u) >> 7u;
const auto block_rows = (static_cast<uint64_t>(height) + 127u) >> 7u;
if (blocks_per_row > UINT64_MAX / block_rows / 65536u ||
size != blocks_per_row * block_rows * 65536u) {
EXIT("invalid Standard64KB32 allocation, extent=%ux%u pitch=%u size=0x%016" PRIx64
" blocks=%" PRIu64 "x%" PRIu64 "\n",
width, height, pitch, size, blocks_per_row, block_rows);
}
const auto* x_words = tables.x_words.data();
const auto* y_words = tables.y_words.data();
std::memset(dst32, 0, static_cast<size_t>(size));
for (uint32_t block_y = 0; block_y < height; block_y += 128u) {
const uint32_t block_height = std::min(128u, height - block_y);
for (uint32_t block_x = 0; block_x < width; block_x += 128u) {
const uint32_t block_width = std::min(128u, width - block_x);
const uint64_t block_base =
(((static_cast<uint64_t>(block_y) >> 7u) * blocks_per_row) + (block_x >> 7u))
<< 14u;
if (block_width == 128u && block_height == 128u) {
for (uint32_t local_y = 0; local_y < 128u; local_y++) {
const uint64_t src_row =
(static_cast<uint64_t>(block_y + local_y) * pitch) + block_x;
const uint64_t dst_row = block_base + y_words[local_y];
for (uint32_t local_x = 0; local_x < 128u; local_x += 4u) {
dst32[dst_row + x_words[local_x + 0u]] = src32[src_row + local_x + 0u];
dst32[dst_row + x_words[local_x + 1u]] = src32[src_row + local_x + 1u];
dst32[dst_row + x_words[local_x + 2u]] = src32[src_row + local_x + 2u];
dst32[dst_row + x_words[local_x + 3u]] = src32[src_row + local_x + 3u];
}
}
continue;
}
for (uint32_t local_y = 0; local_y < block_height; local_y++) {
const uint64_t src_row =
(static_cast<uint64_t>(block_y + local_y) * pitch) + block_x;
const uint64_t dst_row = block_base + y_words[local_y];
for (uint32_t local_x = 0; local_x < block_width; local_x++) {
dst32[dst_row + x_words[local_x]] = src32[src_row + local_x];
}
}
}
}
}
void TileConvertTiledToLinearStandard64KB16(void* dst, const void* src, uint32_t width,
uint32_t height, uint32_t pitch, uint64_t size,
uint64_t src_size, uint32_t src_x, uint32_t src_y) {

View File

@ -53,6 +53,8 @@ void TileConvertTiledToLinearStandard64KB32(void* dst, const void* src, uint32_t
uint32_t height, uint32_t pitch, uint64_t size,
uint64_t src_size = 0, uint32_t src_x = 0,
uint32_t src_y = 0);
void TileConvertLinearToTiledStandard64KB32(void* dst, const void* src, uint32_t width,
uint32_t height, uint32_t pitch, uint64_t size);
void TileConvertTiledToLinearStandard64KB16(void* dst, const void* src, uint32_t width,
uint32_t height, uint32_t pitch, uint64_t size,
uint64_t src_size = 0, uint32_t src_x = 0,

View File

@ -373,9 +373,12 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
const bool standard64 =
rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
switch (rt.attrib3.tile_mode) {
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget):
tile = !RenderIsColorTileModeLinear(rt.attrib3.tile_mode);
break;
@ -391,10 +394,32 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
if (bytes_per_element == 0) {
EXIT("render-target format has no valid element size\n");
}
if (standard64 &&
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 || levels != 1 ||
rt.view.current_mip_level != 0 ||
view.base_layer != 0 || view.image_layers != 1 || rt.attrib.num_samples != 0 ||
rt.attrib.num_fragments != 0 || bytes_per_element != 4 ||
rt.pitch.pitch_div8_minus1 != 0 || (rt.base.addr & 0xffffu) != 0 ||
rt.info.fmask_compression_enable || rt.info.fmask_data_compression_disable ||
rt.info.fmask_one_frag_mode || rt.info.cmask_fast_clear_enable ||
rt.info.dcc_compression_enable || rt.info.cmask_is_linear != 0 ||
rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode || rt.cmask.addr != 0 ||
rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 || rt.dcc.data_write_on_dcc_clear_to_reg)) {
EXIT("unsupported Standard64KB render target: addr=0x%016" PRIx64
" dimension=%u depth=%u levels=%u layer=%u/%u samples=%u fragments=%u bpe=%u"
" cmask=0x%016" PRIx64 " fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
rt.base.addr, rt.attrib3.dimension, rt.attrib3.depth, levels, view.base_layer,
view.image_layers,
rt.attrib.num_samples, rt.attrib.num_fragments, bytes_per_element, rt.cmask.addr,
rt.fmask.addr, rt.dcc_addr.addr);
}
if (rt.pitch.pitch_div8_minus1 != 0) {
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
} else if (tile) {
pitch = TileGetRenderTargetPitch(width, bytes_per_element);
pitch = standard64
? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, levels, rt.attrib3.tile_mode)
: TileGetRenderTargetPitch(width, bytes_per_element);
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
@ -404,10 +429,18 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
if (tile) {
TileSizeAlign layout {};
const bool valid_layout =
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element, &layout)
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
levels, &layout, nullptr, nullptr);
bool valid_layout = false;
if (standard64) {
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
height, pitch, levels, rt.attrib3.tile_mode, &layout, nullptr, nullptr);
valid_layout = layout.size != 0 && layout.align == 65536;
} else {
valid_layout =
levels == 1
? TileGetRenderTargetSize(width, height, pitch, bytes_per_element, &layout)
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element, levels,
&layout, nullptr, nullptr);
}
if (!valid_layout) {
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
height, pitch, bytes_per_element, levels);
@ -430,7 +463,14 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
EXIT("render-target backing range is invalid\n");
}
auto video_image = Presentation::DisplayBufferFind(rt.base.addr, true);
auto video_image = Presentation::DisplayBufferFind(rt.base.addr, true);
if (video_image.image != nullptr &&
!IsSupportedDisplayRenderTargetTileMode(rt.attrib3.tile_mode)) {
EXIT("unsupported display render-target tile mode: tile=%u expected=%u addr=0x%010" PRIx64
" backing_size=0x%016" PRIx64 " video_size=0x%016" PRIx64 "\n",
rt.attrib3.tile_mode, Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget),
rt.base.addr, backing_size, video_image.size);
}
bool render_to_texture = view.base_layer != 0 || video_image.image == nullptr;
if (!render_to_texture && (levels != 1 || rt.view.current_mip_level != 0)) {
EXIT("mipmapped display render targets are unsupported\n");

View File

@ -369,15 +369,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) {
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", rt.attrib3.tile_mode);
}
if (rt.attrib3.tile_mode != 0x0000001b) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: using generic PS5 color tile fallback for "
"tile_mode=0x%08" PRIx32 "\n",
rt.attrib3.tile_mode);
logged = true;
}
}
if (!RenderIsColorDimension(rt.attrib3.dimension)) {
EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension);
}

View File

@ -360,6 +360,30 @@ SelectStorageSampledViewShape(uint32_t type, uint32_t depth, uint32_t backing_la
}
}
[[nodiscard]] inline constexpr bool IsSupportedDisplayRenderTargetTileMode(
uint32_t tile_mode) noexcept {
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
}
[[nodiscard]] inline constexpr bool
IsSupportedStandard64RenderTarget(const RenderTargetInfo& info) noexcept {
if (info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) ||
info.address == 0 || (info.address & 0xffffu) != 0 || info.width == 0 || info.height == 0 ||
info.bytes_per_element != 4 || info.levels != 1 || info.layers != 1) {
return false;
}
const auto expected_pitch = (static_cast<uint64_t>(info.width) + 127u) & ~uint64_t {127u};
const auto padded_height = (static_cast<uint64_t>(info.height) + 127u) & ~uint64_t {127u};
return expected_pitch <= UINT32_MAX && info.pitch == expected_pitch &&
expected_pitch <= UINT64_MAX / padded_height / info.bytes_per_element &&
info.size == expected_pitch * padded_height * info.bytes_per_element;
}
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const RenderTargetInfo& info) noexcept {
return info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
IsSupportedStandard64RenderTarget(info);
}
[[nodiscard]] inline constexpr DepthTransitionSource
SelectDepthTransitionSource(bool depth_load_clear, bool sampled_native_available,
bool sampled_cpu_dirty, bool sampled_buffer_modified,

View File

@ -415,9 +415,8 @@ struct TextureCache::ReadbackWorker {
cached.info, Prospero::SurfaceFormat(cached.info.format),
Prospero::NumBytesPerElement(cached.info.format))
: MakeColorImageTransferInfo(cached.target);
const bool linear = info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
const bool tiled =
target && info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
const bool linear = info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
const bool tiled = target && IsTiledRenderTarget(cached.target);
bool single_layer_storage = false;
if (storage) {
switch (static_cast<Prospero::ImageType>(cached.info.type)) {
@ -975,14 +974,16 @@ void UploadRenderTargetLayers(GraphicContext* ctx, RenderTextureVulkanImage* ima
}
const auto slice_size = info.size / info.layers;
const auto upload_size = slice_size * layer_count;
if (info.levels > 1 || info.layers > 1) {
const bool standard64 = IsSupportedStandard64RenderTarget(info);
if (standard64 || info.levels > 1 || info.layers > 1) {
const auto format = RenderTargetTransferFormat(info.bytes_per_element);
auto layout = TextureCalcUploadLayout(format, info.width, info.height, info.levels,
layer_count, info.pitch, info.tile_mode, upload_size,
false, false, false, "TextureCache render target");
const bool tiled =
const bool render_target_tiled =
info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
if ((tiled && !layout.fmt_tiled_render_target) || layout.pitch != info.pitch) {
if ((render_target_tiled && !layout.fmt_tiled_render_target) ||
(standard64 && !layout.fmt_tiled_standard64kb) || layout.pitch != info.pitch) {
EXIT("TextureCache: unsupported render-target mip upload layout, pitch=%u/%u tile=%u\n",
info.pitch, layout.pitch, info.tile_mode);
}
@ -2074,13 +2075,15 @@ StorageTextureVulkanImage* TextureCache::FindStorageTexture(CommandBuffer* com
RenderTextureVulkanImage* TextureCache::FindRenderTarget(CommandBuffer* command,
GraphicContext* ctx,
const RenderTargetInfo& info) {
const bool standard64 = IsSupportedStandard64RenderTarget(info);
if (command == nullptr || ctx == nullptr || info.address == 0 || info.size == 0 ||
info.address >= TRACKER_ADDRESS_SIZE || info.size > TRACKER_ADDRESS_SIZE - info.address ||
info.format == VK_FORMAT_UNDEFINED || info.width == 0 || info.height == 0 ||
info.pitch < info.width || info.bytes_per_element == 0 || info.levels == 0 ||
info.levels > 16 || info.layers == 0 || info.size % info.layers != 0 ||
(info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear) &&
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget))) {
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) &&
!standard64)) {
EXIT("TextureCache: invalid render-target request, ctx=%p addr=0x%016" PRIx64
" size=0x%016" PRIx64 " extent=%ux%u pitch=%u bpe=%u tile=%u format=%d\n",
static_cast<const void*>(ctx), info.address, info.size, info.width, info.height,
@ -2100,6 +2103,19 @@ RenderTextureVulkanImage* TextureCache::FindRenderTarget(CommandBuffer*
info.bytes_per_element, info.levels, info.tile_mode);
}
}
if (standard64) {
const auto format = RenderTargetTransferFormat(info.bytes_per_element);
const auto expected_pitch =
TileGetTexturePitch(format, info.width, info.levels, info.tile_mode);
TileSizeAlign layout {};
TileGetTextureSize(format, info.width, info.height, expected_pitch, info.levels,
info.tile_mode, &layout, nullptr, nullptr);
if (expected_pitch != info.pitch || layout.align != 65536 || layout.size != info.size) {
EXIT("TextureCache: invalid Standard64KB render-target layout,"
" size=0x%016" PRIx64 " expected=0x%08x align=0x%08x pitch=%u/%u\n",
info.size, layout.size, layout.align, info.pitch, expected_pitch);
}
}
const auto rows = static_cast<uint64_t>(info.height - 1);
if (rows > (UINT64_MAX - info.width) / info.pitch) {
EXIT("TextureCache: render-target element count overflow, extent=%ux%u pitch=%u\n",
@ -3085,12 +3101,21 @@ void TextureCache::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
void TextureCache::SynchronizeRenderTargetToBufferLocked(CachedImage& cached) {
const auto& target = cached.target;
const bool linear = target.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
const bool tiled =
target.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
const bool tiled = IsTiledRenderTarget(target);
const auto rows = static_cast<uint64_t>(target.height - 1);
TileSizeAlign exact {};
const bool single_slice = TileGetRenderTargetSize(target.width, target.height, target.pitch,
target.bytes_per_element, &exact);
bool single_slice = false;
if (IsSupportedStandard64RenderTarget(target)) {
const auto format = RenderTargetTransferFormat(target.bytes_per_element);
const auto expected_pitch =
TileGetTexturePitch(format, target.width, target.levels, target.tile_mode);
TileGetTextureSize(format, target.width, target.height, expected_pitch, target.levels,
target.tile_mode, &exact, nullptr, nullptr);
single_slice = target.pitch == expected_pitch && exact.align == 65536 && exact.size != 0;
} else {
single_slice = TileGetRenderTargetSize(target.width, target.height, target.pitch,
target.bytes_per_element, &exact);
}
const bool layered_size = target.layers != 0 && single_slice &&
exact.size <= UINT64_MAX / target.layers &&
exact.size * target.layers == target.size;

View File

@ -214,7 +214,7 @@ void Tiler::DetileStencil(GraphicContext* ctx, DepthStencilVulkanImage* image,
void Tiler::TileImage(void* dst, const void* src, const RenderTargetInfo& info) const {
if (dst == nullptr || src == nullptr || info.address == 0 || info.size == 0 ||
info.width == 0 || info.height == 0 || info.pitch < info.width ||
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
!IsTiledRenderTarget(info) ||
info.levels != 1 || info.layers == 0 || info.size % info.layers != 0 ||
!IsSupportedRenderTargetElementSize(info.bytes_per_element)) {
EXIT("Tiler: unsupported render-target tile, dst=%p src=%p "
@ -227,8 +227,13 @@ void Tiler::TileImage(void* dst, const void* src, const RenderTargetInfo& info)
for (uint32_t layer = 0; layer < info.layers; layer++) {
auto* guest_slice = static_cast<uint8_t*>(dst) + slice_size * layer;
auto* linear_slice = static_cast<const uint8_t*>(src) + slice_size * layer;
TileConvertLinearToTiledRenderTarget(guest_slice, linear_slice, info.width, info.height,
info.pitch, info.bytes_per_element, slice_size);
if (IsSupportedStandard64RenderTarget(info)) {
TileConvertLinearToTiledStandard64KB32(guest_slice, linear_slice, info.width,
info.height, info.pitch, slice_size);
} else {
TileConvertLinearToTiledRenderTarget(guest_slice, linear_slice, info.width, info.height,
info.pitch, info.bytes_per_element, slice_size);
}
}
}

View File

@ -9649,6 +9649,180 @@ void CheckColorResolveLayers() {
std::printf("[host] %-32s ok\n", "ColorResolveLayers");
}
[[noreturn]] void RunStandard64RenderTargetDeathCase() {
RenderTargetInfo info{};
info.address = 0x10000;
info.size = 0x10000;
info.format = VK_FORMAT_R16G16B16A16_SFLOAT;
info.width = 128;
info.height = 64;
info.pitch = 128;
info.bytes_per_element = 8;
info.tile_mode =
Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
std::vector<uint8_t> linear(info.size, 0);
std::vector<uint8_t> tiled(info.size, 0);
Tiler tiler;
tiler.TileImage(tiled.data(), linear.data(), info);
std::_Exit(0x7f);
}
void CheckStandard64RenderTargetTileRoundTrip() {
constexpr uint32_t format =
Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
constexpr uint32_t tile =
Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
constexpr uint32_t observed_width = 3840;
constexpr uint32_t observed_height = 2160;
const auto observed_pitch =
TileGetTexturePitch(format, observed_width, 1, tile);
TileSizeAlign observed{};
TileGetTextureSize(format, observed_width, observed_height, observed_pitch,
1, tile, &observed, nullptr, nullptr);
Require("Standard64RenderTarget", "observed layout",
observed_pitch == 3840 && observed.size == 0x1fe0000 &&
observed.align == 0x10000,
"PPSA02721 Standard64KB render-target footprint changed");
constexpr uint32_t width = 257;
constexpr uint32_t height = 131;
const auto pitch = TileGetTexturePitch(format, width, 1, tile);
TileSizeAlign storage{};
TileGetTextureSize(format, width, height, pitch, 1, tile, &storage,
nullptr, nullptr);
Require("Standard64RenderTarget", "partial layout",
pitch == 384 && storage.size == 0x60000 &&
storage.align == 0x10000,
"partial Standard64KB footprint was not padded in 128x128 blocks");
RenderTargetInfo info{};
info.address = 0x10000;
info.size = storage.size;
info.format = VK_FORMAT_R8G8B8A8_UNORM;
info.width = width;
info.height = height;
info.pitch = pitch;
info.bytes_per_element = 4;
info.tile_mode = tile;
Require("Standard64RenderTarget", "support boundary",
IsSupportedStandard64RenderTarget(info) &&
IsTiledRenderTarget(info),
"exact Standard64KB render target was not classified as tiled");
Require(
"Standard64RenderTarget", "display tile boundary",
IsSupportedDisplayRenderTargetTileMode(
Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget)) &&
!IsSupportedDisplayRenderTargetTileMode(tile),
"Standard64KB render target could alias a mode-27 display image");
auto unsupported = info;
unsupported.address += 4;
Require("Standard64RenderTarget", "address guard",
!IsSupportedStandard64RenderTarget(unsupported),
"unaligned Standard64KB backing was accepted");
unsupported = info;
unsupported.bytes_per_element = 8;
Require("Standard64RenderTarget", "element guard",
!IsSupportedStandard64RenderTarget(unsupported),
"unimplemented Standard64KB element size was accepted");
unsupported = info;
unsupported.pitch += 128;
Require("Standard64RenderTarget", "pitch guard",
!IsSupportedStandard64RenderTarget(unsupported),
"non-minimal Standard64KB pitch was accepted");
unsupported = info;
unsupported.size += 0x10000;
Require("Standard64RenderTarget", "size guard",
!IsSupportedStandard64RenderTarget(unsupported),
"non-exact Standard64KB allocation was accepted");
unsupported = info;
unsupported.levels = 2;
Require("Standard64RenderTarget", "mip guard",
!IsSupportedStandard64RenderTarget(unsupported),
"unimplemented Standard64KB mip chain was accepted");
unsupported = info;
unsupported.layers = 2;
Require("Standard64RenderTarget", "layer guard",
!IsSupportedStandard64RenderTarget(unsupported),
"unimplemented Standard64KB array was accepted");
std::vector<uint32_t> linear(storage.size / sizeof(uint32_t), 0);
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
linear[static_cast<uint64_t>(y) * pitch + x] =
(y * 0x1f123bb5u) ^ (x * 0x9e3779b9u) ^ 0xa55a3cc3u;
}
}
std::vector<uint32_t> tiled(linear.size(), 0xcdcdcdcdu);
std::vector<uint32_t> restored(linear.size(), 0xa5a5a5a5u);
Tiler tiler;
tiler.TileImage(tiled.data(), linear.data(), info);
TileConvertTiledToLinearStandard64KB32(
restored.data(), tiled.data(), width, height, pitch, storage.size);
for (uint32_t y = 0; y < height; y++) {
const auto row = static_cast<uint64_t>(y) * pitch;
Require("Standard64RenderTarget", "round trip",
std::memcmp(linear.data() + row, restored.data() + row,
width * sizeof(uint32_t)) == 0,
"Standard64KB render-target conversion did not round-trip");
}
Require("Standard64RenderTarget", "padding",
tiled.back() == 0,
"Standard64KB render-target conversion retained stale padding");
constexpr uint32_t block_width = 128;
constexpr uint32_t block_height = 128;
constexpr uint64_t block_size = 0x10000;
std::vector<uint32_t> known(block_size / sizeof(uint32_t));
for (uint32_t i = 0; i < known.size(); i++) {
known[i] = i;
}
std::vector<uint32_t> standard(known.size(), 0);
std::vector<uint32_t> render_target(known.size(), 0);
TileConvertLinearToTiledStandard64KB32(
standard.data(), known.data(), block_width, block_height, block_width,
block_size);
Require("Standard64RenderTarget", "SDK byte mapping",
standard[0] == 0 && standard[1] == 1 && standard[2] == 2 &&
standard[4] == 128 && standard[8] == 256 &&
standard[16] == 512 && standard[32] == 4 &&
standard[16383] == 16383,
"Standard64KB reciprocal mapping disagreed with the PS5 SDK");
TileConvertLinearToTiledRenderTarget(
render_target.data(), known.data(), block_width, block_height,
block_width, sizeof(uint32_t), block_size);
Require("Standard64RenderTarget", "mode identity",
standard != render_target,
"Standard64KB was accidentally treated as RenderTarget/XOR tiling");
char path[MAX_PATH]{};
Require("Standard64RenderTarget", "host",
GetModuleFileNameA(nullptr, path, MAX_PATH) != 0,
"GetModuleFileName failed");
std::string command =
std::string("\"") + path + "\" --standard64-rt-death";
std::vector<char> mutable_command(command.begin(), command.end());
mutable_command.push_back('\0');
STARTUPINFOA startup{sizeof(startup)};
PROCESS_INFORMATION process{};
Require("Standard64RenderTarget", "host",
CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr,
FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup,
&process) != 0,
"CreateProcess failed");
Require("Standard64RenderTarget", "host",
WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0,
"unsupported Standard64KB render-target case timed out");
DWORD exit_code = 0;
const bool exited = GetExitCodeProcess(process.hProcess, &exit_code) != 0;
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
Require("Standard64RenderTarget", "hard failure",
exited && exit_code == 321,
"unsupported Standard64KB render-target shape lost its fatal guard");
std::printf("[host] %-32s ok\n", "Standard64RenderTarget");
}
void CheckRenderTargetTileRoundTrip() {
struct Case {
uint32_t width;
@ -11790,10 +11964,17 @@ int main(int argc, char **argv) {
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-death") == 0) {
RunReverseRenderTargetDeathCase();
}
if (argc == 2 && std::strcmp(argv[1], "--standard64-rt-death") == 0) {
RunStandard64RenderTargetDeathCase();
}
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-only") == 0) {
CheckReverseRenderTargetFormatContract();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--standard64-rt-only") == 0) {
CheckStandard64RenderTargetTileRoundTrip();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) {
CheckImageOverlapResolution();
return 0;
@ -11878,6 +12059,7 @@ int main(int argc, char **argv) {
CheckStorageTextureLinearReadbackLayout();
CheckStorageImageSwizzleSpecializationId();
CheckColorResolveLayers();
CheckStandard64RenderTargetTileRoundTrip();
CheckRenderTargetTileRoundTrip();
CheckDepthTargetTileRoundTrip();
CheckStencilTargetTileRoundTrip();