diff --git a/src/graphics/guest_gpu/tile.cpp b/src/graphics/guest_gpu/tile.cpp index 34df092c..ff51716b 100644 --- a/src/graphics/guest_gpu/tile.cpp +++ b/src/graphics/guest_gpu/tile.cpp @@ -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(height == 0 ? 0 : height - 1u); + const auto expected_pitch = (static_cast(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(dst); + const auto* src32 = static_cast(src); + const auto& tables = GetStandard64KB32Tables(); + const auto blocks_per_row = (static_cast(pitch) + 127u) >> 7u; + const auto block_rows = (static_cast(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)); + 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(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(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(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) { diff --git a/src/graphics/guest_gpu/tile.h b/src/graphics/guest_gpu/tile.h index e4079359..c66ec97b 100644 --- a/src/graphics/guest_gpu/tile.h +++ b/src/graphics/guest_gpu/tile.h @@ -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, diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index d11b4736..833c83c7 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -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"); diff --git a/src/graphics/host_gpu/renderer/debug.cpp b/src/graphics/host_gpu/renderer/debug.cpp index 390a0a9d..e92fb082 100644 --- a/src/graphics/host_gpu/renderer/debug.cpp +++ b/src/graphics/host_gpu/renderer/debug.cpp @@ -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); } diff --git a/src/graphics/host_gpu/renderer/imageInfo.h b/src/graphics/host_gpu/renderer/imageInfo.h index a24051a8..dc66ce89 100644 --- a/src/graphics/host_gpu/renderer/imageInfo.h +++ b/src/graphics/host_gpu/renderer/imageInfo.h @@ -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(info.width) + 127u) & ~uint64_t {127u}; + const auto padded_height = (static_cast(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, diff --git a/src/graphics/host_gpu/renderer/textureCache.cpp b/src/graphics/host_gpu/renderer/textureCache.cpp index 0f0abb6c..62dc7503 100644 --- a/src/graphics/host_gpu/renderer/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/textureCache.cpp @@ -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(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(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(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(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; diff --git a/src/graphics/host_gpu/renderer/tiler.cpp b/src/graphics/host_gpu/renderer/tiler.cpp index bec5978d..8d9cd3d2 100644 --- a/src/graphics/host_gpu/renderer/tiler.cpp +++ b/src/graphics/host_gpu/renderer/tiler.cpp @@ -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(dst) + slice_size * layer; auto* linear_slice = static_cast(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); + } } } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index e11a6464..d8ab5a64 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -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 linear(info.size, 0); + std::vector 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 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(y) * pitch + x] = + (y * 0x1f123bb5u) ^ (x * 0x9e3779b9u) ^ 0xa55a3cc3u; + } + } + std::vector tiled(linear.size(), 0xcdcdcdcdu); + std::vector 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(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 known(block_size / sizeof(uint32_t)); + for (uint32_t i = 0; i < known.size(); i++) { + known[i] = i; + } + std::vector standard(known.size(), 0); + std::vector 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 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();