diff --git a/src/xenia/gpu/d3d12/d3d12_texture_cache.cc b/src/xenia/gpu/d3d12/d3d12_texture_cache.cc index 5226f5696..94d2e038c 100644 --- a/src/xenia/gpu/d3d12/d3d12_texture_cache.cc +++ b/src/xenia/gpu/d3d12/d3d12_texture_cache.cc @@ -1732,7 +1732,36 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, source_box.front + std::max(depth >> level, uint32_t(1)); source_box_ptr = &source_box; } else { + // Non-packed level: copy the whole footprint. The footprint is sized as + // the guest mip reduced then scaled, while the host mip subresource is + // the base scaled then reduced, so for the deepest mips of scaled + // textures the footprint can be a row or column larger. Compressed dests + // round up to the block and absorb it. For uncompressed dests clamp the + // copy to the exact subresource with an explicit source box to avoid + // overrunning. Clamp per axis to min(footprint, subresource) since a + // non-power-of-two axis can be smaller than the subresource while another + // axis is larger, and the box must not exceed the source footprint + // either. source_box_ptr = nullptr; + if (!host_block_compressed) { + const D3D12_SUBRESOURCE_FOOTPRINT& footprint = + location_source.PlacedFootprint.Footprint; + uint32_t dst_width = std::max( + (width * texture_resolution_scale_x) >> level, uint32_t(1)); + uint32_t dst_height = std::max( + (height * texture_resolution_scale_y) >> level, uint32_t(1)); + uint32_t dst_depth = std::max(depth >> level, uint32_t(1)); + if (footprint.Width > dst_width || footprint.Height > dst_height || + footprint.Depth > dst_depth) { + source_box.left = 0; + source_box.top = 0; + source_box.front = 0; + source_box.right = std::min(footprint.Width, dst_width); + source_box.bottom = std::min(footprint.Height, dst_height); + source_box.back = std::min(footprint.Depth, dst_depth); + source_box_ptr = &source_box; + } + } } for (uint32_t slice = 0; slice < array_size; ++slice) { command_list.D3DCopyTextureRegion(&location_dest, 0, 0, 0, diff --git a/src/xenia/gpu/texture_cache.h b/src/xenia/gpu/texture_cache.h index 029aaf213..ac03a329e 100644 --- a/src/xenia/gpu/texture_cache.h +++ b/src/xenia/gpu/texture_cache.h @@ -638,6 +638,19 @@ class TextureCache { // implementation to update the internal dependencies of the binding. virtual void UpdateTextureBindingsImpl(uint32_t fetch_constant_mask) {} + // Checks if there are any pages that contain scaled resolve data within the + // range. + bool IsRangeScaledResolved(uint32_t start_unscaled, uint32_t length_unscaled); + + // Whether the mips of a scaled resolve texture must be generated on the host + // (the guest did not resolve them into scaled memory itself). + bool ScaledResolveMipsNeedGeneration(const Texture& texture) { + const TextureKey& key = texture.key(); + return key.scaled_resolve && key.mip_max_level != 0 && + !IsRangeScaledResolved(key.mip_page << 12, + texture.GetGuestMipsSize()); + } + private: void UpdateTexturesTotalHostMemoryUsage(uint64_t add, uint64_t subtract); @@ -646,9 +659,6 @@ class TextureCache { void* context, void* data, uint64_t argument, bool invalidated_by_gpu); - // Checks if there are any pages that contain scaled resolve data within the - // range. - bool IsRangeScaledResolved(uint32_t start_unscaled, uint32_t length_unscaled); // Global shared memory invalidation callback for invalidating scaled resolved // texture data. static void ScaledResolveGlobalWatchCallbackThunk( diff --git a/src/xenia/gpu/vulkan/vulkan_texture_cache.cc b/src/xenia/gpu/vulkan/vulkan_texture_cache.cc index d494a19bb..8db76d763 100644 --- a/src/xenia/gpu/vulkan/vulkan_texture_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_texture_cache.cc @@ -1233,10 +1233,9 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, uint32_t bytes_per_block = guest_format_info->bytes_per_block(); uint32_t level_first = load_base ? 0 : 1; uint32_t level_last = load_mips ? texture_key.mip_max_level : 0; - // For scaled resolve textures, we only load level 0 from the scaled buffer - - // mips will be generated via blit. + // Load the guest's resolved mips from the scaled buffer, else generate them. uint32_t level_last_for_blit_gen = 0; - if (texture_key.scaled_resolve && level_last > 0) { + if (level_last > 0 && ScaledResolveMipsNeedGeneration(texture)) { level_last_for_blit_gen = level_last; level_last = 0; // Only load base level from buffer } @@ -1755,10 +1754,17 @@ bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, copy_region.imageOffset.x = 0; copy_region.imageOffset.y = 0; copy_region.imageOffset.z = 0; - copy_region.imageExtent.width = - std::max((width * texture_resolution_scale_x) >> level, UINT32_C(1)); - copy_region.imageExtent.height = - std::max((height * texture_resolution_scale_y) >> level, UINT32_C(1)); + // The image mip is scale-then-reduce (max((dim*scale)>>level,1)) while the + // buffer footprint (bufferRowLength/bufferImageHeight) is + // reduce-then-scale, so for the deepest mips of scaled textures the mip can + // be a row or column larger than the buffer holds. Clamp the copy extent to + // the footprint so the GPU never reads past the buffer. + copy_region.imageExtent.width = std::min( + std::max((width * texture_resolution_scale_x) >> level, UINT32_C(1)), + copy_region.bufferRowLength); + copy_region.imageExtent.height = std::min( + std::max((height * texture_resolution_scale_y) >> level, UINT32_C(1)), + copy_region.bufferImageHeight); copy_region.imageExtent.depth = std::max(depth >> level, UINT32_C(1)); } @@ -2054,6 +2060,12 @@ VkImageView VulkanTextureCache::VulkanTexture::GetOrCreate3DAs2DImageView( image_create_info.format = format; image_create_info.extent.width = key().GetWidth(); image_create_info.extent.height = key().GetHeight(); + if (key().scaled_resolve) { + image_create_info.extent.width *= + vulkan_texture_cache.draw_resolution_scale_x(); + image_create_info.extent.height *= + vulkan_texture_cache.draw_resolution_scale_y(); + } image_create_info.extent.depth = 1; image_create_info.mipLevels = 1; image_create_info.arrayLayers = 1;