diff options
| -rw-r--r-- | src/xenia/gpu/vulkan/render_cache.cc | 5 | ||||
| -rw-r--r-- | src/xenia/gpu/vulkan/render_cache.h | 1 | ||||
| -rw-r--r-- | src/xenia/gpu/vulkan/texture_cache.cc | 355 | ||||
| -rw-r--r-- | src/xenia/gpu/vulkan/texture_cache.h | 93 | ||||
| -rw-r--r-- | src/xenia/gpu/vulkan/vulkan_command_processor.cc | 39 |
5 files changed, 340 insertions, 153 deletions
diff --git a/src/xenia/gpu/vulkan/render_cache.cc b/src/xenia/gpu/vulkan/render_cache.cc index 2b7d20a7b..5cdb848c0 100644 --- a/src/xenia/gpu/vulkan/render_cache.cc +++ b/src/xenia/gpu/vulkan/render_cache.cc @@ -641,6 +641,7 @@ bool RenderCache::dirty() const { dirty |= cur_regs.rb_color2_info.value != regs[XE_GPU_REG_RB_COLOR2_INFO].u32; dirty |= cur_regs.rb_color3_info.value != regs[XE_GPU_REG_RB_COLOR3_INFO].u32; dirty |= cur_regs.rb_depth_info.value != regs[XE_GPU_REG_RB_DEPTH_INFO].u32; + dirty |= cur_regs.rb_color_mask != regs[XE_GPU_REG_RB_COLOR_MASK].u32; dirty |= cur_regs.pa_sc_window_scissor_tl != regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL].u32; dirty |= cur_regs.pa_sc_window_scissor_br != @@ -659,6 +660,7 @@ const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer, current_command_buffer_ = command_buffer; // Lookup or construct a render pass compatible with our current state. + auto previous_render_pass = current_state_.render_pass; auto config = ¤t_state_.config; CachedRenderPass* render_pass = nullptr; CachedFramebuffer* framebuffer = nullptr; @@ -678,6 +680,7 @@ const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer, SetShadowRegister(®s.rb_color3_info.value, XE_GPU_REG_RB_COLOR3_INFO); dirty |= SetShadowRegister(®s.rb_depth_info.value, XE_GPU_REG_RB_DEPTH_INFO); + dirty |= SetShadowRegister(®s.rb_color_mask, XE_GPU_REG_RB_COLOR_MASK); dirty |= SetShadowRegister(®s.pa_sc_window_scissor_tl, XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL); dirty |= SetShadowRegister(®s.pa_sc_window_scissor_br, @@ -803,6 +806,7 @@ bool RenderCache::ParseConfiguration(RenderConfiguration* config) { for (int i = 0; i < 4; ++i) { config->color[i].edram_base = color_info[i].color_base; config->color[i].format = color_info[i].color_format; + config->color[i].used = ((regs.rb_color_mask >> (i * 4)) & 0xf) != 0; // We don't support GAMMA formats, so switch them to what we do support. switch (config->color[i].format) { case ColorRenderTargetFormat::k_8_8_8_8_GAMMA: @@ -832,6 +836,7 @@ bool RenderCache::ParseConfiguration(RenderConfiguration* config) { config->mode_control == ModeControl::kDepth) { config->depth_stencil.edram_base = regs.rb_depth_info.depth_base; config->depth_stencil.format = regs.rb_depth_info.depth_format; + config->depth_stencil.used = true; } else { config->depth_stencil.edram_base = 0; config->depth_stencil.format = DepthRenderTargetFormat::kD24S8; diff --git a/src/xenia/gpu/vulkan/render_cache.h b/src/xenia/gpu/vulkan/render_cache.h index 3b43a5f98..432dc5992 100644 --- a/src/xenia/gpu/vulkan/render_cache.h +++ b/src/xenia/gpu/vulkan/render_cache.h @@ -380,6 +380,7 @@ class RenderCache { reg::RB_COLOR_INFO rb_color2_info; reg::RB_COLOR_INFO rb_color3_info; reg::RB_DEPTH_INFO rb_depth_info; + uint32_t rb_color_mask; uint32_t pa_sc_window_scissor_tl; uint32_t pa_sc_window_scissor_br; diff --git a/src/xenia/gpu/vulkan/texture_cache.cc b/src/xenia/gpu/vulkan/texture_cache.cc index 95ed924c5..bccb1b7b8 100644 --- a/src/xenia/gpu/vulkan/texture_cache.cc +++ b/src/xenia/gpu/vulkan/texture_cache.cc @@ -235,12 +235,13 @@ void TextureCache::Shutdown() { nullptr); } -TextureCache::Texture* TextureCache::AllocateTexture( - const TextureInfo& texture_info, VkFormatFeatureFlags required_flags) { +TextureCache::TextureRegion* TextureCache::AllocateTextureRegion( + Texture* texture, VkOffset3D region_offset, VkExtent3D region_size, + VkFormatFeatureFlags required_flags) { // Create an image first. VkImageCreateInfo image_info = {}; image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - switch (texture_info.dimension) { + switch (texture->texture_info.dimension) { case Dimension::k1D: case Dimension::k2D: image_info.imageType = VK_IMAGE_TYPE_2D; @@ -253,18 +254,19 @@ TextureCache::Texture* TextureCache::AllocateTexture( image_info.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; break; default: - assert_unhandled_case(texture_info.dimension); + assert_unhandled_case(texture->texture_info.dimension); return nullptr; } - assert_not_null(texture_info.format_info()); - auto& config = texture_configs[int(texture_info.format_info()->format)]; + assert_not_null(texture->texture_info.format_info()); + auto& config = + texture_configs[int(texture->texture_info.format_info()->format)]; VkFormat format = config.host_format; if (format == VK_FORMAT_UNDEFINED) { XELOGE( "Texture Cache: Attempted to allocate texture format %s, which is " "defined as VK_FORMAT_UNDEFINED!", - texture_info.format_info()->name); + texture->texture_info.format_info()->name); return nullptr; } @@ -274,20 +276,17 @@ TextureCache::Texture* TextureCache::AllocateTexture( // Check the device limits for the format before we create it. VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(*device_, format, &props); + vkGetPhysicalDeviceFormatProperties(*device_, texture->format, &props); if ((props.optimalTilingFeatures & required_flags) != required_flags) { // Texture needs conversion on upload to a native format. XELOGE( - "Texture Cache: Invalid usage flag specified on format %s (%s)\n\t" - "(requested: %s)", - texture_info.format_info()->name, ui::vulkan::to_string(format), - ui::vulkan::to_flags_string( - static_cast<VkFormatFeatureFlagBits>(required_flags & - ~props.optimalTilingFeatures)) - .c_str()); + "Texture Cache: Invalid usage flag specified on format %s (vk %d) " + "(0x%.8X != 0x%.8X)", + texture->texture_info.format_info()->name, texture->format, + (props.optimalTilingFeatures & required_flags), required_flags); } - if (texture_info.dimension != Dimension::kCube && + if (texture->texture_info.dimension != Dimension::kCube && props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) { // Add color attachment usage if it's supported. image_info.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; @@ -303,44 +302,78 @@ TextureCache::Texture* TextureCache::AllocateTexture( VkImageFormatProperties image_props; vkGetPhysicalDeviceImageFormatProperties( - *device_, format, image_info.imageType, image_info.tiling, + *device_, texture->format, image_info.imageType, image_info.tiling, image_info.usage, image_info.flags, &image_props); // TODO(DrChat): Actually check the image properties. - image_info.format = format; - image_info.extent = {texture_info.width + 1, texture_info.height + 1, 1}; - image_info.mipLevels = texture_info.mip_levels; - image_info.arrayLayers = texture_info.depth + 1; + image_info.format = texture->format; + image_info.extent = region_size; + image_info.mipLevels = texture->texture_info.mip_levels; + image_info.arrayLayers = texture->texture_info.depth + 1; image_info.samples = VK_SAMPLE_COUNT_1_BIT; image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; image_info.queueFamilyIndexCount = 0; image_info.pQueueFamilyIndices = nullptr; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImage image; - VmaAllocation alloc; + VmaAllocation allocation; VmaAllocationCreateInfo vma_create_info = { 0, VMA_MEMORY_USAGE_GPU_ONLY, 0, 0, 0, nullptr, nullptr, }; VmaAllocationInfo vma_info = {}; - VkResult status = vmaCreateImage(mem_allocator_, &image_info, - &vma_create_info, &image, &alloc, &vma_info); + VkResult status = + vmaCreateImage(mem_allocator_, &image_info, &vma_create_info, &image, + &allocation, &vma_info); if (status != VK_SUCCESS) { // Allocation failed. return nullptr; } + TextureRegion* region = new TextureRegion(); + region->texture = texture; + + region->region_offset = region_offset; + region->region_size = region_size; + + region->image = image; + region->image_layout = image_info.initialLayout; + region->usage_flags = image_info.usage; + region->allocation = allocation; + region->allocation_info = vma_info; + + region->region_contents_valid = false; + return region; +} + +TextureCache::Texture* TextureCache::AllocateTexture( + const TextureInfo& texture_info, VkFormatFeatureFlags required_flags) { + VkOffset3D region_offset = {0, 0, 0}; + VkExtent3D region_extent = {texture_info.width + 1, texture_info.height + 1, + 1}; + auto texture = new Texture(); - texture->format = image_info.format; - texture->image = image; - texture->image_layout = image_info.initialLayout; - texture->alloc = alloc; - texture->alloc_info = vma_info; texture->framebuffer = nullptr; - texture->usage_flags = image_info.usage; texture->access_watch_handle = 0; texture->texture_info = texture_info; + + assert_not_null(texture->texture_info.format_info()); + auto& config = + texture_configs[int(texture->texture_info.format_info()->format)]; + texture->format = config.host_format != VK_FORMAT_UNDEFINED + ? config.host_format + : VK_FORMAT_R8G8B8A8_UNORM; + + TextureRegion* base_region = AllocateTextureRegion( + texture, region_offset, region_extent, required_flags); + if (!base_region) { + delete texture; + return nullptr; + } + + texture->base_region = base_region; return texture; } @@ -357,17 +390,28 @@ bool TextureCache::FreeTexture(Texture* texture) { vkDestroyFramebuffer(*device_, texture->framebuffer, nullptr); } - for (auto it = texture->views.begin(); it != texture->views.end();) { - vkDestroyImageView(*device_, (*it)->view, nullptr); - it = texture->views.erase(it); + for (auto region_it = texture->regions.begin(); + region_it != texture->regions.end(); ++region_it) { + TextureRegion* region = region_it->get(); + for (auto& view : region->views) { + vkDestroyImageView(*device_, view->view, nullptr); + } + vmaDestroyImage(mem_allocator_, region->image, region->allocation); + } + texture->regions.clear(); + + // Free the base region (which is not part of regions) + for (auto& view : texture->base_region->views) { + vkDestroyImageView(*device_, view->view, nullptr); } + vmaDestroyImage(mem_allocator_, texture->base_region->image, + texture->base_region->allocation); if (texture->access_watch_handle) { memory_->CancelAccessWatch(texture->access_watch_handle); texture->access_watch_handle = 0; } - vmaDestroyImage(mem_allocator_, texture->image, texture->alloc); delete texture; return true; } @@ -399,6 +443,11 @@ TextureCache::Texture* TextureCache::DemandResolveTexture( break; } + for (auto& region : it->second->regions) { + // Invalidate region contents + region->region_contents_valid = false; + } + // Tell the trace writer to "cache" this memory (but not read it) trace_writer_->WriteMemoryReadCachedNop(texture_info.guest_address, texture_info.input_length); @@ -425,7 +474,7 @@ TextureCache::Texture* TextureCache::DemandResolveTexture( // Setup a debug name for the texture. device_->DbgSetObjectName( - reinterpret_cast<uint64_t>(texture->image), + reinterpret_cast<uint64_t>(texture->base_region->image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, xe::format_string( "0x%.8X - 0x%.8X", texture_info.guest_address, @@ -441,9 +490,9 @@ TextureCache::Texture* TextureCache::DemandResolveTexture( return texture; } -TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, - VkCommandBuffer command_buffer, - VkFence completion_fence) { +TextureCache::TextureRegion* TextureCache::DemandRegion( + const TextureInfo& texture_info, VkCommandBuffer command_buffer, + VkCommandBuffer setup_buffer, VkFence completion_fence) { // Run a tight loop to scan for an exact match existing texture. auto texture_hash = texture_info.hash(); for (auto it = textures_.find(texture_hash); it != textures_.end(); ++it) { @@ -457,36 +506,113 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, trace_writer_->WriteMemoryReadCached(texture_info.guest_address, texture_info.input_length); - return it->second; + return it->second->base_region; } } - if (!command_buffer) { - // Texture not found and no command buffer was passed, preventing us from - // uploading a new one. - return nullptr; + // If we didn't find an exact match, see if we can find a subregion of an + // existing texture to use. + VkOffset2D offset; + auto containing_tex = LookupAddress( + texture_info.guest_address, texture_info.width + 1, + texture_info.height + 1, texture_info.format_info()->format, &offset); + if (containing_tex != nullptr) { + // Found a larger texture that contains the requested texels. + // Find/create/update a matching subregion. + + // First, see if the region exists + TextureRegion* region = nullptr; + for (auto region_it = containing_tex->regions.begin(); + region_it != containing_tex->regions.end(); ++region_it) { + const VkOffset3D& roffset = (*region_it)->region_offset; + const VkExtent3D& rsize = (*region_it)->region_size; + if (roffset.x == offset.x && roffset.y == offset.y && + rsize.width == (texture_info.width + 1) && + rsize.height == (texture_info.height + 1)) { + // Match found + region = region_it->get(); + break; + } + } + + if (!region) { + // No match, create a new region and add it the texture + region = AllocateTextureRegion( + containing_tex, {offset.x, offset.y, 0}, + {texture_info.width + 1, texture_info.height + 1, 1}, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); + containing_tex->regions.push_back(std::unique_ptr<TextureRegion>(region)); + } + + if (command_buffer && region && !region->region_contents_valid) { + // Region content is out-of-date, recreate it by blitting from the base + // region. + + // Transition the region into a transfer destination layout. + VkImageMemoryBarrier barrier; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = region->image; + barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + if (containing_tex->format == VK_FORMAT_D16_UNORM_S8_UINT || + containing_tex->format == VK_FORMAT_D24_UNORM_S8_UINT || + containing_tex->format == VK_FORMAT_D32_SFLOAT_S8_UINT) { + barrier.subresourceRange.aspectMask = + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } + + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, + nullptr, 1, &barrier); + + // Now move the converted texture into the destination. + VkImageCopy copy_region; + + copy_region.srcSubresource = {barrier.subresourceRange.aspectMask, 0, 0, + 1}; + copy_region.dstSubresource = {barrier.subresourceRange.aspectMask, 0, 0, + 1}; + copy_region.srcOffset = {offset.x, offset.y, 0}; + copy_region.dstOffset = {0, 0, 0}; + copy_region.extent = {texture_info.width + 1, texture_info.height + 1, 1}; + + vkCmdCopyImage(command_buffer, containing_tex->base_region->image, + containing_tex->base_region->image_layout, region->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + + // Now transition the region into a shader readonly source. + barrier.srcAccessMask = barrier.dstAccessMask; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.oldLayout = barrier.newLayout; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, + nullptr, 1, &barrier); + + region->image_layout = barrier.newLayout; + + // Mark contents as up-to-date + region->region_contents_valid = true; + } + + return region; } // Create a new texture and cache it. - auto texture = AllocateTexture(texture_info); + auto texture = + AllocateTexture(texture_info, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); if (!texture) { // Failed to allocate texture (out of memory) XELOGE("Vulkan Texture Cache: Failed to allocate texture!"); return nullptr; } - // Though we didn't find an exact match, that doesn't mean we're out of the - // woods yet. This texture could either be a portion of another texture or - // vice versa. Copy any overlapping textures into this texture. - // TODO: Byte count -> pixel count (on x and y axes) - VkOffset2D offset; - auto collide_tex = LookupAddress( - texture_info.guest_address, texture_info.width + 1, - texture_info.height + 1, texture_info.format_info()->format, &offset); - if (collide_tex != nullptr) { - // assert_always(); - } - trace_writer_->WriteMemoryRead(texture_info.guest_address, texture_info.input_length); @@ -496,14 +622,14 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, texture_info.guest_address, texture_info.input_length, cpu::MMIOHandler::kWatchWrite, &WatchCallback, this, texture); - if (!UploadTexture(command_buffer, completion_fence, texture, texture_info)) { + if (!UploadTexture(setup_buffer, completion_fence, texture, texture_info)) { FreeTexture(texture); return nullptr; } // Setup a debug name for the texture. device_->DbgSetObjectName( - reinterpret_cast<uint64_t>(texture->image), + reinterpret_cast<uint64_t>(texture->base_region->image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, xe::format_string( "0x%.8X - 0x%.8X", texture_info.guest_address, @@ -511,12 +637,12 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, textures_[texture_hash] = texture; COUNT_profile_set("gpu/texture_cache/textures", textures_.size()); - return texture; + return texture->base_region; } -TextureCache::TextureView* TextureCache::DemandView(Texture* texture, - uint16_t swizzle) { - for (auto it = texture->views.begin(); it != texture->views.end(); ++it) { +TextureCache::TextureRegionView* TextureCache::DemandTextureRegionView( + TextureRegion* region, uint16_t swizzle) { + for (auto it = region->views.begin(); it != region->views.end(); ++it) { if ((*it)->swizzle == swizzle) { return (*it).get(); } @@ -526,10 +652,10 @@ TextureCache::TextureView* TextureCache::DemandView(Texture* texture, view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; view_info.pNext = nullptr; view_info.flags = 0; - view_info.image = texture->image; - view_info.format = texture->format; + view_info.image = region->image; + view_info.format = region->texture->format; - switch (texture->texture_info.dimension) { + switch (region->texture->texture_info.dimension) { case Dimension::k1D: case Dimension::k2D: view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -550,7 +676,8 @@ TextureCache::TextureView* TextureCache::DemandView(Texture* texture, VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ONE, VK_COMPONENT_SWIZZLE_IDENTITY, }; - if (texture->texture_info.texture_format == TextureFormat::k_4_4_4_4) { + if (region->texture->texture_info.texture_format == + TextureFormat::k_4_4_4_4) { swiz_component_map[0] = VK_COMPONENT_SWIZZLE_A; swiz_component_map[1] = VK_COMPONENT_SWIZZLE_B; swiz_component_map[2] = VK_COMPONENT_SWIZZLE_G; @@ -564,15 +691,15 @@ TextureCache::TextureView* TextureCache::DemandView(Texture* texture, swiz_component_map[(swizzle >> 9) & 0x7], }; view_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, - texture->texture_info.mip_levels, 0, 1}; - if (texture->format == VK_FORMAT_D16_UNORM_S8_UINT || - texture->format == VK_FORMAT_D24_UNORM_S8_UINT || - texture->format == VK_FORMAT_D32_SFLOAT_S8_UINT) { + region->texture->texture_info.mip_levels, 0, 1}; + if (region->texture->format == VK_FORMAT_D16_UNORM_S8_UINT || + region->texture->format == VK_FORMAT_D24_UNORM_S8_UINT || + region->texture->format == VK_FORMAT_D32_SFLOAT_S8_UINT) { // This applies to any depth/stencil format, but we only use D24S8 / D32FS8. view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; } - if (texture->texture_info.dimension == Dimension::kCube) { + if (region->texture->texture_info.dimension == Dimension::kCube) { view_info.subresourceRange.layerCount = 6; } @@ -580,12 +707,12 @@ TextureCache::TextureView* TextureCache::DemandView(Texture* texture, auto status = vkCreateImageView(*device_, &view_info, nullptr, &view); CheckResult(status, "vkCreateImageView"); if (status == VK_SUCCESS) { - auto texture_view = new TextureView(); - texture_view->texture = texture; - texture_view->view = view; - texture_view->swizzle = swizzle; - texture->views.push_back(std::unique_ptr<TextureView>(texture_view)); - return texture_view; + auto region_view = new TextureRegionView(); + region_view->region = region; + region_view->view = view; + region_view->swizzle = swizzle; + region->views.push_back(std::unique_ptr<TextureRegionView>(region_view)); + return region_view; } return nullptr; @@ -845,16 +972,16 @@ void TextureSwap(Endian endianness, void* dest, const void* src, } } -void TextureCache::FlushPendingCommands(VkCommandBuffer command_buffer, +void TextureCache::FlushPendingCommands(VkCommandBuffer setup_buffer, VkFence completion_fence) { - auto status = vkEndCommandBuffer(command_buffer); + auto status = vkEndCommandBuffer(setup_buffer); CheckResult(status, "vkEndCommandBuffer"); VkSubmitInfo submit_info; std::memset(&submit_info, 0, sizeof(submit_info)); submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.commandBufferCount = 1; - submit_info.pCommandBuffers = &command_buffer; + submit_info.pCommandBuffers = &setup_buffer; if (device_queue_) { auto status = @@ -873,12 +1000,12 @@ void TextureCache::FlushPendingCommands(VkCommandBuffer command_buffer, vkResetFences(*device_, 1, &completion_fence); // Reset the command buffer and put it back into the recording state. - vkResetCommandBuffer(command_buffer, 0); + vkResetCommandBuffer(setup_buffer, 0); VkCommandBufferBeginInfo begin_info; std::memset(&begin_info, 0, sizeof(begin_info)); begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - vkBeginCommandBuffer(command_buffer, &begin_info); + vkBeginCommandBuffer(setup_buffer, &begin_info); } bool TextureCache::ConvertTexture2D(uint8_t* dest, @@ -1070,6 +1197,14 @@ bool TextureCache::UploadTexture(VkCommandBuffer command_buffer, src.guest_address, src.input_length, src.format_info()->name); } + // Invalidate contents of all regions for this texture, except for the base + // region. + for (auto region_it = dest->regions.begin(); region_it != dest->regions.end(); + ++region_it) { + (*region_it)->region_contents_valid = false; + } + dest->base_region->region_contents_valid = true; + // Upload texture into GPU memory. // TODO: If the GPU supports it, we can submit a compute batch to convert the // texture and copy it to its destination. Otherwise, fallback to conversion @@ -1103,11 +1238,11 @@ bool TextureCache::UploadTexture(VkCommandBuffer command_buffer, barrier.pNext = nullptr; barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.oldLayout = dest->image_layout; + barrier.oldLayout = dest->base_region->image_layout; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = dest->image; + barrier.image = dest->base_region->image; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, src.mip_levels, copy_regions[0].imageSubresource.baseArrayLayer, copy_regions[0].imageSubresource.layerCount}; @@ -1131,8 +1266,9 @@ bool TextureCache::UploadTexture(VkCommandBuffer command_buffer, copy_regions[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; } vkCmdCopyBufferToImage(command_buffer, staging_buffer_.gpu_buffer(), - dest->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - src.mip_levels, copy_regions.data()); + dest->base_region->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, src.mip_levels, + copy_regions.data()); // Now transition the texture into a shader readonly source. barrier.srcAccessMask = barrier.dstAccessMask; @@ -1144,7 +1280,7 @@ bool TextureCache::UploadTexture(VkCommandBuffer command_buffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - dest->image_layout = barrier.newLayout; + dest->base_region->image_layout = barrier.newLayout; return true; } @@ -1178,7 +1314,8 @@ bool TextureCache::ComputeTextureStorage(size_t* output_length, void TextureCache::WritebackTexture(Texture* texture) { VkResult status = VK_SUCCESS; VkFence fence = wb_command_pool_->BeginBatch(); - auto alloc = wb_staging_buffer_.Acquire(texture->alloc_info.size, fence); + auto alloc = wb_staging_buffer_.Acquire( + texture->base_region->allocation_info.size, fence); if (!alloc) { wb_command_pool_->EndBatch(); return; @@ -1205,7 +1342,7 @@ void TextureCache::WritebackTexture(Texture* texture) { {texture->texture_info.width + 1, texture->texture_info.height + 1, 1}, }; - vkCmdCopyImageToBuffer(command_buffer, texture->image, + vkCmdCopyImageToBuffer(command_buffer, texture->base_region->image, VK_IMAGE_LAYOUT_GENERAL, wb_staging_buffer_.gpu_buffer(), 1, ®ion); @@ -1269,7 +1406,8 @@ void TextureCache::HashTextureBindings( } VkDescriptorSet TextureCache::PrepareTextureSet( - VkCommandBuffer command_buffer, VkFence completion_fence, + VkCommandBuffer command_buffer, VkCommandBuffer setup_buffer, + VkFence completion_fence, const std::vector<Shader::TextureBinding>& vertex_bindings, const std::vector<Shader::TextureBinding>& pixel_bindings) { XXH64_state_t hash_state; @@ -1296,12 +1434,14 @@ VkDescriptorSet TextureCache::PrepareTextureSet( // This does things lazily and de-dupes fetch constants reused in both // shaders. bool any_failed = false; - any_failed = !SetupTextureBindings(command_buffer, completion_fence, - update_set_info, vertex_bindings) || - any_failed; - any_failed = !SetupTextureBindings(command_buffer, completion_fence, - update_set_info, pixel_bindings) || - any_failed; + any_failed = + !SetupTextureBindings(command_buffer, setup_buffer, completion_fence, + update_set_info, vertex_bindings) || + any_failed; + any_failed = + !SetupTextureBindings(command_buffer, setup_buffer, completion_fence, + update_set_info, pixel_bindings) || + any_failed; if (any_failed) { XELOGW("Failed to setup one or more texture bindings"); // TODO(benvanik): actually bail out here? @@ -1333,17 +1473,18 @@ VkDescriptorSet TextureCache::PrepareTextureSet( } bool TextureCache::SetupTextureBindings( - VkCommandBuffer command_buffer, VkFence completion_fence, - UpdateSetInfo* update_set_info, + VkCommandBuffer command_buffer, VkCommandBuffer setup_buffer, + VkFence completion_fence, UpdateSetInfo* update_set_info, const std::vector<Shader::TextureBinding>& bindings) { bool any_failed = false; for (auto& binding : bindings) { uint32_t fetch_bit = 1 << binding.fetch_constant; if ((update_set_info->has_setup_fetch_mask & fetch_bit) == 0) { // Needs setup. - any_failed = !SetupTextureBinding(command_buffer, completion_fence, - update_set_info, binding) || - any_failed; + any_failed = + !SetupTextureBinding(command_buffer, setup_buffer, completion_fence, + update_set_info, binding) || + any_failed; update_set_info->has_setup_fetch_mask |= fetch_bit; } } @@ -1351,6 +1492,7 @@ bool TextureCache::SetupTextureBindings( } bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer, + VkCommandBuffer setup_buffer, VkFence completion_fence, UpdateSetInfo* update_set_info, const Shader::TextureBinding& binding) { @@ -1384,14 +1526,15 @@ bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer, // Search via the base format. texture_info.texture_format = GetBaseFormat(texture_info.texture_format); - auto texture = Demand(texture_info, command_buffer, completion_fence); + auto texture_region = DemandRegion(texture_info, command_buffer, setup_buffer, + completion_fence); auto sampler = Demand(sampler_info); - if (texture == nullptr || sampler == nullptr) { + if (texture_region == nullptr || sampler == nullptr) { return false; } uint16_t swizzle = static_cast<uint16_t>(fetch.swizzle); - auto view = DemandView(texture, swizzle); + auto view = DemandTextureRegionView(texture_region, swizzle); auto image_info = &update_set_info->image_infos[update_set_info->image_write_count]; @@ -1414,9 +1557,9 @@ bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer, image_write->pTexelBufferView = nullptr; image_info->imageView = view->view; - image_info->imageLayout = texture->image_layout; + image_info->imageLayout = texture_region->image_layout; image_info->sampler = sampler->sampler; - texture->in_flight_fence = completion_fence; + texture_region->texture->in_flight_fence = completion_fence; return true; } diff --git a/src/xenia/gpu/vulkan/texture_cache.h b/src/xenia/gpu/vulkan/texture_cache.h index ab529333d..01716b864 100644 --- a/src/xenia/gpu/vulkan/texture_cache.h +++ b/src/xenia/gpu/vulkan/texture_cache.h @@ -34,30 +34,11 @@ namespace vulkan { // class TextureCache { public: - struct TextureView; + struct TextureRegion; + struct Texture; - // This represents an uploaded Vulkan texture. - struct Texture { - TextureInfo texture_info; - std::vector<std::unique_ptr<TextureView>> views; - - VkFormat format; - VkImage image; - VkImageLayout image_layout; - VmaAllocation alloc; - VmaAllocationInfo alloc_info; - VkFramebuffer framebuffer; // Blit target frame buffer. - VkImageUsageFlags usage_flags; - - uintptr_t access_watch_handle; - bool pending_invalidation; - - // Pointer to the latest usage fence. - VkFence in_flight_fence; - }; - - struct TextureView { - Texture* texture; + struct TextureRegionView { + TextureRegion* region; VkImageView view; union { @@ -74,6 +55,42 @@ class TextureCache { }; }; + struct TextureRegion { + Texture* texture; + + std::vector<std::unique_ptr<TextureRegionView>> views; + + VkOffset3D region_offset; + VkExtent3D region_size; + + VkImage image; + VkImageLayout image_layout; + VkImageUsageFlags usage_flags; + VmaAllocation allocation; + VmaAllocationInfo allocation_info; + + bool region_contents_valid; + }; + + // This represents an uploaded Vulkan texture. A texture has a base image + // region containing its full content area, and zero or more regions + // that are crops of that base region. + struct Texture { + TextureInfo texture_info; + VkFormat format; + + std::vector<std::unique_ptr<TextureRegion>> regions; + + TextureRegion* base_region; // Base region representing the entire image. + VkFramebuffer framebuffer; // Blit target frame buffer. + + uintptr_t access_watch_handle; + bool pending_invalidation; + + // Pointer to the latest usage fence. + VkFence in_flight_fence; + }; + TextureCache(Memory* memory, RegisterFile* register_file, TraceWriter* trace_writer, ui::vulkan::VulkanDevice* device); ~TextureCache(); @@ -91,8 +108,12 @@ class TextureCache { // bindings. The textures will be uploaded/converted/etc as needed. // Requires a fence to be provided that will be signaled when finished // using the returned descriptor set. + // The setup buffer may be flushed to the device if we run out of space. + // The command buffer may be transitioned out of a render pass if an + // upload is performed to fill a dirty texture region. VkDescriptorSet PrepareTextureSet( - VkCommandBuffer setup_command_buffer, VkFence completion_fence, + VkCommandBuffer command_buffer, VkCommandBuffer setup_buffer, + VkFence completion_fence, const std::vector<Shader::TextureBinding>& vertex_bindings, const std::vector<Shader::TextureBinding>& pixel_bindings); @@ -109,7 +130,7 @@ class TextureCache { uint32_t height, TextureFormat format, VkOffset2D* out_offset = nullptr); - TextureView* DemandView(Texture* texture, uint16_t swizzle); + TextureRegionView* DemandTextureRegionView(TextureRegion*, uint16_t swizzle); // Demands a texture for the purpose of resolving from EDRAM. This either // creates a new texture or returns a previously created texture. @@ -132,21 +153,24 @@ class TextureCache { // Allocates a new texture and memory to back it on the GPU. Texture* AllocateTexture(const TextureInfo& texture_info, - VkFormatFeatureFlags required_flags = - VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); + VkFormatFeatureFlags required_flags); bool FreeTexture(Texture* texture); static void WatchCallback(void* context_ptr, void* data_ptr, uint32_t address); + TextureRegion* AllocateTextureRegion(Texture*, VkOffset3D region_offset, + VkExtent3D region_size, + VkFormatFeatureFlags required_flags); // Demands a texture. If command_buffer is null and the texture hasn't been // uploaded to graphics memory already, we will return null and bail. - Texture* Demand(const TextureInfo& texture_info, - VkCommandBuffer command_buffer = nullptr, - VkFence completion_fence = nullptr); + TextureRegion* DemandRegion(const TextureInfo& texture_info, + VkCommandBuffer command_buffer, + VkCommandBuffer setup_buffer, + VkFence completion_fence = nullptr); Sampler* Demand(const SamplerInfo& sampler_info); - void FlushPendingCommands(VkCommandBuffer command_buffer, + void FlushPendingCommands(VkCommandBuffer setup_buffer, VkFence completion_fence); bool ConvertTexture2D(uint8_t* dest, VkBufferImageCopy* copy_region, @@ -164,16 +188,17 @@ class TextureCache { // Queues commands to upload a texture from system memory, applying any // conversions necessary. This may flush the command buffer to the GPU if we // run out of staging memory. - bool UploadTexture(VkCommandBuffer command_buffer, VkFence completion_fence, + bool UploadTexture(VkCommandBuffer setup_buffer, VkFence completion_fence, Texture* dest, const TextureInfo& src); void HashTextureBindings(XXH64_state_t* hash_state, uint32_t& fetch_mask, const std::vector<Shader::TextureBinding>& bindings); bool SetupTextureBindings( - VkCommandBuffer command_buffer, VkFence completion_fence, - UpdateSetInfo* update_set_info, + VkCommandBuffer command_buffer, VkCommandBuffer setup_buffer, + VkFence completion_fence, UpdateSetInfo* update_set_info, const std::vector<Shader::TextureBinding>& bindings); bool SetupTextureBinding(VkCommandBuffer command_buffer, + VkCommandBuffer setup_buffer, VkFence completion_fence, UpdateSetInfo* update_set_info, const Shader::TextureBinding& binding); diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 2dbed49b5..335a312d6 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -436,11 +436,11 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.oldLayout = texture->image_layout; - barrier.newLayout = texture->image_layout; + barrier.oldLayout = texture->base_region->image_layout; + barrier.newLayout = texture->base_region->image_layout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = texture->image; + barrier.image = texture->base_region->image; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; vkCmdPipelineBarrier(copy_commands, @@ -473,7 +473,9 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, blitter_->BlitTexture2D( copy_commands, current_batch_fence_, - texture_cache_->DemandView(texture, 0x688)->view, src_rect, + texture_cache_->DemandTextureRegionView(texture->base_region, 0x688) + ->view, + src_rect, {texture->texture_info.width + 1, texture->texture_info.height + 1}, VK_FORMAT_R8G8B8A8_UNORM, dst_rect, {frontbuffer_width, frontbuffer_height}, fb_framebuffer_, viewport, @@ -825,7 +827,8 @@ bool VulkanCommandProcessor::PopulateSamplers(VkCommandBuffer command_buffer, std::vector<xe::gpu::Shader::TextureBinding> dummy_bindings; auto descriptor_set = texture_cache_->PrepareTextureSet( - setup_buffer, current_batch_fence_, vertex_shader->texture_bindings(), + command_buffer, setup_buffer, current_batch_fence_, + vertex_shader->texture_bindings(), pixel_shader ? pixel_shader->texture_bindings() : dummy_bindings); if (!descriptor_set) { // Unable to bind set. @@ -1022,8 +1025,9 @@ bool VulkanCommandProcessor::IssueCopy() { return false; } - if (!(texture->usage_flags & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT))) { + if (!(texture->base_region->usage_flags & + (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT))) { // Resolve image doesn't support drawing, and we don't support conversion. return false; } @@ -1042,7 +1046,15 @@ bool VulkanCommandProcessor::IssueCopy() { } auto command_buffer = current_command_buffer_; - if (texture->image_layout == VK_IMAGE_LAYOUT_UNDEFINED) { + // Mark all regions of the texture (except base region, which we will write + // to) as invalid + for (auto region_it = texture->regions.begin(); + region_it != texture->regions.end(); ++region_it) { + (*region_it)->region_contents_valid = false; + } + texture->base_region->region_contents_valid = true; + + if (texture->base_region->image_layout == VK_IMAGE_LAYOUT_UNDEFINED) { // Transition the image to a general layout. VkImageMemoryBarrier image_barrier; image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -1053,13 +1065,13 @@ bool VulkanCommandProcessor::IssueCopy() { image_barrier.dstAccessMask = 0; image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - image_barrier.image = texture->image; + image_barrier.image = texture->base_region->image; image_barrier.subresourceRange = {0, 0, 1, 0, 1}; image_barrier.subresourceRange.aspectMask = is_color_source ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - texture->image_layout = VK_IMAGE_LAYOUT_GENERAL; + texture->base_region->image_layout = VK_IMAGE_LAYOUT_GENERAL; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, @@ -1077,11 +1089,11 @@ bool VulkanCommandProcessor::IssueCopy() { image_barrier.dstAccessMask = is_color_source ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - image_barrier.oldLayout = texture->image_layout; + image_barrier.oldLayout = texture->base_region->image_layout; image_barrier.newLayout = is_color_source ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - image_barrier.image = texture->image; + image_barrier.image = texture->base_region->image; image_barrier.subresourceRange = {0, 0, 1, 0, 1}; image_barrier.subresourceRange.aspectMask = is_color_source ? VK_IMAGE_ASPECT_COLOR_BIT @@ -1155,7 +1167,8 @@ bool VulkanCommandProcessor::IssueCopy() { // Create a framebuffer containing our image. if (!texture->framebuffer) { - auto texture_view = texture_cache_->DemandView(texture, 0x688); + auto texture_view = texture_cache_->DemandTextureRegionView( + texture->base_region, 0x688); VkFramebufferCreateInfo fb_create_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, |
