diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 8ba6e2a4f..e84c19986 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -867,22 +867,28 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding, case BindingType::Texture: break; case BindingType::Storage: owner = "StorageTextureCache"; break; case BindingType::RenderTarget: + if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 || + info.samples != 1 || image.backing.samples != 1) { + EXIT("TextureCache: invalid color-attachment upload\n"); + } + format = ImageOps::RenderTargetTransferFormat(info.bytes_per_block); + allow_depth_tile = false; + plan.swap_bgra16 = info.bgra16; + owner = "RenderTarget"; + break; case BindingType::VideoOut: if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 || info.samples != 1 || image.backing.samples != 1 || - (binding == BindingType::VideoOut && - info.metadata.compression != VideoOutCompression::Uncompressed)) { + info.metadata.compression != VideoOutCompression::Uncompressed) { EXIT("TextureCache: invalid color-attachment upload\n"); } - format = binding == BindingType::RenderTarget - ? ImageOps::RenderTargetTransferFormat(info.bytes_per_block) - : info.guest_format; + format = info.guest_format; layers = info.resources.layers; volume = false; layered = layers > 1; allow_depth_tile = false; plan.swap_bgra16 = info.bgra16; - owner = binding == BindingType::RenderTarget ? "RenderTarget" : "VideoOut"; + owner = "VideoOut"; break; case BindingType::DepthTarget: return plan; } diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index ffed443b3..b81611e57 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -119,7 +119,18 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB uint32_t pitch = 0; uint64_t size = 0; bool tile = false; - const bool standard64 = + const bool volume = rt.attrib3.dimension == 2; + if (rt.attrib3.dimension != 1 && !volume) { + EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension); + } + if (!volume && rt.attrib3.depth != 0) { + EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth); + } + if (volume && samples != 1) { + EXIT("multisampled 3D render targets are unsupported\n"); + } + const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u; + const bool standard64 = rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB); switch (rt.attrib3.tile_mode) { @@ -145,6 +156,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB if (bytes_per_element == 0) { EXIT("render-target format has no valid element size\n"); } + const auto transfer_format = ImageOps::RenderTargetTransferFormat(bytes_per_element); 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 || @@ -165,10 +177,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB if (rt.pitch.pitch_div8_minus1 != 0) { pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u; } else if (tile) { - pitch = standard64 - ? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), - width, levels, rt.attrib3.tile_mode) - : TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments); + if (volume) { + pitch = TileGetTexturePitch(transfer_format, width, levels, rt.attrib3.tile_mode); + } else if (standard64) { + pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), + width, levels, rt.attrib3.tile_mode); + } else { + pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments); + } if (pitch == 0) { EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element); } @@ -176,9 +192,19 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB pitch = width; } - TileSizeOffset mip_sizes[16] {}; - TilePaddedSize mip_padded[16] {}; - if (tile) { + TileSizeOffset mip_sizes[16] {}; + TilePaddedSize mip_padded[16] {}; + TileVolumeLayout volume_layout {}; + uint64_t backing_size = 0; + if (volume) { + if (!tile || !TileGetTextureVolumeLayout(transfer_format, width, height, depth, levels, + rt.attrib3.tile_mode, volume_layout)) { + EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height, + depth, levels, rt.attrib3.tile_mode); + } + size = volume_layout.block_slice_size; + backing_size = volume_layout.total_size; + } else if (tile) { TileSizeAlign layout {}; bool valid_layout = false; if (standard64) { @@ -203,12 +229,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB mip_sizes[0] = {static_cast(size), 0, 0, 0, 0, 0}; mip_padded[0] = {pitch, height}; } - if (rt.slice.slice_div64_minus1 != 0 && - (static_cast(rt.slice.slice_div64_minus1) + 1u) * 64u != size) { - EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64 - "\n", - (static_cast(rt.slice.slice_div64_minus1) + 1u) * 64u, size); - } } else { size = static_cast(pitch) * height * bytes_per_element * samples; if (size > UINT32_MAX) { @@ -217,40 +237,66 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB mip_sizes[0] = {static_cast(size), 0, 0, 0, 0, 0}; mip_padded[0] = {pitch, height}; } - if (size == 0 || size > UINT64_MAX / view.image_layers) { + if (rt.slice.slice_div64_minus1 != 0 && + (static_cast(rt.slice.slice_div64_minus1) + 1u) * 64u != size) { + EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64 + "\n", + (static_cast(rt.slice.slice_div64_minus1) + 1u) * 64u, size); + } + if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) { EXIT("render-target memory footprint is invalid\n"); } - const auto backing_size = size * view.image_layers; + if (!volume) { + backing_size = size * view.image_layers; + } + if (backing_size == 0) { + EXIT("render-target backing is empty\n"); + } if (backing_size > TRACKER_ADDRESS_SIZE - rt.base.addr) { EXIT("render-target backing range is invalid\n"); } const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u), std::max(height >> rt.view.current_mip_level, 1u)}; + const uint32_t view_depth = std::max(depth >> rt.view.current_mip_level, 1u); + if (volume && + (view.base_layer >= view_depth || view.layer_count > view_depth - view.base_layer)) { + EXIT("3D render-target view exceeds mip depth: base=%u count=%u depth=%u mip=%u\n", + view.base_layer, view.layer_count, view_depth, rt.view.current_mip_level); + } auto decision_log_id = g_render_color_log_count.fetch_add(1); if (decision_log_id < 128) { LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64 - " extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u" + " extent=%ux%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u" " fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n", - rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level, + rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level, view_extent.width, view_extent.height, levels, pitch, rt.info.format, rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear"); } TextureCache::ImageDesc desc {}; - desc.type = TextureCache::BindingType::RenderTarget; - desc.info.data = {rt.base.addr, backing_size}; - desc.info.pixel_format = target_format.format; - desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element); - desc.info.type = Prospero::ImageType::kColor2D; - desc.info.extent = {width, height, 1}; - desc.info.resources = {levels, view.image_layers}; - desc.info.pitch = pitch; + desc.type = TextureCache::BindingType::RenderTarget; + desc.info.data = {rt.base.addr, backing_size}; + desc.info.pixel_format = target_format.format; + desc.info.guest_format = transfer_format; + desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D; + desc.info.extent = {width, height, depth}; + desc.info.resources = {levels, volume ? 1u : view.image_layers}; + desc.info.pitch = pitch; desc.info.bytes_per_block = bytes_per_element; desc.info.samples = samples; desc.info.tile_mode = rt.attrib3.tile_mode; for (uint32_t level = 0; level < levels; level++) { + if (volume) { + desc.info.mip_layout[level] = { + volume_layout.level_offsets[level], + volume_layout.level_sizes[level], + volume_layout.level_widths[level], + volume_layout.level_heights[level], + }; + continue; + } const auto level_offset = mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset; const auto level_size = diff --git a/src/graphics/host_gpu/renderer/debug.cpp b/src/graphics/host_gpu/renderer/debug.cpp index bda3fcc89..7a8fe33b6 100644 --- a/src/graphics/host_gpu/renderer/debug.cpp +++ b/src/graphics/host_gpu/renderer/debug.cpp @@ -359,30 +359,12 @@ static void RtCheck(const HW::RenderTarget& rt) { 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) { diff --git a/src/graphics/host_gpu/renderer/image/image.cpp b/src/graphics/host_gpu/renderer/image/image.cpp index b025e3db8..e22203de7 100644 --- a/src/graphics/host_gpu/renderer/image/image.cpp +++ b/src/graphics/host_gpu/renderer/image/image.cpp @@ -105,6 +105,10 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destinat std::optional range) { auto& state = backing.state; auto& subresource_states = backing.subresource_states; + if (range && info.IsVolume()) { + range->base_layer = 0; + range->layer_count = 1; + } const bool partial = range && (range->base_level != 0 || range->level_count != info.resources.levels || diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 680fdbe76..b9490b6bd 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -248,6 +248,12 @@ struct RenderExecutorTestAccess { executor.ResolveRenderDepthTarget(submit_id, buffer, depth); } + static void ResolveRenderColorTarget(RenderExecutor& executor, uint64_t submit_id, + RenderCommandBuffer& buffer, RenderColorInfo& color, + uint32_t slot) { + executor.ResolveRenderColorTarget(submit_id, buffer, color, 0, slot); + } + static void BindRenderTarget(RenderExecutor& executor, ImageId id) { executor.BindRenderTarget(id); } @@ -4751,6 +4757,155 @@ public: std::printf("[gpu] %-32s ok\n", name); } + void CheckRenderExecutorColorVolumeDiscovery() { + constexpr const char* name = "RenderExecutorColorVolumeDiscovery"; + constexpr uintptr_t base = 0x0000000203e00000ull; + constexpr uint64_t allocation_size = 0x200000; + constexpr uint64_t allocation_alignment = 0x10000; + EnsureRuntimeContext(); + + int64_t direct_offset = -1; + Require(name, "direct allocation", + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), allocation_size, + allocation_alignment, 0, &direct_offset) == 0, + "color-volume direct-memory allocation failed"); + void* mapped = reinterpret_cast(base); + Require(name, "direct mapping", + Libs::LibKernel::Memory::KernelMapDirectMemory(&mapped, allocation_size, 0x3, 0x10, + direct_offset, + allocation_alignment) == 0 && + mapped == reinterpret_cast(base), + "color-volume fixed mapping failed"); + std::memset(mapped, 0, allocation_size); + constexpr uint64_t slice_size = 0x10000; + std::memset(static_cast(mapped) + 31 * slice_size, 0x5a, slice_size); + + { + RenderContext context(m_runtime_context); + auto& scheduler = context.GetCommandScheduler(); + HW::Context registers {}; + HW::UserConfig user_config {}; + HW::Shader shaders {}; + registers.SetColorBase(0, {.addr = base}); + registers.SetColorInfo( + 0, {.format = Prospero::GpuEnumValue(Prospero::ChannelLayout::k10_10_10_2), + .channel_type = Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm), + .channel_order = Prospero::GpuEnumValue(Prospero::ChannelOrder::kStandard)}); + registers.SetColorAttrib2(0, {.height = 31, .width = 31}); + registers.SetColorAttrib3( + 0, {.depth = 31, + .tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget), + .dimension = 2, + .cmask_pipe_aligned = true, + .dcc_pipe_aligned = true}); + registers.SetRenderTargetMask(0x0f); + scheduler.Begin(registers, user_config, shaders); + + auto& resources = context.GetGpuResources(); + auto& texture_cache = resources.GetTextureCache(); + auto& executor = context.GetRenderExecutor(); + resources.MapMemory(base, allocation_size); + + RenderColorInfo color {}; + RenderExecutorTestAccess::ResolveRenderColorTarget(executor, 1, scheduler.Current(), + color, 0); + const auto attachment = texture_cache.FindRenderTarget(color.image_id, color.desc); + const auto& image = texture_cache.GetImage(color.image_id); + Require(name, "captured 3D target", + color.image_id && attachment != nullptr && + color.desc.info.type == Prospero::ImageType::kColor3D && + color.desc.info.extent == vk::Extent3D {32, 32, 32} && + color.desc.info.resources == ImageSubresources {1, 1} && + color.desc.info.pitch == 128 && + color.desc.info.data.size == allocation_size && + color.desc.info.mip_layout[0].size == 0x10000 && + color.desc.view_info.type == vk::ImageViewType::e2D && + color.desc.view_info.layer_count == 1 && + image.backing.image_type == vk::ImageType::e3D && + static_cast(image.backing.flags & + vk::ImageCreateFlagBits::e2DArrayCompatible) && + image.usage.render_target && image.IsGpuModified(), + "dimension=2/depth=31 did not create the SDK-defined 32x32x32 " + "backing and 2D " + "attachment slice"); + + RenderExecutorTestAccess::ResetBindings(executor); + registers.SetColorView( + 0, {.base_array_slice_index = 7, .last_array_slice_index = 7}); + RenderColorInfo sliced_color {}; + RenderExecutorTestAccess::ResolveRenderColorTarget( + executor, 2, scheduler.Current(), sliced_color, 0); + RenderDepthInfo no_depth {}; + const auto sliced_rendering = RenderExecutorTestAccess::AcquireRenderTargets( + executor, scheduler.Current(), &sliced_color, 1, no_depth); + Require(name, "3D slice transition", + sliced_color.image_id == color.image_id && sliced_color.image_view != nullptr && + sliced_color.desc.view_info.base_layer == 7 && + sliced_rendering.num_color_attachments == 1 && sliced_rendering.num_layers == 1, + "a nonzero 3D attachment slice was treated as a Vulkan array layer"); + + auto storage_desc = color.desc; + storage_desc.type = BindingType::Storage; + storage_desc.info.guest_format = + Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm); + storage_desc.view_info.type = vk::ImageViewType::e3D; + storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage; + const auto storage_id = texture_cache.FindImage(storage_desc); + const auto storage_view = texture_cache.FindTexture(storage_id, storage_desc); + const auto& shared_image = texture_cache.GetImage(storage_id); + Require(name, "storage alias reuse", + storage_id == color.image_id && storage_view != nullptr && + shared_image.IsGpuModified() && shared_image.usage.render_target, + "the matching 3D storage binding did not reuse the live " + "render-target image"); + + Require(name, "volume readback queue", + TextureCacheTestAccess::TryDownload(texture_cache, storage_id), + "the 3D render target could not be queued for guest-layout readback"); + auto mirror = resources.GetBufferCache().ObtainBuffer( + scheduler.Current(), base, allocation_size, false, true, true); + Require(name, "volume mirror", + mirror.buffer != nullptr && mirror.owner != nullptr, + "the 3D render-target readback has no BufferCache owner"); + scheduler.Current().RetainResourceUntilFence(mirror.owner); + auto slice_probe = + CreateHostBuffer(name, 4, vk::BufferUsageFlagBits::eTransferDst, {0}); + const vk::BufferCopy copy {mirror.offset + 31 * slice_size, 0, 4}; + scheduler.Current().Handle().copyBuffer(mirror.buffer, slice_probe.buffer, 1, ©); + vk::BufferMemoryBarrier barrier {}; + barrier.sType = vk::StructureType::eBufferMemoryBarrier; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eHostRead; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.buffer = slice_probe.buffer; + barrier.size = slice_probe.size; + scheduler.Current().Handle().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, + 1, &barrier, 0, nullptr); + scheduler.Finish(); + scheduler.DrainPriorityOperations(); + Require(name, "volume Z transfer", + ReadBuffer(name, slice_probe, 1) == std::vector {0x5a5a5a5a}, + "render-target upload/readback lost the final Z slice"); + DestroyBuffer(&slice_probe); + + RenderExecutorTestAccess::ResetBindings(executor); + resources.UnmapMemory(base, allocation_size); + scheduler.Finish(); + } + + Require(name, "unmap direct backing", + Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0, + "color-volume direct mapping release failed"); + Require( + name, "release direct backing", + Libs::LibKernel::Memory::KernelReleaseDirectMemory(direct_offset, allocation_size) == 0, + "color-volume direct-memory allocation release failed"); + std::printf("[gpu] %-32s ok\n", name); + } + void CheckRenderExecutorStencilBindingDiscovery() { constexpr const char* name = "RenderExecutorStencilBindingDiscovery"; constexpr uintptr_t base = 0x0000000203600000ull; @@ -17075,6 +17230,7 @@ int main(int argc, char** argv) { CheckDepthAttachmentWrites(); CheckDynamicRenderingState(); VulkanHarness vulkan; + vulkan.CheckRenderExecutorColorVolumeDiscovery(); vulkan.CheckRenderExecutorStencilBindingDiscovery(); vulkan.CheckUnifiedTextureCacheFlow(); vulkan.CheckBgra16Readback(); @@ -17215,6 +17371,7 @@ int main(int argc, char** argv) { vulkan.CheckCommandPoolGrowth(); vulkan.CheckGpuTilerCpuParity(); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS + vulkan.CheckRenderExecutorColorVolumeDiscovery(); vulkan.CheckRenderExecutorStencilBindingDiscovery(); vulkan.CheckUnifiedTextureCacheFlow(); vulkan.CheckBgra16Readback();