/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/vulkan/render_cache.h" #include #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/memory.h" #include "xenia/base/profiling.h" #include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/registers.h" #include "xenia/gpu/vulkan/vulkan_gpu_flags.h" namespace xe { namespace gpu { namespace vulkan { using namespace xe::gpu::xenos; using xe::ui::vulkan::CheckResult; constexpr uint32_t kEdramBufferCapacity = 10 * 1024 * 1024; xenos::ColorRenderTargetFormat GetBaseRTFormat( xenos::ColorRenderTargetFormat format) { switch (format) { case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: return xenos::ColorRenderTargetFormat::k_8_8_8_8; case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: return xenos::ColorRenderTargetFormat::k_2_10_10_10; case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: return xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT; default: return format; } } VkFormat ColorRenderTargetFormatToVkFormat( xenos::ColorRenderTargetFormat format) { switch (format) { case xenos::ColorRenderTargetFormat::k_8_8_8_8: case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: return VK_FORMAT_R8G8B8A8_UNORM; case xenos::ColorRenderTargetFormat::k_2_10_10_10: case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: return VK_FORMAT_A2R10G10B10_UNORM_PACK32; case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT: case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: return VK_FORMAT_R16G16B16A16_SFLOAT; case xenos::ColorRenderTargetFormat::k_16_16: return VK_FORMAT_R16G16_UNORM; case xenos::ColorRenderTargetFormat::k_16_16_16_16: return VK_FORMAT_R16G16B16A16_UNORM; case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: return VK_FORMAT_R16G16_SFLOAT; case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: return VK_FORMAT_R16G16B16A16_SFLOAT; case xenos::ColorRenderTargetFormat::k_32_FLOAT: return VK_FORMAT_R32_SFLOAT; case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: return VK_FORMAT_R32G32_SFLOAT; default: assert_unhandled_case(key.edram_format); return VK_FORMAT_UNDEFINED; } } VkFormat DepthRenderTargetFormatToVkFormat( xenos::DepthRenderTargetFormat format) { switch (format) { case xenos::DepthRenderTargetFormat::kD24S8: return VK_FORMAT_D24_UNORM_S8_UINT; case xenos::DepthRenderTargetFormat::kD24FS8: // Vulkan doesn't support 24-bit floats, so just promote it to 32-bit return VK_FORMAT_D32_SFLOAT_S8_UINT; default: return VK_FORMAT_UNDEFINED; } } // Cached framebuffer referencing tile attachments. // Each framebuffer is specific to a render pass. Ugh. class CachedFramebuffer { public: // TODO(benvanik): optimized key? tile base + format for each? // Framebuffer with the attachments ready for use in the parent render pass. VkFramebuffer handle = nullptr; // Width of the framebuffer in pixels. uint32_t width = 0; // Height of the framebuffer in pixels. uint32_t height = 0; // References to color attachments, if used. CachedTileView* color_attachments[4] = {nullptr}; // Reference to depth/stencil attachment, if used. CachedTileView* depth_stencil_attachment = nullptr; // Associated render pass VkRenderPass render_pass = nullptr; CachedFramebuffer(VkDevice device, VkRenderPass render_pass, uint32_t surface_width, uint32_t surface_height, CachedTileView* target_color_attachments[4], CachedTileView* target_depth_stencil_attachment); ~CachedFramebuffer(); VkResult Initialize(); bool IsCompatible(const RenderConfiguration& desired_config) const; private: VkDevice device_ = nullptr; }; // Cached render passes based on register states. // Each render pass is dependent on the format, dimensions, and use of // all attachments. The same render pass can be reused for multiple // framebuffers pointing at various tile views, though those cached // framebuffers are specific to the render pass. class CachedRenderPass { public: // Configuration this pass was created with. RenderConfiguration config; // Initialized render pass for the register state. VkRenderPass handle = nullptr; // Cache of framebuffers for the various tile attachments. std::vector cached_framebuffers; CachedRenderPass(VkDevice device, const RenderConfiguration& desired_config); ~CachedRenderPass(); VkResult Initialize(); bool IsCompatible(const RenderConfiguration& desired_config) const; private: VkDevice device_ = nullptr; }; CachedTileView::CachedTileView(ui::vulkan::VulkanDevice* device, VkDeviceMemory edram_memory, TileViewKey view_key) : device_(device), key(std::move(view_key)) {} CachedTileView::~CachedTileView() { VK_SAFE_DESTROY(vkDestroyImageView, *device_, image_view, nullptr); VK_SAFE_DESTROY(vkDestroyImageView, *device_, image_view_depth, nullptr); VK_SAFE_DESTROY(vkDestroyImageView, *device_, image_view_stencil, nullptr); VK_SAFE_DESTROY(vkDestroyImage, *device_, image, nullptr); VK_SAFE_DESTROY(vkFreeMemory, *device_, memory, nullptr); } VkResult CachedTileView::Initialize(VkCommandBuffer command_buffer) { VkResult status = VK_SUCCESS; // Map format to Vulkan. VkFormat vulkan_format = VK_FORMAT_UNDEFINED; uint32_t bpp = 4; if (key.color_or_depth) { auto edram_format = static_cast(key.edram_format); vulkan_format = ColorRenderTargetFormatToVkFormat(edram_format); switch (edram_format) { case xenos::ColorRenderTargetFormat::k_16_16_16_16: case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: bpp = 8; break; default: bpp = 4; break; } } else { auto edram_format = static_cast(key.edram_format); vulkan_format = DepthRenderTargetFormatToVkFormat(edram_format); } assert_true(vulkan_format != VK_FORMAT_UNDEFINED); // FIXME(DrChat): Was this check necessary? // assert_true(bpp == 4); // Create the image with the desired properties. VkImageCreateInfo image_info; image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; image_info.pNext = nullptr; // TODO(benvanik): exploit VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT so we can have // multiple views. image_info.flags = 0; image_info.imageType = VK_IMAGE_TYPE_2D; image_info.format = vulkan_format; image_info.extent.width = key.tile_width * 80; image_info.extent.height = key.tile_height * 16; image_info.extent.depth = 1; image_info.mipLevels = 1; image_info.arrayLayers = 1; if (cvars::vulkan_native_msaa) { auto msaa_samples = static_cast(key.msaa_samples); switch (msaa_samples) { case xenos::MsaaSamples::k1X: image_info.samples = VK_SAMPLE_COUNT_1_BIT; break; case xenos::MsaaSamples::k2X: image_info.samples = VK_SAMPLE_COUNT_2_BIT; break; case xenos::MsaaSamples::k4X: image_info.samples = VK_SAMPLE_COUNT_4_BIT; break; default: assert_unhandled_case(msaa_samples); } } else { image_info.samples = VK_SAMPLE_COUNT_1_BIT; } sample_count = image_info.samples; image_info.tiling = VK_IMAGE_TILING_OPTIMAL; image_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; image_info.usage |= key.color_or_depth ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; image_info.queueFamilyIndexCount = 0; image_info.pQueueFamilyIndices = nullptr; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; status = vkCreateImage(*device_, &image_info, nullptr, &image); if (status != VK_SUCCESS) { return status; } device_->DbgSetObjectName( reinterpret_cast(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, fmt::format("RT(d): 0x{:08X} 0x{:08X}({}) 0x{:08X}({}) {} {} {}", uint32_t(key.tile_offset), uint32_t(key.tile_width), uint32_t(key.tile_width), uint32_t(key.tile_height), uint32_t(key.tile_height), uint32_t(key.color_or_depth), uint32_t(key.msaa_samples), uint32_t(key.edram_format))); VkMemoryRequirements memory_requirements; vkGetImageMemoryRequirements(*device_, image, &memory_requirements); // Bind to a newly allocated chunk. // TODO: Alias from a really big buffer? memory = device_->AllocateMemory(memory_requirements, 0); status = vkBindImageMemory(*device_, image, memory, 0); if (status != VK_SUCCESS) { return status; } // Create the image view we'll use to attach it to a framebuffer. VkImageViewCreateInfo image_view_info; image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; image_view_info.pNext = nullptr; image_view_info.flags = 0; image_view_info.image = image; image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; image_view_info.format = image_info.format; // TODO(benvanik): manipulate? may not be able to when attached. image_view_info.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A, }; image_view_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; if (key.color_or_depth) { image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } else { image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; } status = vkCreateImageView(*device_, &image_view_info, nullptr, &image_view); if (status != VK_SUCCESS) { return status; } // Create separate depth/stencil views. if (key.color_or_depth == 0) { image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; status = vkCreateImageView(*device_, &image_view_info, nullptr, &image_view_depth); if (status != VK_SUCCESS) { return status; } image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; status = vkCreateImageView(*device_, &image_view_info, nullptr, &image_view_stencil); if (status != VK_SUCCESS) { return status; } } // TODO(benvanik): transition to general layout? VkImageMemoryBarrier image_barrier; image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; image_barrier.pNext = nullptr; image_barrier.srcAccessMask = 0; image_barrier.dstAccessMask = key.color_or_depth ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; image_barrier.image = image; image_barrier.subresourceRange.aspectMask = key.color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; image_barrier.subresourceRange.baseMipLevel = 0; image_barrier.subresourceRange.levelCount = 1; image_barrier.subresourceRange.baseArrayLayer = 0; image_barrier.subresourceRange.layerCount = 1; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, key.color_or_depth ? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT : VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_barrier); image_layout = image_barrier.newLayout; return VK_SUCCESS; } CachedFramebuffer::CachedFramebuffer( VkDevice device, VkRenderPass render_pass, uint32_t surface_width, uint32_t surface_height, CachedTileView* target_color_attachments[4], CachedTileView* target_depth_stencil_attachment) : device_(device), width(surface_width), height(surface_height), depth_stencil_attachment(target_depth_stencil_attachment), render_pass(render_pass) { for (int i = 0; i < 4; ++i) { color_attachments[i] = target_color_attachments[i]; } } CachedFramebuffer::~CachedFramebuffer() { VK_SAFE_DESTROY(vkDestroyFramebuffer, device_, handle, nullptr); } VkResult CachedFramebuffer::Initialize() { // Create framebuffer. VkImageView image_views[5] = {nullptr}; int image_view_count = 0; for (int i = 0; i < 4; ++i) { if (color_attachments[i]) { image_views[image_view_count++] = color_attachments[i]->image_view; } } if (depth_stencil_attachment) { image_views[image_view_count++] = depth_stencil_attachment->image_view; } VkFramebufferCreateInfo framebuffer_info; framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebuffer_info.pNext = nullptr; framebuffer_info.flags = 0; framebuffer_info.renderPass = render_pass; framebuffer_info.attachmentCount = image_view_count; framebuffer_info.pAttachments = image_views; framebuffer_info.width = width; framebuffer_info.height = height; framebuffer_info.layers = 1; return vkCreateFramebuffer(device_, &framebuffer_info, nullptr, &handle); } bool CachedFramebuffer::IsCompatible( const RenderConfiguration& desired_config) const { // We already know all render pass things line up, so let's verify dimensions, // edram offsets, etc. We need an exact match. uint32_t surface_pitch_px = desired_config.surface_msaa != xenos::MsaaSamples::k4X ? desired_config.surface_pitch_px : desired_config.surface_pitch_px * 2; uint32_t surface_height_px = desired_config.surface_msaa == xenos::MsaaSamples::k1X ? desired_config.surface_height_px : desired_config.surface_height_px * 2; surface_pitch_px = std::min(surface_pitch_px, 2560u); surface_height_px = std::min(surface_height_px, 2560u); if (surface_pitch_px != width || surface_height_px != height) { return false; } // TODO(benvanik): separate image views from images in tiles and store in fb? for (int i = 0; i < 4; ++i) { // Ensure the the attachment points to the same tile. if (!color_attachments[i]) { continue; } auto& color_info = color_attachments[i]->key; auto& desired_color_info = desired_config.color[i]; if (color_info.tile_offset != desired_color_info.edram_base || color_info.edram_format != static_cast(desired_color_info.format)) { return false; } } // Ensure depth attachment is correct. if (depth_stencil_attachment && (depth_stencil_attachment->key.tile_offset != desired_config.depth_stencil.edram_base || depth_stencil_attachment->key.edram_format != static_cast(desired_config.depth_stencil.format))) { return false; } return true; } CachedRenderPass::CachedRenderPass(VkDevice device, const RenderConfiguration& desired_config) : device_(device) { std::memcpy(&config, &desired_config, sizeof(config)); } CachedRenderPass::~CachedRenderPass() { for (auto framebuffer : cached_framebuffers) { delete framebuffer; } cached_framebuffers.clear(); VK_SAFE_DESTROY(vkDestroyRenderPass, device_, handle, nullptr); } VkResult CachedRenderPass::Initialize() { VkSampleCountFlagBits sample_count; if (cvars::vulkan_native_msaa) { switch (config.surface_msaa) { case xenos::MsaaSamples::k1X: sample_count = VK_SAMPLE_COUNT_1_BIT; break; case xenos::MsaaSamples::k2X: sample_count = VK_SAMPLE_COUNT_2_BIT; break; case xenos::MsaaSamples::k4X: sample_count = VK_SAMPLE_COUNT_4_BIT; break; default: assert_unhandled_case(config.surface_msaa); break; } } else { sample_count = VK_SAMPLE_COUNT_1_BIT; } // Initialize all attachments to default unused. // As we set layout(location=RT) in shaders we must always provide 4. VkAttachmentDescription attachments[5]; for (int i = 0; i < 4; ++i) { attachments[i].flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT; attachments[i].format = VK_FORMAT_UNDEFINED; attachments[i].samples = sample_count; attachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachments[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachments[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[i].initialLayout = VK_IMAGE_LAYOUT_GENERAL; attachments[i].finalLayout = VK_IMAGE_LAYOUT_GENERAL; } auto& depth_stencil_attachment = attachments[4]; depth_stencil_attachment.flags = 0; depth_stencil_attachment.format = VK_FORMAT_UNDEFINED; depth_stencil_attachment.samples = sample_count; depth_stencil_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; depth_stencil_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; depth_stencil_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; depth_stencil_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; depth_stencil_attachment.initialLayout = VK_IMAGE_LAYOUT_GENERAL; depth_stencil_attachment.finalLayout = VK_IMAGE_LAYOUT_GENERAL; // Configure attachments based on what's enabled. VkAttachmentReference color_attachment_refs[4]; for (int i = 0; i < 4; ++i) { auto& color_config = config.color[i]; // TODO(benvanik): see how loose we can be with these. attachments[i].format = ColorRenderTargetFormatToVkFormat(color_config.format); auto& color_attachment_ref = color_attachment_refs[i]; color_attachment_ref.attachment = i; color_attachment_ref.layout = VK_IMAGE_LAYOUT_GENERAL; } // Configure depth. VkAttachmentReference depth_stencil_attachment_ref; depth_stencil_attachment_ref.layout = VK_IMAGE_LAYOUT_GENERAL; auto& depth_config = config.depth_stencil; depth_stencil_attachment_ref.attachment = 4; depth_stencil_attachment.format = DepthRenderTargetFormatToVkFormat(depth_config.format); // Single subpass that writes to our attachments. // FIXME: "Multiple attachments that alias the same memory must not be used in // a single subpass" // TODO: Input attachment for depth/stencil reads? VkSubpassDescription subpass_info; subpass_info.flags = 0; subpass_info.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass_info.inputAttachmentCount = 0; subpass_info.pInputAttachments = nullptr; subpass_info.colorAttachmentCount = 4; subpass_info.pColorAttachments = color_attachment_refs; subpass_info.pResolveAttachments = nullptr; subpass_info.pDepthStencilAttachment = &depth_stencil_attachment_ref; subpass_info.preserveAttachmentCount = 0; subpass_info.pPreserveAttachments = nullptr; // Create the render pass. VkRenderPassCreateInfo render_pass_info; std::memset(&render_pass_info, 0, sizeof(render_pass_info)); render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; render_pass_info.pNext = nullptr; render_pass_info.flags = 0; render_pass_info.attachmentCount = 5; render_pass_info.pAttachments = attachments; render_pass_info.subpassCount = 1; render_pass_info.pSubpasses = &subpass_info; // Add a dependency on external render passes -> us (MAY_ALIAS bit) VkSubpassDependency dependencies[1]; dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0; dependencies[0].srcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; dependencies[0].dstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; dependencies[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[0].dependencyFlags = 0; render_pass_info.dependencyCount = 1; render_pass_info.pDependencies = dependencies; return vkCreateRenderPass(device_, &render_pass_info, nullptr, &handle); } bool CachedRenderPass::IsCompatible( const RenderConfiguration& desired_config) const { if (config.surface_msaa != desired_config.surface_msaa && cvars::vulkan_native_msaa) { return false; } for (int i = 0; i < 4; ++i) { // TODO(benvanik): allow compatible vulkan formats. if (config.color[i].format != desired_config.color[i].format) { return false; } } if (config.depth_stencil.format != desired_config.depth_stencil.format) { return false; } return true; } RenderCache::RenderCache(RegisterFile* register_file, ui::vulkan::VulkanDevice* device) : register_file_(register_file), device_(device) {} RenderCache::~RenderCache() { Shutdown(); } VkResult RenderCache::Initialize() { VkResult status = VK_SUCCESS; // Create the buffer we'll bind to our memory. VkBufferCreateInfo buffer_info; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.pNext = nullptr; buffer_info.flags = 0; buffer_info.size = kEdramBufferCapacity; buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; buffer_info.queueFamilyIndexCount = 0; buffer_info.pQueueFamilyIndices = nullptr; status = vkCreateBuffer(*device_, &buffer_info, nullptr, &edram_buffer_); CheckResult(status, "vkCreateBuffer"); if (status != VK_SUCCESS) { return status; } // Query requirements for the buffer. // It should be 1:1. VkMemoryRequirements buffer_requirements; vkGetBufferMemoryRequirements(*device_, edram_buffer_, &buffer_requirements); assert_true(buffer_requirements.size == kEdramBufferCapacity); // Allocate EDRAM memory. // TODO(benvanik): do we need it host visible? edram_memory_ = device_->AllocateMemory(buffer_requirements); assert_not_null(edram_memory_); if (!edram_memory_) { return VK_ERROR_INITIALIZATION_FAILED; } // Bind buffer to map our entire memory. status = vkBindBufferMemory(*device_, edram_buffer_, edram_memory_, 0); CheckResult(status, "vkBindBufferMemory"); if (status != VK_SUCCESS) { return status; } if (status == VK_SUCCESS) { // For debugging, upload a grid into the EDRAM buffer. uint32_t* gpu_data = nullptr; status = vkMapMemory(*device_, edram_memory_, 0, buffer_requirements.size, 0, reinterpret_cast(&gpu_data)); if (status == VK_SUCCESS) { for (int i = 0; i < kEdramBufferCapacity / 4; i++) { gpu_data[i] = (i % 8) >= 4 ? 0xFF0000FF : 0xFFFFFFFF; } vkUnmapMemory(*device_, edram_memory_); } } return VK_SUCCESS; } void RenderCache::Shutdown() { // TODO(benvanik): wait for idle. // Dispose all render passes (and their framebuffers). for (auto render_pass : cached_render_passes_) { delete render_pass; } cached_render_passes_.clear(); // Dispose all of our cached tile views. for (auto tile_view : cached_tile_views_) { delete tile_view; } cached_tile_views_.clear(); // Release underlying EDRAM memory. if (edram_buffer_) { vkDestroyBuffer(*device_, edram_buffer_, nullptr); edram_buffer_ = nullptr; } if (edram_memory_) { vkFreeMemory(*device_, edram_memory_, nullptr); edram_memory_ = nullptr; } } bool RenderCache::dirty() const { auto& regs = *register_file_; auto& cur_regs = shadow_registers_; bool dirty = false; dirty |= cur_regs.rb_modecontrol.value != regs[XE_GPU_REG_RB_MODECONTROL].u32; dirty |= cur_regs.rb_surface_info.value != regs[XE_GPU_REG_RB_SURFACE_INFO].u32; dirty |= cur_regs.rb_color_info.value != regs[XE_GPU_REG_RB_COLOR_INFO].u32; dirty |= cur_regs.rb_color1_info.value != regs[XE_GPU_REG_RB_COLOR1_INFO].u32; 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.pa_sc_window_scissor_tl != regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL].u32; dirty |= cur_regs.pa_sc_window_scissor_br != regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_BR].u32; return dirty; } const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer, VulkanShader* vertex_shader, VulkanShader* pixel_shader) { #if FINE_GRAINED_DRAW_SCOPES SCOPE_profile_cpu_f("gpu"); #endif // FINE_GRAINED_DRAW_SCOPES assert_null(current_command_buffer_); current_command_buffer_ = command_buffer; // Lookup or construct a render pass compatible with our current state. auto config = ¤t_state_.config; CachedRenderPass* render_pass = nullptr; CachedFramebuffer* framebuffer = nullptr; auto& regs = shadow_registers_; bool dirty = false; dirty |= SetShadowRegister(®s.rb_modecontrol.value, XE_GPU_REG_RB_MODECONTROL); dirty |= SetShadowRegister(®s.rb_surface_info.value, XE_GPU_REG_RB_SURFACE_INFO); dirty |= SetShadowRegister(®s.rb_color_info.value, XE_GPU_REG_RB_COLOR_INFO); dirty |= SetShadowRegister(®s.rb_color1_info.value, XE_GPU_REG_RB_COLOR1_INFO); dirty |= SetShadowRegister(®s.rb_color2_info.value, XE_GPU_REG_RB_COLOR2_INFO); dirty |= 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.pa_sc_window_scissor_tl, XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL); dirty |= SetShadowRegister(®s.pa_sc_window_scissor_br, XE_GPU_REG_PA_SC_WINDOW_SCISSOR_BR); if (!dirty && current_state_.render_pass) { // No registers have changed so we can reuse the previous render pass - // just begin with what we had. render_pass = current_state_.render_pass; framebuffer = current_state_.framebuffer; } else { // Re-parse configuration. if (!ParseConfiguration(config)) { return nullptr; } // Lookup or generate a new render pass and framebuffer for the new state. if (!ConfigureRenderPass(command_buffer, config, &render_pass, &framebuffer)) { return nullptr; } current_state_.render_pass = render_pass; current_state_.render_pass_handle = render_pass->handle; current_state_.framebuffer = framebuffer; current_state_.framebuffer_handle = framebuffer->handle; // TODO(DrChat): Determine if we actually need an EDRAM buffer. /* // Depth auto depth_target = current_state_.framebuffer->depth_stencil_attachment; if (depth_target && current_state_.config.depth_stencil.used) { UpdateTileView(command_buffer, depth_target, true); } // Color for (int i = 0; i < 4; i++) { auto target = current_state_.framebuffer->color_attachments[i]; if (!target || !current_state_.config.color[i].used) { continue; } UpdateTileView(command_buffer, target, true); } */ } if (!render_pass) { return nullptr; } // Setup render pass in command buffer. // This is meant to preserve previous contents as we may be called // repeatedly. VkRenderPassBeginInfo render_pass_begin_info; render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; render_pass_begin_info.pNext = nullptr; render_pass_begin_info.renderPass = render_pass->handle; render_pass_begin_info.framebuffer = framebuffer->handle; // Render into the entire buffer (or at least tell the API we are doing // this). In theory it'd be better to clip this to the scissor region, but // the docs warn anything but the full framebuffer may be slow. render_pass_begin_info.renderArea.offset.x = 0; render_pass_begin_info.renderArea.offset.y = 0; render_pass_begin_info.renderArea.extent.width = config->surface_pitch_px; render_pass_begin_info.renderArea.extent.height = config->surface_height_px; if (config->surface_msaa == xenos::MsaaSamples::k2X) { render_pass_begin_info.renderArea.extent.height = std::min(config->surface_height_px * 2, 2560u); } else if (config->surface_msaa == xenos::MsaaSamples::k4X) { render_pass_begin_info.renderArea.extent.width *= 2; render_pass_begin_info.renderArea.extent.height = std::min(config->surface_height_px * 2, 2560u); } // Configure clear color, if clearing. // TODO(benvanik): enable clearing here during resolve? render_pass_begin_info.clearValueCount = 0; render_pass_begin_info.pClearValues = nullptr; // Begin the render pass. vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); return ¤t_state_; } bool RenderCache::ParseConfiguration(RenderConfiguration* config) { auto& regs = shadow_registers_; // RB_MODECONTROL // Rough mode control (color, color+depth, etc). config->mode_control = regs.rb_modecontrol.edram_mode; // RB_SURFACE_INFO // https://fossies.org/dox/MesaLib-10.3.5/fd2__gmem_8c_source.html config->surface_pitch_px = regs.rb_surface_info.surface_pitch; config->surface_msaa = regs.rb_surface_info.msaa_samples; // TODO(benvanik): verify min/max so we don't go out of bounds. // TODO(benvanik): has to be a good way to get height. // Guess the height from the scissor height. // It's wildly inaccurate, but I've never seen it be bigger than the // EDRAM tiling. /* uint32_t ws_y = (regs.pa_sc_window_scissor_tl >> 16) & 0x7FFF; uint32_t ws_h = ((regs.pa_sc_window_scissor_br >> 16) & 0x7FFF) - ws_y; config->surface_height_px = std::min(2560u, xe::round_up(ws_h, 16)); */ // TODO(DrChat): Find an accurate way to get the surface height. Until we do, // we're going to hardcode it to 2560, as that's the absolute maximum. config->surface_height_px = 2560; // Color attachment configuration. if (config->mode_control == ModeControl::kColorDepth) { reg::RB_COLOR_INFO color_info[4] = { regs.rb_color_info, regs.rb_color1_info, regs.rb_color2_info, regs.rb_color3_info, }; for (int i = 0; i < 4; ++i) { config->color[i].edram_base = color_info[i].color_base; config->color[i].format = GetBaseRTFormat(color_info[i].color_format); } } else { for (int i = 0; i < 4; ++i) { config->color[i].edram_base = 0; config->color[i].format = xenos::ColorRenderTargetFormat::k_8_8_8_8; config->color[i].used = false; } } // Depth/stencil attachment configuration. if (config->mode_control == ModeControl::kColorDepth || 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; } else { config->depth_stencil.edram_base = 0; config->depth_stencil.format = xenos::DepthRenderTargetFormat::kD24S8; config->depth_stencil.used = false; } return true; } bool RenderCache::ConfigureRenderPass(VkCommandBuffer command_buffer, RenderConfiguration* config, CachedRenderPass** out_render_pass, CachedFramebuffer** out_framebuffer) { *out_render_pass = nullptr; *out_framebuffer = nullptr; // TODO(benvanik): better lookup. // Attempt to find the render pass in our cache. CachedRenderPass* render_pass = nullptr; for (auto cached_render_pass : cached_render_passes_) { if (cached_render_pass->IsCompatible(*config)) { // Found a match. render_pass = cached_render_pass; break; } } // If no render pass was found in the cache create a new one. if (!render_pass) { render_pass = new CachedRenderPass(*device_, *config); VkResult status = render_pass->Initialize(); if (status != VK_SUCCESS) { XELOGE("{}: Failed to create render pass, status {}", __func__, ui::vulkan::to_string(status)); delete render_pass; return false; } cached_render_passes_.push_back(render_pass); } // TODO(benvanik): better lookup. // Attempt to find the framebuffer in the render pass cache. CachedFramebuffer* framebuffer = nullptr; for (auto cached_framebuffer : render_pass->cached_framebuffers) { if (cached_framebuffer->IsCompatible(*config)) { // Found a match. framebuffer = cached_framebuffer; break; } } // If no framebuffer was found in the cache create a new one. if (!framebuffer) { uint32_t tile_width = config->surface_msaa == xenos::MsaaSamples::k4X ? 40 : 80; uint32_t tile_height = config->surface_msaa != xenos::MsaaSamples::k1X ? 8 : 16; CachedTileView* target_color_attachments[4] = {nullptr, nullptr, nullptr, nullptr}; for (int i = 0; i < 4; ++i) { TileViewKey color_key; color_key.tile_offset = config->color[i].edram_base; color_key.tile_width = xe::round_up(config->surface_pitch_px, tile_width) / tile_width; // color_key.tile_height = // xe::round_up(config->surface_height_px, tile_height) / tile_height; color_key.tile_height = 160; color_key.color_or_depth = 1; color_key.msaa_samples = 0; // static_cast(config->surface_msaa); color_key.edram_format = static_cast(config->color[i].format); target_color_attachments[i] = FindOrCreateTileView(command_buffer, color_key); if (!target_color_attachments[i]) { XELOGE("Failed to get tile view for color attachment"); return false; } } TileViewKey depth_stencil_key; depth_stencil_key.tile_offset = config->depth_stencil.edram_base; depth_stencil_key.tile_width = xe::round_up(config->surface_pitch_px, tile_width) / tile_width; // depth_stencil_key.tile_height = // xe::round_up(config->surface_height_px, tile_height) / tile_height; depth_stencil_key.tile_height = 160; depth_stencil_key.color_or_depth = 0; depth_stencil_key.msaa_samples = 0; // static_cast(config->surface_msaa); depth_stencil_key.edram_format = static_cast(config->depth_stencil.format); auto target_depth_stencil_attachment = FindOrCreateTileView(command_buffer, depth_stencil_key); if (!target_depth_stencil_attachment) { XELOGE("Failed to get tile view for depth/stencil attachment"); return false; } uint32_t surface_pitch_px = config->surface_msaa != xenos::MsaaSamples::k4X ? config->surface_pitch_px : config->surface_pitch_px * 2; uint32_t surface_height_px = config->surface_msaa == xenos::MsaaSamples::k1X ? config->surface_height_px : config->surface_height_px * 2; surface_pitch_px = std::min(surface_pitch_px, 2560u); surface_height_px = std::min(surface_height_px, 2560u); framebuffer = new CachedFramebuffer( *device_, render_pass->handle, surface_pitch_px, surface_height_px, target_color_attachments, target_depth_stencil_attachment); VkResult status = framebuffer->Initialize(); if (status != VK_SUCCESS) { XELOGE("{}: Failed to create framebuffer, status {}", __func__, ui::vulkan::to_string(status)); delete framebuffer; return false; } render_pass->cached_framebuffers.push_back(framebuffer); } *out_render_pass = render_pass; *out_framebuffer = framebuffer; return true; } CachedTileView* RenderCache::FindTileView(uint32_t base, uint32_t pitch, xenos::MsaaSamples samples, bool color_or_depth, uint32_t format) { uint32_t tile_width = samples == xenos::MsaaSamples::k4X ? 40 : 80; uint32_t tile_height = samples != xenos::MsaaSamples::k1X ? 8 : 16; if (color_or_depth) { // Adjust similar formats for easier matching. format = static_cast( GetBaseRTFormat(static_cast(format))); } TileViewKey key; key.tile_offset = base; key.tile_width = xe::round_up(pitch, tile_width) / tile_width; key.tile_height = 160; key.color_or_depth = color_or_depth ? 1 : 0; key.msaa_samples = 0; key.edram_format = static_cast(format); auto view = FindTileView(key); if (view) { return view; } return nullptr; } CachedTileView* RenderCache::FindOrCreateTileView( VkCommandBuffer command_buffer, const TileViewKey& view_key) { auto tile_view = FindTileView(view_key); if (tile_view) { return tile_view; } // Create a new tile and add to the cache. tile_view = new CachedTileView(device_, edram_memory_, view_key); VkResult status = tile_view->Initialize(command_buffer); if (status != VK_SUCCESS) { XELOGE("{}: Failed to create tile view, status {}", __func__, ui::vulkan::to_string(status)); delete tile_view; return nullptr; } cached_tile_views_.push_back(tile_view); return tile_view; } void RenderCache::UpdateTileView(VkCommandBuffer command_buffer, CachedTileView* view, bool load, bool insert_barrier) { uint32_t tile_width = view->key.msaa_samples == uint16_t(xenos::MsaaSamples::k4X) ? 40 : 80; uint32_t tile_height = view->key.msaa_samples != uint16_t(xenos::MsaaSamples::k1X) ? 8 : 16; if (insert_barrier) { VkBufferMemoryBarrier barrier; barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; barrier.pNext = nullptr; if (load) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; } else { barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; } barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.buffer = edram_buffer_; barrier.offset = view->key.tile_offset * 5120; barrier.size = view->key.tile_width * tile_width * view->key.tile_height * tile_height * view->key.color_or_depth ? 4 : 1; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 1, &barrier, 0, nullptr); } // TODO(DrChat): Stencil copies. VkBufferImageCopy region; region.bufferOffset = view->key.tile_offset * 5120; region.bufferRowLength = 0; region.bufferImageHeight = 0; region.imageSubresource = {0, 0, 0, 1}; region.imageSubresource.aspectMask = view->key.color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; region.imageOffset = {0, 0, 0}; region.imageExtent = {view->key.tile_width * tile_width, view->key.tile_height * tile_height, 1}; if (load) { vkCmdCopyBufferToImage(command_buffer, edram_buffer_, view->image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); } else { vkCmdCopyImageToBuffer(command_buffer, view->image, VK_IMAGE_LAYOUT_GENERAL, edram_buffer_, 1, ®ion); } } CachedTileView* RenderCache::FindTileView(const TileViewKey& view_key) const { // Check the cache. // TODO(benvanik): better lookup. for (auto tile_view : cached_tile_views_) { if (tile_view->IsEqual(view_key)) { return tile_view; } } return nullptr; } void RenderCache::EndRenderPass() { assert_not_null(current_command_buffer_); // End the render pass. vkCmdEndRenderPass(current_command_buffer_); // Copy all render targets back into our EDRAM buffer. // Don't bother waiting on this command to complete, as next render pass may // reuse previous framebuffer attachments. If they need this, they will wait. // TODO: Should we bother re-tiling the images on copy back? // // FIXME: There's a case where we may have a really big render target (as we // can't get the correct height atm) and we may end up overwriting the valid // contents of another render target by mistake! Need to reorder copy commands // to avoid this. // TODO(DrChat): Determine if we actually need an EDRAM buffer. /* std::vector cached_views; // Depth auto depth_target = current_state_.framebuffer->depth_stencil_attachment; if (depth_target && current_state_.config.depth_stencil.used) { cached_views.push_back(depth_target); } // Color for (int i = 0; i < 4; i++) { auto target = current_state_.framebuffer->color_attachments[i]; if (!target || !current_state_.config.color[i].used) { continue; } cached_views.push_back(target); } std::sort( cached_views.begin(), cached_views.end(), [](CachedTileView const* a, CachedTileView const* b) { return *a < *b; }); for (auto view : cached_views) { UpdateTileView(current_command_buffer_, view, false, false); } */ current_command_buffer_ = nullptr; } void RenderCache::ClearCache() { // TODO(benvanik): caching. } void RenderCache::RawCopyToImage(VkCommandBuffer command_buffer, uint32_t edram_base, VkImage image, VkImageLayout image_layout, bool color_or_depth, VkOffset3D offset, VkExtent3D extents) { // Transition the texture into a transfer destination layout. VkImageMemoryBarrier image_barrier; image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; image_barrier.pNext = nullptr; image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; if (image_layout != VK_IMAGE_LAYOUT_GENERAL && image_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { image_barrier.srcAccessMask = 0; image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; image_barrier.oldLayout = image_layout; image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; image_barrier.image = image; image_barrier.subresourceRange = {0, 0, 1, 0, 1}; image_barrier.subresourceRange.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : 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, &image_barrier); } VkBufferMemoryBarrier buffer_barrier; buffer_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; buffer_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; buffer_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; buffer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; buffer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; buffer_barrier.buffer = edram_buffer_; buffer_barrier.offset = edram_base * 5120; // TODO: Calculate this accurately (need texel size) buffer_barrier.size = extents.width * extents.height * 4; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 1, &buffer_barrier, 0, nullptr); // Issue the copy command. // TODO(DrChat): Stencil copies. VkBufferImageCopy region; region.bufferOffset = edram_base * 5120; region.bufferImageHeight = 0; region.bufferRowLength = 0; region.imageOffset = offset; region.imageExtent = extents; region.imageSubresource = {0, 0, 0, 1}; region.imageSubresource.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; vkCmdCopyBufferToImage(command_buffer, edram_buffer_, image, image_layout, 1, ®ion); // Transition the image back into its previous layout. if (image_layout != VK_IMAGE_LAYOUT_GENERAL && image_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { image_barrier.srcAccessMask = image_barrier.dstAccessMask; image_barrier.dstAccessMask = 0; std::swap(image_barrier.oldLayout, image_barrier.newLayout); vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_barrier); } } void RenderCache::BlitToImage(VkCommandBuffer command_buffer, uint32_t edram_base, uint32_t pitch, uint32_t height, xenos::MsaaSamples num_samples, VkImage image, VkImageLayout image_layout, bool color_or_depth, uint32_t format, VkFilter filter, VkOffset3D offset, VkExtent3D extents) { if (color_or_depth) { // Adjust similar formats for easier matching. format = static_cast( GetBaseRTFormat(static_cast(format))); } uint32_t tile_width = num_samples == xenos::MsaaSamples::k4X ? 40 : 80; uint32_t tile_height = num_samples != xenos::MsaaSamples::k1X ? 8 : 16; // Grab a tile view that represents the source image. TileViewKey key; key.color_or_depth = color_or_depth ? 1 : 0; key.msaa_samples = 0; // static_cast(num_samples); key.edram_format = format; key.tile_offset = edram_base; key.tile_width = xe::round_up(pitch, tile_width) / tile_width; // key.tile_height = xe::round_up(height, tile_height) / tile_height; key.tile_height = 160; auto tile_view = FindOrCreateTileView(command_buffer, key); assert_not_null(tile_view); // Update the view with the latest contents. // UpdateTileView(command_buffer, tile_view, true, true); // Put a barrier on the tile view. VkImageMemoryBarrier image_barrier; image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; image_barrier.pNext = nullptr; image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; image_barrier.srcAccessMask = color_or_depth ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; image_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; image_barrier.image = tile_view->image; image_barrier.subresourceRange = {0, 0, 1, 0, 1}; image_barrier.subresourceRange.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_barrier); // If we overflow we'll lose the device here. // assert_true(extents.width <= key.tile_width * tile_width); // assert_true(extents.height <= key.tile_height * tile_height); // Now issue the blit to the destination. if (tile_view->sample_count == VK_SAMPLE_COUNT_1_BIT) { VkImageBlit image_blit; image_blit.srcSubresource = {0, 0, 0, 1}; image_blit.srcSubresource.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; image_blit.srcOffsets[0] = {0, 0, offset.z}; image_blit.srcOffsets[1] = {int32_t(extents.width), int32_t(extents.height), int32_t(extents.depth)}; image_blit.dstSubresource = {0, 0, 0, 1}; image_blit.dstSubresource.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; image_blit.dstOffsets[0] = offset; image_blit.dstOffsets[1] = {offset.x + int32_t(extents.width), offset.y + int32_t(extents.height), offset.z + int32_t(extents.depth)}; vkCmdBlitImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL, image, image_layout, 1, &image_blit, filter); } else { VkImageResolve image_resolve; image_resolve.srcSubresource = {0, 0, 0, 1}; image_resolve.srcSubresource.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; image_resolve.srcOffset = {0, 0, 0}; image_resolve.dstSubresource = {0, 0, 0, 1}; image_resolve.dstSubresource.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; image_resolve.dstOffset = offset; image_resolve.extent = extents; vkCmdResolveImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL, image, image_layout, 1, &image_resolve); } // Add another barrier on the tile view. image_barrier.srcAccessMask = image_barrier.dstAccessMask; image_barrier.dstAccessMask = color_or_depth ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; std::swap(image_barrier.oldLayout, image_barrier.newLayout); vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_barrier); } void RenderCache::ClearEDRAMColor(VkCommandBuffer command_buffer, uint32_t edram_base, xenos::ColorRenderTargetFormat format, uint32_t pitch, uint32_t height, xenos::MsaaSamples num_samples, float* color) { // TODO: For formats <= 4 bpp, we can directly fill the EDRAM buffer. Just // need to detect this and calculate a value. // Adjust similar formats for easier matching. format = GetBaseRTFormat(static_cast(format)); uint32_t tile_width = num_samples == xenos::MsaaSamples::k4X ? 40 : 80; uint32_t tile_height = num_samples != xenos::MsaaSamples::k1X ? 8 : 16; // Grab a tile view (as we need to clear an image first) TileViewKey key; key.color_or_depth = 1; key.msaa_samples = 0; // static_cast(num_samples); key.edram_format = static_cast(format); key.tile_offset = edram_base; key.tile_width = xe::round_up(pitch, tile_width) / tile_width; // key.tile_height = xe::round_up(height, tile_height) / tile_height; key.tile_height = 160; auto tile_view = FindOrCreateTileView(command_buffer, key); assert_not_null(tile_view); VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; VkClearColorValue clear_value; std::memcpy(clear_value.float32, color, sizeof(float) * 4); // Issue a clear command vkCmdClearColorImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &range); // Copy image back into EDRAM buffer // UpdateTileView(command_buffer, tile_view, false, false); } void RenderCache::ClearEDRAMDepthStencil(VkCommandBuffer command_buffer, uint32_t edram_base, xenos::DepthRenderTargetFormat format, uint32_t pitch, uint32_t height, xenos::MsaaSamples num_samples, float depth, uint32_t stencil) { // TODO: For formats <= 4 bpp, we can directly fill the EDRAM buffer. Just // need to detect this and calculate a value. uint32_t tile_width = num_samples == xenos::MsaaSamples::k4X ? 40 : 80; uint32_t tile_height = num_samples != xenos::MsaaSamples::k1X ? 8 : 16; // Grab a tile view (as we need to clear an image first) TileViewKey key; key.color_or_depth = 0; key.msaa_samples = 0; // static_cast(num_samples); key.edram_format = static_cast(format); key.tile_offset = edram_base; key.tile_width = xe::round_up(pitch, tile_width) / tile_width; // key.tile_height = xe::round_up(height, tile_height) / tile_height; key.tile_height = 160; auto tile_view = FindOrCreateTileView(command_buffer, key); assert_not_null(tile_view); VkImageSubresourceRange range = { VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1, }; VkClearDepthStencilValue clear_value; clear_value.depth = depth; clear_value.stencil = stencil; // Issue a clear command vkCmdClearDepthStencilImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &range); // Copy image back into EDRAM buffer // UpdateTileView(command_buffer, tile_view, false, false); } void RenderCache::FillEDRAM(VkCommandBuffer command_buffer, uint32_t value) { vkCmdFillBuffer(command_buffer, edram_buffer_, 0, kEdramBufferCapacity, value); } bool RenderCache::SetShadowRegister(uint32_t* dest, uint32_t register_name) { uint32_t value = register_file_->values[register_name].u32; if (*dest == value) { return false; } *dest = value; return true; } } // namespace vulkan } // namespace gpu } // namespace xe