summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/xenia/gpu/vulkan/render_cache.cc5
-rw-r--r--src/xenia/gpu/vulkan/render_cache.h1
-rw-r--r--src/xenia/gpu/vulkan/texture_cache.cc355
-rw-r--r--src/xenia/gpu/vulkan/texture_cache.h93
-rw-r--r--src/xenia/gpu/vulkan/vulkan_command_processor.cc39
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 = &current_state_.config;
CachedRenderPass* render_pass = nullptr;
CachedFramebuffer* framebuffer = nullptr;
@@ -678,6 +680,7 @@ const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer,
SetShadowRegister(&regs.rb_color3_info.value, XE_GPU_REG_RB_COLOR3_INFO);
dirty |=
SetShadowRegister(&regs.rb_depth_info.value, XE_GPU_REG_RB_DEPTH_INFO);
+ dirty |= SetShadowRegister(&regs.rb_color_mask, XE_GPU_REG_RB_COLOR_MASK);
dirty |= SetShadowRegister(&regs.pa_sc_window_scissor_tl,
XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL);
dirty |= SetShadowRegister(&regs.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, &copy_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, &region);
@@ -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,