Implements EVENT_WRITE_ZPD handling across shared CommandProcessor, D3D12, and Vulkan backends. This adds a shared report lifecycle. Reports can span submissions/render passes, split into multiple segments, and asynchronously retire. Reports can also survive same-slot reuse, which is critical for titles that aggressively recycle against a small pool. RTV/FBO use their respective host occlusion queries, whereas ROV/FSI use counter buffers instead, with shader helpers that are called from the translated PS, accumulating surviving MSAA sample coverage into active slots. Regardless of approach, results are copied to small, dedicated readbacks and fed through shared retirement. Fast mode writes speculative values and patches in resolved results later. Strict mode waits for real results with a small retire backstop so it doesn't spin forever when a report gets weird. Fake mode's fundamentals are unchanged, it just now uses XenosZPDReport memory helpers like all the modes do. Also adds optional sample count saturation for instances when attenuation isn't a one-for-one with real hardware, an optional alternate fast behavior that preserves cached zeroes (that can improve flare-heavy titles but might regress occlusion culling), and normalization for upscaling. QueryBatch is still unsupported other than a fake path that's enabled when a range is set via cvar.
5551 lines
243 KiB
C++
5551 lines
243 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
|
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
|
|
#include "xenia/apu/audio_system.h"
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/byte_order.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/base/profiling.h"
|
|
#include "xenia/emulator.h"
|
|
#include "xenia/gpu/draw_util.h"
|
|
#include "xenia/gpu/gpu_flags.h"
|
|
#include "xenia/gpu/packet_disassembler.h"
|
|
#include "xenia/gpu/registers.h"
|
|
#include "xenia/gpu/shader.h"
|
|
#include "xenia/gpu/spirv_shader_translator.h"
|
|
#include "xenia/gpu/vulkan/vulkan_pipeline_cache.h"
|
|
#include "xenia/gpu/vulkan/vulkan_render_target_cache.h"
|
|
#include "xenia/gpu/vulkan/vulkan_shader.h"
|
|
#include "xenia/gpu/vulkan/vulkan_shared_memory.h"
|
|
#include "xenia/gpu/xenos.h"
|
|
#include "xenia/gpu/xenos_zpd_report.h"
|
|
#include "xenia/kernel/kernel_state.h"
|
|
#include "xenia/kernel/user_module.h"
|
|
#include "xenia/ui/vulkan/vulkan_presenter.h"
|
|
#include "xenia/ui/vulkan/vulkan_util.h"
|
|
|
|
DECLARE_bool(clear_memory_page_state);
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
namespace vulkan {
|
|
|
|
// Generated with `xb buildshaders`.
|
|
namespace shaders {
|
|
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/apply_gamma_pwl_fxaa_luma_ps.h"
|
|
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/apply_gamma_pwl_ps.h"
|
|
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/apply_gamma_table_fxaa_luma_ps.h"
|
|
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/apply_gamma_table_ps.h"
|
|
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/fullscreen_cw_vs.h"
|
|
} // namespace shaders
|
|
|
|
constexpr VkDescriptorPoolSize
|
|
VulkanCommandProcessor::kDescriptorPoolSizeUniformBuffer = {
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
|
SpirvShaderTranslator::kConstantBufferCount*
|
|
kLinkedTypeDescriptorPoolSetCount};
|
|
|
|
constexpr VkDescriptorPoolSize
|
|
VulkanCommandProcessor::kDescriptorPoolSizeStorageBuffer = {
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, kLinkedTypeDescriptorPoolSetCount};
|
|
|
|
// 2x descriptors for texture images because of unsigned and signed bindings.
|
|
constexpr VkDescriptorPoolSize
|
|
VulkanCommandProcessor::kDescriptorPoolSizeTextures[2] = {
|
|
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
|
2 * kLinkedTypeDescriptorPoolSetCount},
|
|
{VK_DESCRIPTOR_TYPE_SAMPLER, kLinkedTypeDescriptorPoolSetCount},
|
|
};
|
|
|
|
VulkanCommandProcessor::VulkanCommandProcessor(
|
|
VulkanGraphicsSystem* graphics_system, kernel::KernelState* kernel_state)
|
|
: CommandProcessor(graphics_system, kernel_state),
|
|
completion_timeline_(static_cast<const ui::vulkan::VulkanProvider*>(
|
|
graphics_system->provider())
|
|
->vulkan_device()),
|
|
deferred_command_buffer_(*this),
|
|
transient_descriptor_allocator_uniform_buffer_(
|
|
static_cast<const ui::vulkan::VulkanProvider*>(
|
|
graphics_system->provider())
|
|
->vulkan_device(),
|
|
&kDescriptorPoolSizeUniformBuffer, 1,
|
|
kLinkedTypeDescriptorPoolSetCount),
|
|
transient_descriptor_allocator_storage_buffer_(
|
|
static_cast<const ui::vulkan::VulkanProvider*>(
|
|
graphics_system->provider())
|
|
->vulkan_device(),
|
|
&kDescriptorPoolSizeStorageBuffer, 1,
|
|
kLinkedTypeDescriptorPoolSetCount),
|
|
transient_descriptor_allocator_textures_(
|
|
static_cast<const ui::vulkan::VulkanProvider*>(
|
|
graphics_system->provider())
|
|
->vulkan_device(),
|
|
kDescriptorPoolSizeTextures,
|
|
uint32_t(xe::countof(kDescriptorPoolSizeTextures)),
|
|
kLinkedTypeDescriptorPoolSetCount) {}
|
|
|
|
VulkanCommandProcessor::~VulkanCommandProcessor() = default;
|
|
|
|
void VulkanCommandProcessor::ClearCaches() {
|
|
CommandProcessor::ClearCaches();
|
|
cache_clear_requested_ = true;
|
|
}
|
|
|
|
void VulkanCommandProcessor::TracePlaybackWroteMemory(uint32_t base_ptr,
|
|
uint32_t length) {
|
|
shared_memory_->MemoryInvalidationCallback(base_ptr, length, true);
|
|
primitive_processor_->MemoryInvalidationCallback(base_ptr, length, true);
|
|
}
|
|
|
|
void VulkanCommandProcessor::InitializeShaderStorage(
|
|
const std::filesystem::path& cache_root, uint32_t title_id, bool blocking,
|
|
std::function<void()> completion_callback) {
|
|
CommandProcessor::InitializeShaderStorage(cache_root, title_id, blocking,
|
|
nullptr);
|
|
pipeline_cache_->InitializeShaderStorage(cache_root, title_id, blocking,
|
|
std::move(completion_callback));
|
|
}
|
|
|
|
void VulkanCommandProcessor::RestoreEdramSnapshot(const void* snapshot) {}
|
|
|
|
std::string VulkanCommandProcessor::GetWindowTitleText() const {
|
|
std::ostringstream title;
|
|
title << "Vulkan";
|
|
if (render_target_cache_) {
|
|
switch (render_target_cache_->GetPath()) {
|
|
case RenderTargetCache::Path::kHostRenderTargets:
|
|
title << " - FBO";
|
|
break;
|
|
case RenderTargetCache::Path::kPixelShaderInterlock:
|
|
title << " - FSI";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
uint32_t draw_resolution_scale_x =
|
|
texture_cache_ ? texture_cache_->draw_resolution_scale_x() : 1;
|
|
uint32_t draw_resolution_scale_y =
|
|
texture_cache_ ? texture_cache_->draw_resolution_scale_y() : 1;
|
|
if (draw_resolution_scale_x > 1 || draw_resolution_scale_y > 1) {
|
|
title << ' ' << draw_resolution_scale_x << 'x' << draw_resolution_scale_y;
|
|
}
|
|
}
|
|
auto* audio_system = kernel_state_->emulator()->audio_system();
|
|
if (audio_system) {
|
|
title << " - " << audio_system->name();
|
|
}
|
|
return title.str();
|
|
}
|
|
|
|
bool VulkanCommandProcessor::SetupContext() {
|
|
if (!CommandProcessor::SetupContext()) {
|
|
XELOGE("Failed to initialize base command processor context");
|
|
return false;
|
|
}
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
const ui::vulkan::VulkanDevice::Properties& device_properties =
|
|
vulkan_device->properties();
|
|
|
|
// The unconditional inclusion of the vertex shader stage also covers the case
|
|
// of manual index / factor buffer fetch (the system constants and the shared
|
|
// memory are needed for that) in the tessellation vertex shader when
|
|
// fullDrawIndexUint32 is not supported.
|
|
guest_shader_pipeline_stages_ = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
guest_shader_vertex_stages_ = VK_SHADER_STAGE_VERTEX_BIT;
|
|
if (device_properties.tessellationShader) {
|
|
guest_shader_pipeline_stages_ |=
|
|
VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
|
|
guest_shader_vertex_stages_ |= VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
|
|
}
|
|
if (!device_properties.vertexPipelineStoresAndAtomics) {
|
|
// For memory export from vertex shaders converted to compute shaders.
|
|
guest_shader_pipeline_stages_ |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
|
guest_shader_vertex_stages_ |= VK_SHADER_STAGE_COMPUTE_BIT;
|
|
}
|
|
|
|
// 16384 is bigger than any single uniform buffer that Xenia needs, but is the
|
|
// minimum maxUniformBufferRange, thus the safe minimum amount.
|
|
uniform_buffer_pool_ = std::make_unique<ui::vulkan::VulkanUploadBufferPool>(
|
|
vulkan_device, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
|
xe::align(std::max(ui::GraphicsUploadBufferPool::kDefaultPageSize,
|
|
size_t(16384)),
|
|
size_t(device_properties.minUniformBufferOffsetAlignment)));
|
|
|
|
// Descriptor set layouts that don't depend on the setup of other subsystems.
|
|
VkShaderStageFlags guest_shader_stages =
|
|
guest_shader_vertex_stages_ | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
// Empty.
|
|
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info;
|
|
descriptor_set_layout_create_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
descriptor_set_layout_create_info.pNext = nullptr;
|
|
descriptor_set_layout_create_info.flags = 0;
|
|
descriptor_set_layout_create_info.bindingCount = 0;
|
|
descriptor_set_layout_create_info.pBindings = nullptr;
|
|
if (dfn.vkCreateDescriptorSetLayout(
|
|
device, &descriptor_set_layout_create_info, nullptr,
|
|
&descriptor_set_layout_empty_) != VK_SUCCESS) {
|
|
XELOGE("Failed to create an empty Vulkan descriptor set layout");
|
|
return false;
|
|
}
|
|
// Guest draw constants.
|
|
VkDescriptorSetLayoutBinding descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferCount] = {};
|
|
for (uint32_t i = 0; i < SpirvShaderTranslator::kConstantBufferCount; ++i) {
|
|
VkDescriptorSetLayoutBinding& constants_binding =
|
|
descriptor_set_layout_bindings_constants[i];
|
|
constants_binding.binding = i;
|
|
constants_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
constants_binding.descriptorCount = 1;
|
|
constants_binding.pImmutableSamplers = nullptr;
|
|
}
|
|
descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferSystem]
|
|
.stageFlags =
|
|
guest_shader_stages |
|
|
(device_properties.tessellationShader
|
|
? VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT
|
|
: 0) |
|
|
(device_properties.geometryShader ? VK_SHADER_STAGE_GEOMETRY_BIT : 0);
|
|
descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferFloatVertex]
|
|
.stageFlags = guest_shader_vertex_stages_;
|
|
descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferFloatPixel]
|
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferBoolLoop]
|
|
.stageFlags = guest_shader_stages;
|
|
descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferFetch]
|
|
.stageFlags = guest_shader_stages;
|
|
// Clip plane constants - used by vertex shader (and TES for tessellation).
|
|
descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferClipPlanes]
|
|
.stageFlags = guest_shader_vertex_stages_;
|
|
// Tessellation constants - used by tessellation control shader, the
|
|
// tessellation vertex shader (for index/factor processing), and the
|
|
// tessellation evaluation shader (domain shader, which is the translated
|
|
// Xenos vertex shader).
|
|
descriptor_set_layout_bindings_constants
|
|
[SpirvShaderTranslator::kConstantBufferTessellation]
|
|
.stageFlags = device_properties.tessellationShader
|
|
? (VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT |
|
|
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT |
|
|
VK_SHADER_STAGE_VERTEX_BIT)
|
|
: 0;
|
|
descriptor_set_layout_create_info.bindingCount =
|
|
uint32_t(xe::countof(descriptor_set_layout_bindings_constants));
|
|
descriptor_set_layout_create_info.pBindings =
|
|
descriptor_set_layout_bindings_constants;
|
|
if (dfn.vkCreateDescriptorSetLayout(
|
|
device, &descriptor_set_layout_create_info, nullptr,
|
|
&descriptor_set_layout_constants_) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to create a Vulkan descriptor set layout for guest draw "
|
|
"constant buffers");
|
|
return false;
|
|
}
|
|
// Transient: storage buffer for compute shaders.
|
|
VkDescriptorSetLayoutBinding descriptor_set_layout_binding_transient;
|
|
descriptor_set_layout_binding_transient.binding = 0;
|
|
descriptor_set_layout_binding_transient.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
descriptor_set_layout_binding_transient.descriptorCount = 1;
|
|
descriptor_set_layout_binding_transient.stageFlags =
|
|
VK_SHADER_STAGE_COMPUTE_BIT;
|
|
descriptor_set_layout_binding_transient.pImmutableSamplers = nullptr;
|
|
descriptor_set_layout_create_info.bindingCount = 1;
|
|
descriptor_set_layout_create_info.pBindings =
|
|
&descriptor_set_layout_binding_transient;
|
|
if (dfn.vkCreateDescriptorSetLayout(
|
|
device, &descriptor_set_layout_create_info, nullptr,
|
|
&descriptor_set_layouts_single_transient_[size_t(
|
|
SingleTransientDescriptorLayout::kStorageBufferCompute)]) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to create a Vulkan descriptor set layout for a storage buffer "
|
|
"bound to the compute shader");
|
|
return false;
|
|
}
|
|
|
|
shared_memory_ = std::make_unique<VulkanSharedMemory>(
|
|
*this, *memory_, trace_writer_, guest_shader_pipeline_stages_);
|
|
if (!shared_memory_->Initialize()) {
|
|
XELOGE("Failed to initialize shared memory");
|
|
return false;
|
|
}
|
|
|
|
primitive_processor_ = std::make_unique<VulkanPrimitiveProcessor>(
|
|
*register_file_, *memory_, trace_writer_, *shared_memory_, *this);
|
|
if (!primitive_processor_->Initialize()) {
|
|
XELOGE("Failed to initialize the geometric primitive processor");
|
|
return false;
|
|
}
|
|
|
|
uint32_t shared_memory_binding_count_log2 =
|
|
SpirvShaderTranslator::GetSharedMemoryStorageBufferCountLog2(
|
|
device_properties.maxStorageBufferRange);
|
|
uint32_t shared_memory_binding_count = UINT32_C(1)
|
|
<< shared_memory_binding_count_log2;
|
|
|
|
// Requires the transient descriptor set layouts.
|
|
// Get draw resolution scale and clamp based on device capabilities
|
|
uint32_t draw_resolution_scale_x, draw_resolution_scale_y;
|
|
bool draw_resolution_scale_not_clamped =
|
|
TextureCache::GetConfigDrawResolutionScale(draw_resolution_scale_x,
|
|
draw_resolution_scale_y);
|
|
// Check if sparse binding is supported for resolution scaling
|
|
bool has_sparse_binding = device_properties.sparseBinding &&
|
|
device_properties.sparseResidencyBuffer;
|
|
if (!TextureCache::ClampDrawResolutionScaleToMaxSupported(
|
|
draw_resolution_scale_x, draw_resolution_scale_y, has_sparse_binding,
|
|
0)) {
|
|
draw_resolution_scale_not_clamped = false;
|
|
}
|
|
if (!draw_resolution_scale_not_clamped) {
|
|
XELOGW(
|
|
"The requested draw resolution scale is not supported by the device or "
|
|
"the emulator, reducing to {}x{}",
|
|
draw_resolution_scale_x, draw_resolution_scale_y);
|
|
}
|
|
|
|
render_target_cache_ = std::make_unique<VulkanRenderTargetCache>(
|
|
*register_file_, *memory_, trace_writer_, draw_resolution_scale_x,
|
|
draw_resolution_scale_y, *this);
|
|
if (!render_target_cache_->Initialize(shared_memory_binding_count)) {
|
|
XELOGE("Failed to initialize the render target cache");
|
|
return false;
|
|
}
|
|
|
|
// Shared memory, EDRAM, and ZPD FSI counter descriptor set layout.
|
|
bool edram_fragment_shader_interlock =
|
|
render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kPixelShaderInterlock;
|
|
VkDescriptorSetLayoutBinding
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[3];
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[0].binding = 0;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[0].descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[0].descriptorCount =
|
|
shared_memory_binding_count;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[0].stageFlags =
|
|
guest_shader_stages;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[0].pImmutableSamplers =
|
|
nullptr;
|
|
VkDescriptorSetLayoutCreateInfo
|
|
shared_memory_and_edram_descriptor_set_layout_create_info;
|
|
shared_memory_and_edram_descriptor_set_layout_create_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
shared_memory_and_edram_descriptor_set_layout_create_info.pNext = nullptr;
|
|
shared_memory_and_edram_descriptor_set_layout_create_info.flags = 0;
|
|
shared_memory_and_edram_descriptor_set_layout_create_info.pBindings =
|
|
shared_memory_and_edram_descriptor_set_layout_bindings;
|
|
if (edram_fragment_shader_interlock) {
|
|
// EDRAM.
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[1].binding = 1;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[1].descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[1].descriptorCount =
|
|
1;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[1].stageFlags =
|
|
VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[1]
|
|
.pImmutableSamplers = nullptr;
|
|
shared_memory_and_edram_descriptor_set_layout_create_info.bindingCount = 2;
|
|
// ZPD FSI counter.
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[2].binding = 2;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[2].descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[2].descriptorCount =
|
|
1;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[2].stageFlags =
|
|
VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[2]
|
|
.pImmutableSamplers = nullptr;
|
|
shared_memory_and_edram_descriptor_set_layout_create_info.bindingCount = 3;
|
|
} else {
|
|
shared_memory_and_edram_descriptor_set_layout_create_info.bindingCount = 1;
|
|
}
|
|
if (dfn.vkCreateDescriptorSetLayout(
|
|
device, &shared_memory_and_edram_descriptor_set_layout_create_info,
|
|
nullptr,
|
|
&descriptor_set_layout_shared_memory_and_edram_) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to create a Vulkan descriptor set layout for the shared memory "
|
|
"and the EDRAM");
|
|
return false;
|
|
}
|
|
|
|
pipeline_cache_ = std::make_unique<VulkanPipelineCache>(
|
|
*this, *register_file_, *render_target_cache_,
|
|
guest_shader_vertex_stages_);
|
|
if (!pipeline_cache_->Initialize()) {
|
|
XELOGE("Failed to initialize the graphics pipeline cache");
|
|
return false;
|
|
}
|
|
|
|
// Requires the transient descriptor set layouts.
|
|
// Use the same draw resolution scale as render target cache
|
|
texture_cache_ = VulkanTextureCache::Create(
|
|
*register_file_, *shared_memory_, draw_resolution_scale_x,
|
|
draw_resolution_scale_y, *this, guest_shader_pipeline_stages_);
|
|
if (!texture_cache_) {
|
|
XELOGE("Failed to initialize the texture cache");
|
|
return false;
|
|
}
|
|
|
|
// Needed by NormalizeSampleCount.
|
|
zpd_draw_resolution_scale_x_ = draw_resolution_scale_x;
|
|
zpd_draw_resolution_scale_y_ = draw_resolution_scale_y;
|
|
|
|
const VkDeviceSize zpd_fsi_counter_sink_range =
|
|
sizeof(uint32_t) * kZPDQueryPoolCapacity;
|
|
if (edram_fragment_shader_interlock) {
|
|
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
|
vulkan_device, zpd_fsi_counter_sink_range,
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
|
ui::vulkan::util::MemoryPurpose::kDeviceLocal,
|
|
zpd_fsi_counter_sink_buffer_,
|
|
zpd_fsi_counter_sink_buffer_memory_)) {
|
|
XELOGE("Failed to create the ZPD FSI counter sink buffer");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Shared memory, EDRAM, and ZPD FSI counter common bindings.
|
|
VkDescriptorPoolSize descriptor_pool_sizes[1];
|
|
descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
descriptor_pool_sizes[0].descriptorCount =
|
|
shared_memory_binding_count +
|
|
2u * uint32_t(edram_fragment_shader_interlock);
|
|
VkDescriptorPoolCreateInfo descriptor_pool_create_info;
|
|
descriptor_pool_create_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
descriptor_pool_create_info.pNext = nullptr;
|
|
descriptor_pool_create_info.flags = 0;
|
|
descriptor_pool_create_info.maxSets = 1;
|
|
descriptor_pool_create_info.poolSizeCount = 1;
|
|
descriptor_pool_create_info.pPoolSizes = descriptor_pool_sizes;
|
|
if (dfn.vkCreateDescriptorPool(device, &descriptor_pool_create_info, nullptr,
|
|
&shared_memory_and_edram_descriptor_pool_) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to create the Vulkan descriptor pool for shared memory and "
|
|
"EDRAM");
|
|
return false;
|
|
}
|
|
VkDescriptorSetAllocateInfo descriptor_set_allocate_info;
|
|
descriptor_set_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
descriptor_set_allocate_info.pNext = nullptr;
|
|
descriptor_set_allocate_info.descriptorPool =
|
|
shared_memory_and_edram_descriptor_pool_;
|
|
descriptor_set_allocate_info.descriptorSetCount = 1;
|
|
descriptor_set_allocate_info.pSetLayouts =
|
|
&descriptor_set_layout_shared_memory_and_edram_;
|
|
if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info,
|
|
&shared_memory_and_edram_descriptor_set_) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to allocate the Vulkan descriptor set for shared memory and "
|
|
"EDRAM");
|
|
return false;
|
|
}
|
|
VkDescriptorBufferInfo
|
|
shared_memory_descriptor_buffers_info[SharedMemory::kBufferSize /
|
|
(128 << 20)];
|
|
uint32_t shared_memory_binding_range =
|
|
SharedMemory::kBufferSize >> shared_memory_binding_count_log2;
|
|
for (uint32_t i = 0; i < shared_memory_binding_count; ++i) {
|
|
VkDescriptorBufferInfo& shared_memory_descriptor_buffer_info =
|
|
shared_memory_descriptor_buffers_info[i];
|
|
shared_memory_descriptor_buffer_info.buffer = shared_memory_->buffer();
|
|
shared_memory_descriptor_buffer_info.offset =
|
|
shared_memory_binding_range * i;
|
|
shared_memory_descriptor_buffer_info.range = shared_memory_binding_range;
|
|
}
|
|
VkWriteDescriptorSet write_descriptor_sets[3];
|
|
VkWriteDescriptorSet& write_descriptor_set_shared_memory =
|
|
write_descriptor_sets[0];
|
|
write_descriptor_set_shared_memory.sType =
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write_descriptor_set_shared_memory.pNext = nullptr;
|
|
write_descriptor_set_shared_memory.dstSet =
|
|
shared_memory_and_edram_descriptor_set_;
|
|
write_descriptor_set_shared_memory.dstBinding = 0;
|
|
write_descriptor_set_shared_memory.dstArrayElement = 0;
|
|
write_descriptor_set_shared_memory.descriptorCount =
|
|
shared_memory_binding_count;
|
|
write_descriptor_set_shared_memory.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
write_descriptor_set_shared_memory.pImageInfo = nullptr;
|
|
write_descriptor_set_shared_memory.pBufferInfo =
|
|
shared_memory_descriptor_buffers_info;
|
|
write_descriptor_set_shared_memory.pTexelBufferView = nullptr;
|
|
VkDescriptorBufferInfo edram_descriptor_buffer_info;
|
|
if (edram_fragment_shader_interlock) {
|
|
edram_descriptor_buffer_info.buffer = render_target_cache_->edram_buffer();
|
|
edram_descriptor_buffer_info.offset = 0;
|
|
edram_descriptor_buffer_info.range = VK_WHOLE_SIZE;
|
|
VkWriteDescriptorSet& write_descriptor_set_edram = write_descriptor_sets[1];
|
|
write_descriptor_set_edram.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write_descriptor_set_edram.pNext = nullptr;
|
|
write_descriptor_set_edram.dstSet = shared_memory_and_edram_descriptor_set_;
|
|
write_descriptor_set_edram.dstBinding = 1;
|
|
write_descriptor_set_edram.dstArrayElement = 0;
|
|
write_descriptor_set_edram.descriptorCount = 1;
|
|
write_descriptor_set_edram.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
write_descriptor_set_edram.pImageInfo = nullptr;
|
|
write_descriptor_set_edram.pBufferInfo = &edram_descriptor_buffer_info;
|
|
write_descriptor_set_edram.pTexelBufferView = nullptr;
|
|
// ZPD FSI counter.
|
|
VkDescriptorBufferInfo zpd_fsi_counter_descriptor_buffer_info;
|
|
zpd_fsi_counter_descriptor_buffer_info.buffer =
|
|
zpd_fsi_counter_sink_buffer_;
|
|
zpd_fsi_counter_descriptor_buffer_info.offset = 0;
|
|
zpd_fsi_counter_descriptor_buffer_info.range = zpd_fsi_counter_sink_range;
|
|
// Keep binding 2 valid until the real counter buffer is ready.
|
|
VkWriteDescriptorSet& write_descriptor_set_zpd_fsi_counter_init =
|
|
write_descriptor_sets[2];
|
|
write_descriptor_set_zpd_fsi_counter_init.sType =
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write_descriptor_set_zpd_fsi_counter_init.pNext = nullptr;
|
|
write_descriptor_set_zpd_fsi_counter_init.dstSet =
|
|
shared_memory_and_edram_descriptor_set_;
|
|
write_descriptor_set_zpd_fsi_counter_init.dstBinding = 2;
|
|
write_descriptor_set_zpd_fsi_counter_init.dstArrayElement = 0;
|
|
write_descriptor_set_zpd_fsi_counter_init.descriptorCount = 1;
|
|
write_descriptor_set_zpd_fsi_counter_init.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
write_descriptor_set_zpd_fsi_counter_init.pImageInfo = nullptr;
|
|
write_descriptor_set_zpd_fsi_counter_init.pBufferInfo =
|
|
&zpd_fsi_counter_descriptor_buffer_info;
|
|
write_descriptor_set_zpd_fsi_counter_init.pTexelBufferView = nullptr;
|
|
}
|
|
dfn.vkUpdateDescriptorSets(device,
|
|
1 + 2 * uint32_t(edram_fragment_shader_interlock),
|
|
write_descriptor_sets, 0, nullptr);
|
|
if (edram_fragment_shader_interlock) {
|
|
zpd_fsi_counter_descriptor_buffer_ = zpd_fsi_counter_sink_buffer_;
|
|
zpd_fsi_counter_descriptor_range_ = zpd_fsi_counter_sink_range;
|
|
}
|
|
|
|
// Swap objects.
|
|
|
|
// Gamma ramp, either device-local and host-visible at once, or separate
|
|
// device-local texel buffer and host-visible upload buffer.
|
|
gamma_ramp_256_entry_table_current_frame_ = UINT32_MAX;
|
|
gamma_ramp_pwl_current_frame_ = UINT32_MAX;
|
|
// Try to create a device-local host-visible buffer first, to skip copying.
|
|
constexpr uint32_t kGammaRampSize256EntryTable = sizeof(uint32_t) * 256;
|
|
constexpr uint32_t kGammaRampSizePWL = sizeof(uint16_t) * 2 * 3 * 128;
|
|
constexpr uint32_t kGammaRampSize =
|
|
kGammaRampSize256EntryTable + kGammaRampSizePWL;
|
|
VkBufferCreateInfo gamma_ramp_host_visible_buffer_create_info;
|
|
gamma_ramp_host_visible_buffer_create_info.sType =
|
|
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
gamma_ramp_host_visible_buffer_create_info.pNext = nullptr;
|
|
gamma_ramp_host_visible_buffer_create_info.flags = 0;
|
|
gamma_ramp_host_visible_buffer_create_info.size =
|
|
kGammaRampSize * kMaxFramesInFlight;
|
|
gamma_ramp_host_visible_buffer_create_info.usage =
|
|
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
|
|
gamma_ramp_host_visible_buffer_create_info.sharingMode =
|
|
VK_SHARING_MODE_EXCLUSIVE;
|
|
gamma_ramp_host_visible_buffer_create_info.queueFamilyIndexCount = 0;
|
|
gamma_ramp_host_visible_buffer_create_info.pQueueFamilyIndices = nullptr;
|
|
if (dfn.vkCreateBuffer(device, &gamma_ramp_host_visible_buffer_create_info,
|
|
nullptr, &gamma_ramp_buffer_) == VK_SUCCESS) {
|
|
bool use_gamma_ramp_host_visible_buffer = false;
|
|
VkMemoryRequirements gamma_ramp_host_visible_buffer_memory_requirements;
|
|
dfn.vkGetBufferMemoryRequirements(
|
|
device, gamma_ramp_buffer_,
|
|
&gamma_ramp_host_visible_buffer_memory_requirements);
|
|
uint32_t gamma_ramp_host_visible_buffer_memory_types =
|
|
gamma_ramp_host_visible_buffer_memory_requirements.memoryTypeBits &
|
|
(vulkan_device->memory_types().device_local &
|
|
vulkan_device->memory_types().host_visible);
|
|
VkMemoryAllocateInfo gamma_ramp_host_visible_buffer_memory_allocate_info;
|
|
// Prefer a host-uncached (because it's write-only) memory type, but try a
|
|
// host-cached host-visible device-local one as well.
|
|
if (xe::bit_scan_forward(
|
|
gamma_ramp_host_visible_buffer_memory_types &
|
|
~vulkan_device->memory_types().host_cached,
|
|
&(gamma_ramp_host_visible_buffer_memory_allocate_info
|
|
.memoryTypeIndex)) ||
|
|
xe::bit_scan_forward(
|
|
gamma_ramp_host_visible_buffer_memory_types,
|
|
&(gamma_ramp_host_visible_buffer_memory_allocate_info
|
|
.memoryTypeIndex))) {
|
|
VkMemoryAllocateInfo*
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info_last =
|
|
&gamma_ramp_host_visible_buffer_memory_allocate_info;
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info.pNext = nullptr;
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info.allocationSize =
|
|
gamma_ramp_host_visible_buffer_memory_requirements.size;
|
|
VkMemoryDedicatedAllocateInfo
|
|
gamma_ramp_host_visible_buffer_memory_dedicated_allocate_info;
|
|
if (vulkan_device->extensions().ext_1_1_KHR_dedicated_allocation) {
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info_last->pNext =
|
|
&gamma_ramp_host_visible_buffer_memory_dedicated_allocate_info;
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info_last =
|
|
reinterpret_cast<VkMemoryAllocateInfo*>(
|
|
&gamma_ramp_host_visible_buffer_memory_dedicated_allocate_info);
|
|
gamma_ramp_host_visible_buffer_memory_dedicated_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
|
|
gamma_ramp_host_visible_buffer_memory_dedicated_allocate_info.pNext =
|
|
nullptr;
|
|
gamma_ramp_host_visible_buffer_memory_dedicated_allocate_info.image =
|
|
VK_NULL_HANDLE;
|
|
gamma_ramp_host_visible_buffer_memory_dedicated_allocate_info.buffer =
|
|
gamma_ramp_buffer_;
|
|
}
|
|
if (dfn.vkAllocateMemory(
|
|
device, &gamma_ramp_host_visible_buffer_memory_allocate_info,
|
|
nullptr, &gamma_ramp_buffer_memory_) == VK_SUCCESS) {
|
|
if (dfn.vkBindBufferMemory(device, gamma_ramp_buffer_,
|
|
gamma_ramp_buffer_memory_,
|
|
0) == VK_SUCCESS) {
|
|
if (dfn.vkMapMemory(device, gamma_ramp_buffer_memory_, 0,
|
|
VK_WHOLE_SIZE, 0,
|
|
&gamma_ramp_upload_mapping_) == VK_SUCCESS) {
|
|
use_gamma_ramp_host_visible_buffer = true;
|
|
gamma_ramp_upload_memory_size_ =
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info
|
|
.allocationSize;
|
|
gamma_ramp_upload_memory_type_ =
|
|
gamma_ramp_host_visible_buffer_memory_allocate_info
|
|
.memoryTypeIndex;
|
|
}
|
|
}
|
|
if (!use_gamma_ramp_host_visible_buffer) {
|
|
dfn.vkFreeMemory(device, gamma_ramp_buffer_memory_, nullptr);
|
|
gamma_ramp_buffer_memory_ = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
}
|
|
if (!use_gamma_ramp_host_visible_buffer) {
|
|
dfn.vkDestroyBuffer(device, gamma_ramp_buffer_, nullptr);
|
|
gamma_ramp_buffer_ = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
if (gamma_ramp_buffer_ == VK_NULL_HANDLE) {
|
|
// Create separate buffers for the shader and uploading.
|
|
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
|
vulkan_device, kGammaRampSize,
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
|
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT,
|
|
ui::vulkan::util::MemoryPurpose::kDeviceLocal, gamma_ramp_buffer_,
|
|
gamma_ramp_buffer_memory_)) {
|
|
XELOGE("Failed to create the gamma ramp buffer");
|
|
return false;
|
|
}
|
|
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
|
vulkan_device, kGammaRampSize * kMaxFramesInFlight,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
ui::vulkan::util::MemoryPurpose::kUpload, gamma_ramp_upload_buffer_,
|
|
gamma_ramp_upload_buffer_memory_, &gamma_ramp_upload_memory_type_,
|
|
&gamma_ramp_upload_memory_size_)) {
|
|
XELOGE("Failed to create the gamma ramp upload buffer");
|
|
return false;
|
|
}
|
|
if (dfn.vkMapMemory(device, gamma_ramp_upload_buffer_memory_, 0,
|
|
VK_WHOLE_SIZE, 0,
|
|
&gamma_ramp_upload_mapping_) != VK_SUCCESS) {
|
|
XELOGE("Failed to map the gamma ramp upload buffer");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Gamma ramp buffer views.
|
|
uint32_t gamma_ramp_frame_count =
|
|
gamma_ramp_upload_buffer_ == VK_NULL_HANDLE ? kMaxFramesInFlight : 1;
|
|
VkBufferViewCreateInfo gamma_ramp_buffer_view_create_info;
|
|
gamma_ramp_buffer_view_create_info.sType =
|
|
VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
|
|
gamma_ramp_buffer_view_create_info.pNext = nullptr;
|
|
gamma_ramp_buffer_view_create_info.flags = 0;
|
|
gamma_ramp_buffer_view_create_info.buffer = gamma_ramp_buffer_;
|
|
// 256-entry table.
|
|
gamma_ramp_buffer_view_create_info.format =
|
|
VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
|
gamma_ramp_buffer_view_create_info.range = kGammaRampSize256EntryTable;
|
|
for (uint32_t i = 0; i < gamma_ramp_frame_count; ++i) {
|
|
gamma_ramp_buffer_view_create_info.offset = kGammaRampSize * i;
|
|
if (dfn.vkCreateBufferView(device, &gamma_ramp_buffer_view_create_info,
|
|
nullptr, &gamma_ramp_buffer_views_[i * 2]) !=
|
|
VK_SUCCESS) {
|
|
XELOGE("Failed to create a 256-entry table gamma ramp buffer view");
|
|
return false;
|
|
}
|
|
}
|
|
// Piecewise linear.
|
|
gamma_ramp_buffer_view_create_info.format = VK_FORMAT_R16G16_UINT;
|
|
gamma_ramp_buffer_view_create_info.range = kGammaRampSizePWL;
|
|
for (uint32_t i = 0; i < gamma_ramp_frame_count; ++i) {
|
|
gamma_ramp_buffer_view_create_info.offset =
|
|
kGammaRampSize * i + kGammaRampSize256EntryTable;
|
|
if (dfn.vkCreateBufferView(device, &gamma_ramp_buffer_view_create_info,
|
|
nullptr, &gamma_ramp_buffer_views_[i * 2 + 1]) !=
|
|
VK_SUCCESS) {
|
|
XELOGE("Failed to create a PWL gamma ramp buffer view");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Swap descriptor set layouts.
|
|
VkDescriptorSetLayoutBinding swap_descriptor_set_layout_binding;
|
|
swap_descriptor_set_layout_binding.binding = 0;
|
|
swap_descriptor_set_layout_binding.descriptorCount = 1;
|
|
swap_descriptor_set_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
swap_descriptor_set_layout_binding.pImmutableSamplers = nullptr;
|
|
VkDescriptorSetLayoutCreateInfo swap_descriptor_set_layout_create_info;
|
|
swap_descriptor_set_layout_create_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
swap_descriptor_set_layout_create_info.pNext = nullptr;
|
|
swap_descriptor_set_layout_create_info.flags = 0;
|
|
swap_descriptor_set_layout_create_info.bindingCount = 1;
|
|
swap_descriptor_set_layout_create_info.pBindings =
|
|
&swap_descriptor_set_layout_binding;
|
|
swap_descriptor_set_layout_binding.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
if (dfn.vkCreateDescriptorSetLayout(
|
|
device, &swap_descriptor_set_layout_create_info, nullptr,
|
|
&swap_descriptor_set_layout_sampled_image_) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to create the presentation sampled image descriptor set "
|
|
"layout");
|
|
return false;
|
|
}
|
|
swap_descriptor_set_layout_binding.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
|
if (dfn.vkCreateDescriptorSetLayout(
|
|
device, &swap_descriptor_set_layout_create_info, nullptr,
|
|
&swap_descriptor_set_layout_uniform_texel_buffer_) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to create the presentation uniform texel buffer descriptor set "
|
|
"layout");
|
|
return false;
|
|
}
|
|
|
|
// Swap descriptor pool.
|
|
std::array<VkDescriptorPoolSize, 2> swap_descriptor_pool_sizes;
|
|
VkDescriptorPoolCreateInfo swap_descriptor_pool_create_info;
|
|
swap_descriptor_pool_create_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
swap_descriptor_pool_create_info.pNext = nullptr;
|
|
swap_descriptor_pool_create_info.flags = 0;
|
|
swap_descriptor_pool_create_info.maxSets = 0;
|
|
swap_descriptor_pool_create_info.poolSizeCount = 0;
|
|
swap_descriptor_pool_create_info.pPoolSizes =
|
|
swap_descriptor_pool_sizes.data();
|
|
// TODO(Triang3l): FXAA combined image and sampler sources.
|
|
{
|
|
VkDescriptorPoolSize& swap_descriptor_pool_size_sampled_image =
|
|
swap_descriptor_pool_sizes[swap_descriptor_pool_create_info
|
|
.poolSizeCount++];
|
|
swap_descriptor_pool_size_sampled_image.type =
|
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
// Source images.
|
|
swap_descriptor_pool_size_sampled_image.descriptorCount =
|
|
kMaxFramesInFlight;
|
|
swap_descriptor_pool_create_info.maxSets += kMaxFramesInFlight;
|
|
}
|
|
// 256-entry table and PWL gamma ramps. If the gamma ramp buffer is
|
|
// host-visible, for multiple frames.
|
|
uint32_t gamma_ramp_buffer_view_count = 2 * gamma_ramp_frame_count;
|
|
{
|
|
VkDescriptorPoolSize& swap_descriptor_pool_size_uniform_texel_buffer =
|
|
swap_descriptor_pool_sizes[swap_descriptor_pool_create_info
|
|
.poolSizeCount++];
|
|
swap_descriptor_pool_size_uniform_texel_buffer.type =
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
|
swap_descriptor_pool_size_uniform_texel_buffer.descriptorCount =
|
|
gamma_ramp_buffer_view_count;
|
|
swap_descriptor_pool_create_info.maxSets += gamma_ramp_buffer_view_count;
|
|
}
|
|
if (dfn.vkCreateDescriptorPool(device, &swap_descriptor_pool_create_info,
|
|
nullptr,
|
|
&swap_descriptor_pool_) != VK_SUCCESS) {
|
|
XELOGE("Failed to create the presentation descriptor pool");
|
|
return false;
|
|
}
|
|
|
|
// Swap descriptor set allocation.
|
|
VkDescriptorSetAllocateInfo swap_descriptor_set_allocate_info;
|
|
swap_descriptor_set_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
swap_descriptor_set_allocate_info.pNext = nullptr;
|
|
swap_descriptor_set_allocate_info.descriptorPool = swap_descriptor_pool_;
|
|
swap_descriptor_set_allocate_info.descriptorSetCount = 1;
|
|
swap_descriptor_set_allocate_info.pSetLayouts =
|
|
&swap_descriptor_set_layout_uniform_texel_buffer_;
|
|
for (uint32_t i = 0; i < gamma_ramp_buffer_view_count; ++i) {
|
|
if (dfn.vkAllocateDescriptorSets(device, &swap_descriptor_set_allocate_info,
|
|
&swap_descriptors_gamma_ramp_[i]) !=
|
|
VK_SUCCESS) {
|
|
XELOGE("Failed to allocate the gamma ramp descriptor sets");
|
|
return false;
|
|
}
|
|
}
|
|
swap_descriptor_set_allocate_info.pSetLayouts =
|
|
&swap_descriptor_set_layout_sampled_image_;
|
|
for (uint32_t i = 0; i < kMaxFramesInFlight; ++i) {
|
|
if (dfn.vkAllocateDescriptorSets(device, &swap_descriptor_set_allocate_info,
|
|
&swap_descriptors_source_[i]) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to allocate the presentation source image descriptor sets");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Gamma ramp descriptor sets.
|
|
VkWriteDescriptorSet gamma_ramp_write_descriptor_set;
|
|
gamma_ramp_write_descriptor_set.sType =
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
gamma_ramp_write_descriptor_set.pNext = nullptr;
|
|
gamma_ramp_write_descriptor_set.dstBinding = 0;
|
|
gamma_ramp_write_descriptor_set.dstArrayElement = 0;
|
|
gamma_ramp_write_descriptor_set.descriptorCount = 1;
|
|
gamma_ramp_write_descriptor_set.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
|
gamma_ramp_write_descriptor_set.pImageInfo = nullptr;
|
|
gamma_ramp_write_descriptor_set.pBufferInfo = nullptr;
|
|
for (uint32_t i = 0; i < gamma_ramp_buffer_view_count; ++i) {
|
|
gamma_ramp_write_descriptor_set.dstSet = swap_descriptors_gamma_ramp_[i];
|
|
gamma_ramp_write_descriptor_set.pTexelBufferView =
|
|
&gamma_ramp_buffer_views_[i];
|
|
dfn.vkUpdateDescriptorSets(device, 1, &gamma_ramp_write_descriptor_set, 0,
|
|
nullptr);
|
|
}
|
|
|
|
// Gamma ramp application pipeline layout.
|
|
std::array<VkDescriptorSetLayout, kSwapApplyGammaDescriptorSetCount>
|
|
swap_apply_gamma_descriptor_set_layouts{};
|
|
swap_apply_gamma_descriptor_set_layouts[kSwapApplyGammaDescriptorSetRamp] =
|
|
swap_descriptor_set_layout_uniform_texel_buffer_;
|
|
swap_apply_gamma_descriptor_set_layouts[kSwapApplyGammaDescriptorSetSource] =
|
|
swap_descriptor_set_layout_sampled_image_;
|
|
VkPipelineLayoutCreateInfo swap_apply_gamma_pipeline_layout_create_info;
|
|
swap_apply_gamma_pipeline_layout_create_info.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_layout_create_info.pNext = nullptr;
|
|
swap_apply_gamma_pipeline_layout_create_info.flags = 0;
|
|
swap_apply_gamma_pipeline_layout_create_info.setLayoutCount =
|
|
uint32_t(swap_apply_gamma_descriptor_set_layouts.size());
|
|
swap_apply_gamma_pipeline_layout_create_info.pSetLayouts =
|
|
swap_apply_gamma_descriptor_set_layouts.data();
|
|
swap_apply_gamma_pipeline_layout_create_info.pushConstantRangeCount = 0;
|
|
swap_apply_gamma_pipeline_layout_create_info.pPushConstantRanges = nullptr;
|
|
if (dfn.vkCreatePipelineLayout(
|
|
device, &swap_apply_gamma_pipeline_layout_create_info, nullptr,
|
|
&swap_apply_gamma_pipeline_layout_) != VK_SUCCESS) {
|
|
XELOGE("Failed to create the gamma ramp application pipeline layout");
|
|
return false;
|
|
}
|
|
|
|
// Gamma application render pass. Doesn't make assumptions about outer usage
|
|
// (explicit barriers must be used instead) for simplicity of use in different
|
|
// scenarios with different pipelines.
|
|
VkAttachmentDescription swap_apply_gamma_render_pass_attachment;
|
|
swap_apply_gamma_render_pass_attachment.flags = 0;
|
|
swap_apply_gamma_render_pass_attachment.format =
|
|
ui::vulkan::VulkanPresenter::kGuestOutputFormat;
|
|
swap_apply_gamma_render_pass_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
swap_apply_gamma_render_pass_attachment.loadOp =
|
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
swap_apply_gamma_render_pass_attachment.storeOp =
|
|
VK_ATTACHMENT_STORE_OP_STORE;
|
|
swap_apply_gamma_render_pass_attachment.stencilLoadOp =
|
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
swap_apply_gamma_render_pass_attachment.stencilStoreOp =
|
|
VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
swap_apply_gamma_render_pass_attachment.initialLayout =
|
|
VK_IMAGE_LAYOUT_UNDEFINED;
|
|
swap_apply_gamma_render_pass_attachment.finalLayout =
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
VkAttachmentReference swap_apply_gamma_render_pass_color_attachment;
|
|
swap_apply_gamma_render_pass_color_attachment.attachment = 0;
|
|
swap_apply_gamma_render_pass_color_attachment.layout =
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
VkSubpassDescription swap_apply_gamma_render_pass_subpass = {};
|
|
swap_apply_gamma_render_pass_subpass.pipelineBindPoint =
|
|
VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
swap_apply_gamma_render_pass_subpass.colorAttachmentCount = 1;
|
|
swap_apply_gamma_render_pass_subpass.pColorAttachments =
|
|
&swap_apply_gamma_render_pass_color_attachment;
|
|
VkSubpassDependency swap_apply_gamma_render_pass_dependencies[2];
|
|
for (uint32_t i = 0; i < 2; ++i) {
|
|
VkSubpassDependency& swap_apply_gamma_render_pass_dependency =
|
|
swap_apply_gamma_render_pass_dependencies[i];
|
|
swap_apply_gamma_render_pass_dependency.srcSubpass =
|
|
i ? 0 : VK_SUBPASS_EXTERNAL;
|
|
swap_apply_gamma_render_pass_dependency.dstSubpass =
|
|
i ? VK_SUBPASS_EXTERNAL : 0;
|
|
swap_apply_gamma_render_pass_dependency.srcStageMask =
|
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
swap_apply_gamma_render_pass_dependency.dstStageMask =
|
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
swap_apply_gamma_render_pass_dependency.srcAccessMask =
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
swap_apply_gamma_render_pass_dependency.dstAccessMask =
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
swap_apply_gamma_render_pass_dependency.dependencyFlags =
|
|
VK_DEPENDENCY_BY_REGION_BIT;
|
|
}
|
|
VkRenderPassCreateInfo swap_apply_gamma_render_pass_create_info;
|
|
swap_apply_gamma_render_pass_create_info.sType =
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
swap_apply_gamma_render_pass_create_info.pNext = nullptr;
|
|
swap_apply_gamma_render_pass_create_info.flags = 0;
|
|
swap_apply_gamma_render_pass_create_info.attachmentCount = 1;
|
|
swap_apply_gamma_render_pass_create_info.pAttachments =
|
|
&swap_apply_gamma_render_pass_attachment;
|
|
swap_apply_gamma_render_pass_create_info.subpassCount = 1;
|
|
swap_apply_gamma_render_pass_create_info.pSubpasses =
|
|
&swap_apply_gamma_render_pass_subpass;
|
|
swap_apply_gamma_render_pass_create_info.dependencyCount =
|
|
uint32_t(xe::countof(swap_apply_gamma_render_pass_dependencies));
|
|
swap_apply_gamma_render_pass_create_info.pDependencies =
|
|
swap_apply_gamma_render_pass_dependencies;
|
|
if (dfn.vkCreateRenderPass(device, &swap_apply_gamma_render_pass_create_info,
|
|
nullptr,
|
|
&swap_apply_gamma_render_pass_) != VK_SUCCESS) {
|
|
XELOGE("Failed to create the gamma ramp application render pass");
|
|
return false;
|
|
}
|
|
|
|
// Gamma ramp application pipeline.
|
|
// Using a graphics pipeline, not a compute one, because storage image support
|
|
// is optional for VK_FORMAT_A2B10G10R10_UNORM_PACK32.
|
|
|
|
enum SwapApplyGammaPixelShader {
|
|
kSwapApplyGammaPixelShader256EntryTable,
|
|
kSwapApplyGammaPixelShaderPWL,
|
|
|
|
kSwapApplyGammaPixelShaderCount,
|
|
};
|
|
std::array<VkShaderModule, kSwapApplyGammaPixelShaderCount>
|
|
swap_apply_gamma_pixel_shaders{};
|
|
bool swap_apply_gamma_pixel_shaders_created =
|
|
(swap_apply_gamma_pixel_shaders[kSwapApplyGammaPixelShader256EntryTable] =
|
|
ui::vulkan::util::CreateShaderModule(
|
|
vulkan_device, shaders::apply_gamma_table_ps,
|
|
sizeof(shaders::apply_gamma_table_ps))) != VK_NULL_HANDLE &&
|
|
(swap_apply_gamma_pixel_shaders[kSwapApplyGammaPixelShaderPWL] =
|
|
ui::vulkan::util::CreateShaderModule(
|
|
vulkan_device, shaders::apply_gamma_pwl_ps,
|
|
sizeof(shaders::apply_gamma_pwl_ps))) != VK_NULL_HANDLE;
|
|
if (!swap_apply_gamma_pixel_shaders_created) {
|
|
XELOGE("Failed to create the gamma ramp application pixel shader modules");
|
|
for (VkShaderModule swap_apply_gamma_pixel_shader :
|
|
swap_apply_gamma_pixel_shaders) {
|
|
if (swap_apply_gamma_pixel_shader != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyShaderModule(device, swap_apply_gamma_pixel_shader,
|
|
nullptr);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo swap_apply_gamma_pipeline_stages[2];
|
|
swap_apply_gamma_pipeline_stages[0].sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_stages[0].pNext = nullptr;
|
|
swap_apply_gamma_pipeline_stages[0].flags = 0;
|
|
swap_apply_gamma_pipeline_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
swap_apply_gamma_pipeline_stages[0].module =
|
|
ui::vulkan::util::CreateShaderModule(vulkan_device,
|
|
shaders::fullscreen_cw_vs,
|
|
sizeof(shaders::fullscreen_cw_vs));
|
|
if (swap_apply_gamma_pipeline_stages[0].module == VK_NULL_HANDLE) {
|
|
XELOGE("Failed to create the gamma ramp application vertex shader module");
|
|
for (VkShaderModule swap_apply_gamma_pixel_shader :
|
|
swap_apply_gamma_pixel_shaders) {
|
|
assert_true(swap_apply_gamma_pixel_shader != VK_NULL_HANDLE);
|
|
dfn.vkDestroyShaderModule(device, swap_apply_gamma_pixel_shader, nullptr);
|
|
}
|
|
}
|
|
swap_apply_gamma_pipeline_stages[0].pName = "main";
|
|
swap_apply_gamma_pipeline_stages[0].pSpecializationInfo = nullptr;
|
|
swap_apply_gamma_pipeline_stages[1].sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_stages[1].pNext = nullptr;
|
|
swap_apply_gamma_pipeline_stages[1].flags = 0;
|
|
swap_apply_gamma_pipeline_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
// The fragment shader module will be specified later.
|
|
swap_apply_gamma_pipeline_stages[1].pName = "main";
|
|
swap_apply_gamma_pipeline_stages[1].pSpecializationInfo = nullptr;
|
|
|
|
VkPipelineVertexInputStateCreateInfo
|
|
swap_apply_gamma_pipeline_vertex_input_state = {};
|
|
swap_apply_gamma_pipeline_vertex_input_state.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo
|
|
swap_apply_gamma_pipeline_input_assembly_state;
|
|
swap_apply_gamma_pipeline_input_assembly_state.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_input_assembly_state.pNext = nullptr;
|
|
swap_apply_gamma_pipeline_input_assembly_state.flags = 0;
|
|
swap_apply_gamma_pipeline_input_assembly_state.topology =
|
|
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
swap_apply_gamma_pipeline_input_assembly_state.primitiveRestartEnable =
|
|
VK_FALSE;
|
|
|
|
VkPipelineViewportStateCreateInfo swap_apply_gamma_pipeline_viewport_state;
|
|
swap_apply_gamma_pipeline_viewport_state.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_viewport_state.pNext = nullptr;
|
|
swap_apply_gamma_pipeline_viewport_state.flags = 0;
|
|
swap_apply_gamma_pipeline_viewport_state.viewportCount = 1;
|
|
swap_apply_gamma_pipeline_viewport_state.pViewports = nullptr;
|
|
swap_apply_gamma_pipeline_viewport_state.scissorCount = 1;
|
|
swap_apply_gamma_pipeline_viewport_state.pScissors = nullptr;
|
|
|
|
VkPipelineRasterizationStateCreateInfo
|
|
swap_apply_gamma_pipeline_rasterization_state = {};
|
|
swap_apply_gamma_pipeline_rasterization_state.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_rasterization_state.polygonMode =
|
|
VK_POLYGON_MODE_FILL;
|
|
swap_apply_gamma_pipeline_rasterization_state.cullMode = VK_CULL_MODE_NONE;
|
|
swap_apply_gamma_pipeline_rasterization_state.frontFace =
|
|
VK_FRONT_FACE_CLOCKWISE;
|
|
swap_apply_gamma_pipeline_rasterization_state.lineWidth = 1.0f;
|
|
|
|
VkPipelineMultisampleStateCreateInfo
|
|
swap_apply_gamma_pipeline_multisample_state = {};
|
|
swap_apply_gamma_pipeline_multisample_state.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_multisample_state.rasterizationSamples =
|
|
VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
VkPipelineColorBlendAttachmentState
|
|
swap_apply_gamma_pipeline_color_blend_attachment_state = {};
|
|
swap_apply_gamma_pipeline_color_blend_attachment_state.colorWriteMask =
|
|
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
|
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
VkPipelineColorBlendStateCreateInfo
|
|
swap_apply_gamma_pipeline_color_blend_state = {};
|
|
swap_apply_gamma_pipeline_color_blend_state.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_color_blend_state.attachmentCount = 1;
|
|
swap_apply_gamma_pipeline_color_blend_state.pAttachments =
|
|
&swap_apply_gamma_pipeline_color_blend_attachment_state;
|
|
|
|
static constexpr VkDynamicState kSwapApplyGammaPipelineDynamicStates[] = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR,
|
|
};
|
|
VkPipelineDynamicStateCreateInfo swap_apply_gamma_pipeline_dynamic_state;
|
|
swap_apply_gamma_pipeline_dynamic_state.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_dynamic_state.pNext = nullptr;
|
|
swap_apply_gamma_pipeline_dynamic_state.flags = 0;
|
|
swap_apply_gamma_pipeline_dynamic_state.dynamicStateCount =
|
|
uint32_t(xe::countof(kSwapApplyGammaPipelineDynamicStates));
|
|
swap_apply_gamma_pipeline_dynamic_state.pDynamicStates =
|
|
kSwapApplyGammaPipelineDynamicStates;
|
|
|
|
VkGraphicsPipelineCreateInfo swap_apply_gamma_pipeline_create_info;
|
|
swap_apply_gamma_pipeline_create_info.sType =
|
|
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
swap_apply_gamma_pipeline_create_info.pNext = nullptr;
|
|
swap_apply_gamma_pipeline_create_info.flags = 0;
|
|
swap_apply_gamma_pipeline_create_info.stageCount =
|
|
uint32_t(xe::countof(swap_apply_gamma_pipeline_stages));
|
|
swap_apply_gamma_pipeline_create_info.pStages =
|
|
swap_apply_gamma_pipeline_stages;
|
|
swap_apply_gamma_pipeline_create_info.pVertexInputState =
|
|
&swap_apply_gamma_pipeline_vertex_input_state;
|
|
swap_apply_gamma_pipeline_create_info.pInputAssemblyState =
|
|
&swap_apply_gamma_pipeline_input_assembly_state;
|
|
swap_apply_gamma_pipeline_create_info.pTessellationState = nullptr;
|
|
swap_apply_gamma_pipeline_create_info.pViewportState =
|
|
&swap_apply_gamma_pipeline_viewport_state;
|
|
swap_apply_gamma_pipeline_create_info.pRasterizationState =
|
|
&swap_apply_gamma_pipeline_rasterization_state;
|
|
swap_apply_gamma_pipeline_create_info.pMultisampleState =
|
|
&swap_apply_gamma_pipeline_multisample_state;
|
|
swap_apply_gamma_pipeline_create_info.pDepthStencilState = nullptr;
|
|
swap_apply_gamma_pipeline_create_info.pColorBlendState =
|
|
&swap_apply_gamma_pipeline_color_blend_state;
|
|
swap_apply_gamma_pipeline_create_info.pDynamicState =
|
|
&swap_apply_gamma_pipeline_dynamic_state;
|
|
swap_apply_gamma_pipeline_create_info.layout =
|
|
swap_apply_gamma_pipeline_layout_;
|
|
swap_apply_gamma_pipeline_create_info.renderPass =
|
|
swap_apply_gamma_render_pass_;
|
|
swap_apply_gamma_pipeline_create_info.subpass = 0;
|
|
swap_apply_gamma_pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
|
|
swap_apply_gamma_pipeline_create_info.basePipelineIndex = -1;
|
|
swap_apply_gamma_pipeline_stages[1].module =
|
|
swap_apply_gamma_pixel_shaders[kSwapApplyGammaPixelShader256EntryTable];
|
|
VkResult swap_apply_gamma_pipeline_256_entry_table_create_result =
|
|
dfn.vkCreateGraphicsPipelines(
|
|
device, VK_NULL_HANDLE, 1, &swap_apply_gamma_pipeline_create_info,
|
|
nullptr, &swap_apply_gamma_256_entry_table_pipeline_);
|
|
swap_apply_gamma_pipeline_stages[1].module =
|
|
swap_apply_gamma_pixel_shaders[kSwapApplyGammaPixelShaderPWL];
|
|
VkResult swap_apply_gamma_pipeline_pwl_create_result =
|
|
dfn.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1,
|
|
&swap_apply_gamma_pipeline_create_info,
|
|
nullptr, &swap_apply_gamma_pwl_pipeline_);
|
|
dfn.vkDestroyShaderModule(device, swap_apply_gamma_pipeline_stages[0].module,
|
|
nullptr);
|
|
for (VkShaderModule swap_apply_gamma_pixel_shader :
|
|
swap_apply_gamma_pixel_shaders) {
|
|
assert_true(swap_apply_gamma_pixel_shader != VK_NULL_HANDLE);
|
|
dfn.vkDestroyShaderModule(device, swap_apply_gamma_pixel_shader, nullptr);
|
|
}
|
|
if (swap_apply_gamma_pipeline_256_entry_table_create_result != VK_SUCCESS ||
|
|
swap_apply_gamma_pipeline_pwl_create_result != VK_SUCCESS) {
|
|
XELOGE("Failed to create the gamma ramp application pipelines");
|
|
return false;
|
|
}
|
|
|
|
// Initialize the ZPD occlusion query pool and resources.
|
|
zpd_host_query_pool_ = std::make_unique<VulkanZPDQueryPool>();
|
|
EnsureZPDQueryResources();
|
|
|
|
// Just not to expose uninitialized memory.
|
|
std::memset(&system_constants_, 0, sizeof(system_constants_));
|
|
// ZPD FSI counter uses UINT32_MAX as its skip sentinel outside query draws.
|
|
system_constants_.zpd_fsi_counter_index = UINT32_MAX;
|
|
zpd_fsi_counter_index_force_update_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void VulkanCommandProcessor::ShutdownContext() {
|
|
AwaitAllQueueOperationsCompletion();
|
|
|
|
ShutdownZPDQueryResources();
|
|
zpd_host_query_pool_.reset();
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
DestroyScratchBuffer();
|
|
|
|
for (SwapFramebuffer& swap_framebuffer : swap_framebuffers_) {
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyFramebuffer, device,
|
|
swap_framebuffer.framebuffer);
|
|
}
|
|
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
|
|
swap_apply_gamma_pwl_pipeline_);
|
|
ui::vulkan::util::DestroyAndNullHandle(
|
|
dfn.vkDestroyPipeline, device,
|
|
swap_apply_gamma_256_entry_table_pipeline_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device,
|
|
swap_apply_gamma_render_pass_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, device,
|
|
swap_apply_gamma_pipeline_layout_);
|
|
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device,
|
|
swap_descriptor_pool_);
|
|
|
|
ui::vulkan::util::DestroyAndNullHandle(
|
|
dfn.vkDestroyDescriptorSetLayout, device,
|
|
swap_descriptor_set_layout_uniform_texel_buffer_);
|
|
ui::vulkan::util::DestroyAndNullHandle(
|
|
dfn.vkDestroyDescriptorSetLayout, device,
|
|
swap_descriptor_set_layout_sampled_image_);
|
|
for (VkBufferView& gamma_ramp_buffer_view : gamma_ramp_buffer_views_) {
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBufferView, device,
|
|
gamma_ramp_buffer_view);
|
|
}
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
gamma_ramp_upload_buffer_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
gamma_ramp_upload_buffer_memory_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
gamma_ramp_buffer_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
gamma_ramp_buffer_memory_);
|
|
|
|
// Clean up all readback buffers.
|
|
for (auto& pair : readback_buffers_) {
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
pair.second.buffers[0]);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
pair.second.memories[0]);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
pair.second.buffers[1]);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
pair.second.memories[1]);
|
|
}
|
|
readback_buffers_.clear();
|
|
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
memexport_readback_buffer_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
memexport_readback_buffer_memory_);
|
|
memexport_readback_buffer_size_ = 0;
|
|
|
|
ui::vulkan::util::DestroyAndNullHandle(
|
|
dfn.vkDestroyDescriptorPool, device,
|
|
shared_memory_and_edram_descriptor_pool_);
|
|
zpd_fsi_counter_descriptor_buffer_ = VK_NULL_HANDLE;
|
|
zpd_fsi_counter_descriptor_range_ = 0;
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
zpd_fsi_counter_sink_buffer_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
zpd_fsi_counter_sink_buffer_memory_);
|
|
|
|
texture_cache_.reset();
|
|
|
|
pipeline_cache_.reset();
|
|
|
|
render_target_cache_.reset();
|
|
|
|
primitive_processor_.reset();
|
|
|
|
shared_memory_.reset();
|
|
|
|
ClearTransientDescriptorPools();
|
|
|
|
for (const auto& pipeline_layout_pair : pipeline_layouts_) {
|
|
dfn.vkDestroyPipelineLayout(
|
|
device, pipeline_layout_pair.second.GetPipelineLayout(), nullptr);
|
|
}
|
|
pipeline_layouts_.clear();
|
|
for (const auto& descriptor_set_layout_pair :
|
|
descriptor_set_layouts_textures_) {
|
|
dfn.vkDestroyDescriptorSetLayout(device, descriptor_set_layout_pair.second,
|
|
nullptr);
|
|
}
|
|
descriptor_set_layouts_textures_.clear();
|
|
|
|
ui::vulkan::util::DestroyAndNullHandle(
|
|
dfn.vkDestroyDescriptorSetLayout, device,
|
|
descriptor_set_layout_shared_memory_and_edram_);
|
|
for (VkDescriptorSetLayout& descriptor_set_layout_single_transient :
|
|
descriptor_set_layouts_single_transient_) {
|
|
ui::vulkan::util::DestroyAndNullHandle(
|
|
dfn.vkDestroyDescriptorSetLayout, device,
|
|
descriptor_set_layout_single_transient);
|
|
}
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout,
|
|
device,
|
|
descriptor_set_layout_constants_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout,
|
|
device, descriptor_set_layout_empty_);
|
|
|
|
uniform_buffer_pool_.reset();
|
|
|
|
sparse_bind_wait_stage_mask_ = 0;
|
|
sparse_buffer_binds_.clear();
|
|
sparse_memory_binds_.clear();
|
|
|
|
deferred_command_buffer_.Reset();
|
|
for (const auto& command_buffer_pair : command_buffers_submitted_) {
|
|
dfn.vkDestroyCommandPool(device, command_buffer_pair.second.pool, nullptr);
|
|
}
|
|
command_buffers_submitted_.clear();
|
|
for (const CommandBuffer& command_buffer : command_buffers_writable_) {
|
|
dfn.vkDestroyCommandPool(device, command_buffer.pool, nullptr);
|
|
}
|
|
command_buffers_writable_.clear();
|
|
|
|
for (const auto& destroy_pair : destroy_framebuffers_) {
|
|
dfn.vkDestroyFramebuffer(device, destroy_pair.second, nullptr);
|
|
}
|
|
destroy_framebuffers_.clear();
|
|
for (const auto& destroy_pair : destroy_buffers_) {
|
|
dfn.vkDestroyBuffer(device, destroy_pair.second, nullptr);
|
|
}
|
|
destroy_buffers_.clear();
|
|
for (const auto& destroy_pair : destroy_memory_) {
|
|
dfn.vkFreeMemory(device, destroy_pair.second, nullptr);
|
|
}
|
|
destroy_memory_.clear();
|
|
|
|
std::memset(closed_frame_submissions_, 0, sizeof(closed_frame_submissions_));
|
|
frame_completed_ = 0;
|
|
frame_current_ = 1;
|
|
frame_open_ = false;
|
|
|
|
for (const auto& semaphore : submissions_in_flight_semaphores_) {
|
|
dfn.vkDestroySemaphore(device, semaphore.second, nullptr);
|
|
}
|
|
submissions_in_flight_semaphores_.clear();
|
|
current_submission_wait_stage_masks_.clear();
|
|
for (VkSemaphore semaphore : current_submission_wait_semaphores_) {
|
|
dfn.vkDestroySemaphore(device, semaphore, nullptr);
|
|
}
|
|
current_submission_wait_semaphores_.clear();
|
|
submission_open_ = false;
|
|
|
|
for (VkSemaphore semaphore : semaphores_free_) {
|
|
dfn.vkDestroySemaphore(device, semaphore, nullptr);
|
|
}
|
|
semaphores_free_.clear();
|
|
|
|
device_lost_ = false;
|
|
|
|
CommandProcessor::ShutdownContext();
|
|
}
|
|
|
|
void VulkanCommandProcessor::WriteRegister(uint32_t index, uint32_t value) {
|
|
CommandProcessor::WriteRegister(index, value);
|
|
|
|
if (index >= XE_GPU_REG_SHADER_CONSTANT_000_X &&
|
|
index <= XE_GPU_REG_SHADER_CONSTANT_511_W) {
|
|
if (frame_open_) {
|
|
uint32_t float_constant_index =
|
|
(index - XE_GPU_REG_SHADER_CONSTANT_000_X) >> 2;
|
|
if (float_constant_index >= 256) {
|
|
float_constant_index -= 256;
|
|
if (current_float_constant_map_pixel_[float_constant_index >> 6] &
|
|
(1ull << (float_constant_index & 63))) {
|
|
current_constant_buffers_up_to_date_ &= ~(
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatPixel);
|
|
}
|
|
} else {
|
|
if (current_float_constant_map_vertex_[float_constant_index >> 6] &
|
|
(1ull << (float_constant_index & 63))) {
|
|
current_constant_buffers_up_to_date_ &= ~(
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatVertex);
|
|
}
|
|
}
|
|
}
|
|
} else if (index >= XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031 &&
|
|
index <= XE_GPU_REG_SHADER_CONSTANT_LOOP_31) {
|
|
current_constant_buffers_up_to_date_ &=
|
|
~(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferBoolLoop);
|
|
} else if (index >= XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 &&
|
|
index <= XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5) {
|
|
current_constant_buffers_up_to_date_ &=
|
|
~(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFetch);
|
|
if (texture_cache_) {
|
|
texture_cache_->TextureFetchConstantWritten(
|
|
(index - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) / 6);
|
|
}
|
|
}
|
|
}
|
|
void VulkanCommandProcessor::WriteRegistersFromMem(uint32_t start_index,
|
|
uint32_t* base,
|
|
uint32_t num_registers) {
|
|
for (uint32_t i = 0; i < num_registers; ++i) {
|
|
uint32_t data = xe::load_and_swap<uint32_t>(base + i);
|
|
VulkanCommandProcessor::WriteRegister(start_index + i, data);
|
|
}
|
|
}
|
|
void VulkanCommandProcessor::SparseBindBuffer(
|
|
VkBuffer buffer, uint32_t bind_count, const VkSparseMemoryBind* binds,
|
|
VkPipelineStageFlags wait_stage_mask) {
|
|
if (!bind_count) {
|
|
return;
|
|
}
|
|
SparseBufferBind& buffer_bind = sparse_buffer_binds_.emplace_back();
|
|
buffer_bind.buffer = buffer;
|
|
buffer_bind.bind_offset = sparse_memory_binds_.size();
|
|
buffer_bind.bind_count = bind_count;
|
|
sparse_memory_binds_.reserve(sparse_memory_binds_.size() + bind_count);
|
|
sparse_memory_binds_.insert(sparse_memory_binds_.end(), binds,
|
|
binds + bind_count);
|
|
sparse_bind_wait_stage_mask_ |= wait_stage_mask;
|
|
}
|
|
|
|
void VulkanCommandProcessor::OnGammaRamp256EntryTableValueWritten() {
|
|
gamma_ramp_256_entry_table_current_frame_ = UINT32_MAX;
|
|
}
|
|
|
|
void VulkanCommandProcessor::OnGammaRampPWLValueWritten() {
|
|
gamma_ramp_pwl_current_frame_ = UINT32_MAX;
|
|
}
|
|
|
|
void VulkanCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
|
|
uint32_t frontbuffer_width,
|
|
uint32_t frontbuffer_height) {
|
|
SCOPE_profile_cpu_f("gpu");
|
|
|
|
ui::Presenter* presenter = graphics_system_->presenter();
|
|
if (!presenter) {
|
|
return;
|
|
}
|
|
|
|
// In case the swap command is the only one in the frame.
|
|
if (!BeginSubmission(true)) {
|
|
return;
|
|
}
|
|
|
|
// Obtaining the actual front buffer size to pass to RefreshGuestOutput,
|
|
// resolution-scaled if it's a resolve destination, or not otherwise.
|
|
uint32_t frontbuffer_width_scaled, frontbuffer_height_scaled;
|
|
xenos::TextureFormat frontbuffer_format;
|
|
VkImageView swap_texture_view = texture_cache_->RequestSwapTexture(
|
|
frontbuffer_width_scaled, frontbuffer_height_scaled, frontbuffer_format);
|
|
if (swap_texture_view == VK_NULL_HANDLE) {
|
|
return;
|
|
}
|
|
|
|
auto aspect = graphics_system_->GetScaledAspectRatio();
|
|
|
|
presenter->RefreshGuestOutput(
|
|
frontbuffer_width_scaled, frontbuffer_height_scaled, aspect.first,
|
|
aspect.second,
|
|
[this, frontbuffer_width_scaled, frontbuffer_height_scaled,
|
|
frontbuffer_format, swap_texture_view](
|
|
ui::Presenter::GuestOutputRefreshContext& context) -> bool {
|
|
// In case the swap command is the only one in the frame.
|
|
if (!BeginSubmission(true)) {
|
|
return false;
|
|
}
|
|
|
|
auto& vulkan_context = static_cast<
|
|
ui::vulkan::VulkanPresenter::VulkanGuestOutputRefreshContext&>(
|
|
context);
|
|
uint64_t guest_output_image_version = vulkan_context.image_version();
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn =
|
|
vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
uint32_t swap_frame_index =
|
|
uint32_t(frame_current_ % kMaxFramesInFlight);
|
|
|
|
// This is according to D3D::InitializePresentationParameters from a
|
|
// game executable, which initializes the 256-entry table gamma ramp for
|
|
// 8_8_8_8 output and the PWL gamma ramp for 2_10_10_10.
|
|
// TODO(Triang3l): Choose between the table and PWL based on
|
|
// DC_LUTA_CONTROL, support both for all formats (and also different
|
|
// increments for PWL).
|
|
bool use_pwl_gamma_ramp =
|
|
frontbuffer_format == xenos::TextureFormat::k_2_10_10_10 ||
|
|
frontbuffer_format ==
|
|
xenos::TextureFormat::k_2_10_10_10_AS_16_16_16_16;
|
|
|
|
// TODO(Triang3l): FXAA can result in more than 8 bits of precision.
|
|
context.SetIs8bpc(!use_pwl_gamma_ramp);
|
|
|
|
// Update the gamma ramp if it's out of date.
|
|
uint32_t& gamma_ramp_frame_index_ref =
|
|
use_pwl_gamma_ramp ? gamma_ramp_pwl_current_frame_
|
|
: gamma_ramp_256_entry_table_current_frame_;
|
|
if (gamma_ramp_frame_index_ref == UINT32_MAX) {
|
|
constexpr uint32_t kGammaRampSize256EntryTable =
|
|
sizeof(uint32_t) * 256;
|
|
constexpr uint32_t kGammaRampSizePWL = sizeof(uint16_t) * 2 * 3 * 128;
|
|
constexpr uint32_t kGammaRampSize =
|
|
kGammaRampSize256EntryTable + kGammaRampSizePWL;
|
|
uint32_t gamma_ramp_offset_in_frame =
|
|
use_pwl_gamma_ramp ? kGammaRampSize256EntryTable : 0;
|
|
uint32_t gamma_ramp_upload_offset =
|
|
kGammaRampSize * swap_frame_index + gamma_ramp_offset_in_frame;
|
|
uint32_t gamma_ramp_size = use_pwl_gamma_ramp
|
|
? kGammaRampSizePWL
|
|
: kGammaRampSize256EntryTable;
|
|
void* gamma_ramp_frame_upload =
|
|
reinterpret_cast<uint8_t*>(gamma_ramp_upload_mapping_) +
|
|
gamma_ramp_upload_offset;
|
|
if (std::endian::native != std::endian::little &&
|
|
use_pwl_gamma_ramp) {
|
|
// R16G16 is first R16, where the shader expects the base, and
|
|
// second G16, where the delta should be, but gamma_ramp_pwl_rgb()
|
|
// is an array of 32-bit DC_LUT_PWL_DATA registers - swap 16 bits in
|
|
// each 32.
|
|
auto gamma_ramp_pwl_upload =
|
|
reinterpret_cast<reg::DC_LUT_PWL_DATA*>(
|
|
gamma_ramp_frame_upload);
|
|
const reg::DC_LUT_PWL_DATA* gamma_ramp_pwl = gamma_ramp_pwl_rgb();
|
|
for (size_t i = 0; i < 128 * 3; ++i) {
|
|
reg::DC_LUT_PWL_DATA& gamma_ramp_pwl_upload_entry =
|
|
gamma_ramp_pwl_upload[i];
|
|
reg::DC_LUT_PWL_DATA gamma_ramp_pwl_entry = gamma_ramp_pwl[i];
|
|
gamma_ramp_pwl_upload_entry.base = gamma_ramp_pwl_entry.delta;
|
|
gamma_ramp_pwl_upload_entry.delta = gamma_ramp_pwl_entry.base;
|
|
}
|
|
} else {
|
|
std::memcpy(
|
|
gamma_ramp_frame_upload,
|
|
use_pwl_gamma_ramp
|
|
? static_cast<const void*>(gamma_ramp_pwl_rgb())
|
|
: static_cast<const void*>(gamma_ramp_256_entry_table()),
|
|
gamma_ramp_size);
|
|
}
|
|
bool gamma_ramp_has_upload_buffer =
|
|
gamma_ramp_upload_buffer_memory_ != VK_NULL_HANDLE;
|
|
ui::vulkan::util::FlushMappedMemoryRange(
|
|
vulkan_device,
|
|
gamma_ramp_has_upload_buffer ? gamma_ramp_upload_buffer_memory_
|
|
: gamma_ramp_buffer_memory_,
|
|
gamma_ramp_upload_memory_type_, gamma_ramp_upload_offset,
|
|
gamma_ramp_upload_memory_size_, gamma_ramp_size);
|
|
if (gamma_ramp_has_upload_buffer) {
|
|
// Copy from the host-visible buffer to the device-local one.
|
|
PushBufferMemoryBarrier(
|
|
gamma_ramp_buffer_, gamma_ramp_offset_in_frame, gamma_ramp_size,
|
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_READ_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED, false);
|
|
SubmitBarriers(true);
|
|
VkBufferCopy gamma_ramp_buffer_copy;
|
|
gamma_ramp_buffer_copy.srcOffset = gamma_ramp_upload_offset;
|
|
gamma_ramp_buffer_copy.dstOffset = gamma_ramp_offset_in_frame;
|
|
gamma_ramp_buffer_copy.size = gamma_ramp_size;
|
|
deferred_command_buffer_.CmdVkCopyBuffer(gamma_ramp_upload_buffer_,
|
|
gamma_ramp_buffer_, 1,
|
|
&gamma_ramp_buffer_copy);
|
|
PushBufferMemoryBarrier(
|
|
gamma_ramp_buffer_, gamma_ramp_offset_in_frame, gamma_ramp_size,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, false);
|
|
}
|
|
// The device-local, but not host-visible, gamma ramp buffer doesn't
|
|
// have per-frame sets of gamma ramps.
|
|
gamma_ramp_frame_index_ref =
|
|
gamma_ramp_has_upload_buffer ? 0 : swap_frame_index;
|
|
}
|
|
|
|
// Make sure a framebuffer is available for the current guest output
|
|
// image version.
|
|
size_t swap_framebuffer_index = SIZE_MAX;
|
|
size_t swap_framebuffer_new_index = SIZE_MAX;
|
|
// Try to find the existing framebuffer for the current guest output
|
|
// image version, or an unused (without an existing framebuffer, or with
|
|
// one, but that has never actually been used dynamically) slot.
|
|
for (size_t i = 0; i < swap_framebuffers_.size(); ++i) {
|
|
const SwapFramebuffer& existing_swap_framebuffer =
|
|
swap_framebuffers_[i];
|
|
if (existing_swap_framebuffer.framebuffer != VK_NULL_HANDLE &&
|
|
existing_swap_framebuffer.version == guest_output_image_version) {
|
|
swap_framebuffer_index = i;
|
|
break;
|
|
}
|
|
if (existing_swap_framebuffer.framebuffer == VK_NULL_HANDLE ||
|
|
!existing_swap_framebuffer.last_submission) {
|
|
swap_framebuffer_new_index = i;
|
|
}
|
|
}
|
|
if (swap_framebuffer_index == SIZE_MAX) {
|
|
if (swap_framebuffer_new_index == SIZE_MAX) {
|
|
// Replace the earliest used framebuffer.
|
|
swap_framebuffer_new_index = 0;
|
|
for (size_t i = 1; i < swap_framebuffers_.size(); ++i) {
|
|
if (swap_framebuffers_[i].last_submission <
|
|
swap_framebuffers_[swap_framebuffer_new_index]
|
|
.last_submission) {
|
|
swap_framebuffer_new_index = i;
|
|
}
|
|
}
|
|
}
|
|
swap_framebuffer_index = swap_framebuffer_new_index;
|
|
SwapFramebuffer& new_swap_framebuffer =
|
|
swap_framebuffers_[swap_framebuffer_new_index];
|
|
if (new_swap_framebuffer.framebuffer != VK_NULL_HANDLE) {
|
|
if (GetCompletedSubmission() >=
|
|
new_swap_framebuffer.last_submission) {
|
|
dfn.vkDestroyFramebuffer(device, new_swap_framebuffer.framebuffer,
|
|
nullptr);
|
|
} else {
|
|
destroy_framebuffers_.emplace_back(
|
|
new_swap_framebuffer.last_submission,
|
|
new_swap_framebuffer.framebuffer);
|
|
}
|
|
new_swap_framebuffer.framebuffer = VK_NULL_HANDLE;
|
|
}
|
|
VkImageView guest_output_image_view = vulkan_context.image_view();
|
|
VkFramebufferCreateInfo swap_framebuffer_create_info;
|
|
swap_framebuffer_create_info.sType =
|
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
swap_framebuffer_create_info.pNext = nullptr;
|
|
swap_framebuffer_create_info.flags = 0;
|
|
swap_framebuffer_create_info.renderPass =
|
|
swap_apply_gamma_render_pass_;
|
|
swap_framebuffer_create_info.attachmentCount = 1;
|
|
swap_framebuffer_create_info.pAttachments = &guest_output_image_view;
|
|
swap_framebuffer_create_info.width = frontbuffer_width_scaled;
|
|
swap_framebuffer_create_info.height = frontbuffer_height_scaled;
|
|
swap_framebuffer_create_info.layers = 1;
|
|
if (dfn.vkCreateFramebuffer(
|
|
device, &swap_framebuffer_create_info, nullptr,
|
|
&new_swap_framebuffer.framebuffer) != VK_SUCCESS) {
|
|
XELOGE("Failed to create the Vulkan framebuffer for presentation");
|
|
return false;
|
|
}
|
|
new_swap_framebuffer.version = guest_output_image_version;
|
|
// The actual submission index will be set if the framebuffer is
|
|
// actually used, not dropped due to some error.
|
|
new_swap_framebuffer.last_submission = 0;
|
|
}
|
|
|
|
if (vulkan_context.image_ever_written_previously()) {
|
|
// Insert a barrier after the last presenter's usage of the guest
|
|
// output image. Will be overwriting all the contents, so oldLayout
|
|
// layout is UNDEFINED. The render pass will do the layout transition,
|
|
// but newLayout must not be UNDEFINED.
|
|
PushImageMemoryBarrier(
|
|
vulkan_context.image(),
|
|
ui::vulkan::util::InitializeSubresourceRange(),
|
|
ui::vulkan::VulkanPresenter::kGuestOutputInternalStageMask,
|
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
ui::vulkan::VulkanPresenter::kGuestOutputInternalAccessMask,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
|
}
|
|
|
|
// End the current render pass before inserting barriers and starting a
|
|
// new one, and insert the barrier.
|
|
SubmitBarriers(true);
|
|
|
|
SwapFramebuffer& swap_framebuffer =
|
|
swap_framebuffers_[swap_framebuffer_index];
|
|
swap_framebuffer.last_submission = GetCurrentSubmission();
|
|
|
|
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 = swap_apply_gamma_render_pass_;
|
|
render_pass_begin_info.framebuffer = swap_framebuffer.framebuffer;
|
|
render_pass_begin_info.renderArea.offset.x = 0;
|
|
render_pass_begin_info.renderArea.offset.y = 0;
|
|
render_pass_begin_info.renderArea.extent.width =
|
|
frontbuffer_width_scaled;
|
|
render_pass_begin_info.renderArea.extent.height =
|
|
frontbuffer_height_scaled;
|
|
render_pass_begin_info.clearValueCount = 0;
|
|
render_pass_begin_info.pClearValues = nullptr;
|
|
deferred_command_buffer_.CmdVkBeginRenderPass(
|
|
&render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
|
current_render_pass_ = swap_apply_gamma_render_pass_;
|
|
current_framebuffer_ =
|
|
nullptr; // Not a render target cache framebuffer
|
|
|
|
VkViewport viewport;
|
|
viewport.x = 0.0f;
|
|
viewport.y = 0.0f;
|
|
viewport.width = float(frontbuffer_width_scaled);
|
|
viewport.height = float(frontbuffer_height_scaled);
|
|
viewport.minDepth = 0.0f;
|
|
viewport.maxDepth = 1.0f;
|
|
SetViewport(viewport);
|
|
VkRect2D scissor;
|
|
scissor.offset.x = 0;
|
|
scissor.offset.y = 0;
|
|
scissor.extent.width = frontbuffer_width_scaled;
|
|
scissor.extent.height = frontbuffer_height_scaled;
|
|
SetScissor(scissor);
|
|
|
|
BindExternalGraphicsPipeline(
|
|
use_pwl_gamma_ramp ? swap_apply_gamma_pwl_pipeline_
|
|
: swap_apply_gamma_256_entry_table_pipeline_);
|
|
|
|
VkDescriptorSet swap_descriptor_source =
|
|
swap_descriptors_source_[swap_frame_index];
|
|
VkDescriptorImageInfo swap_descriptor_source_image_info;
|
|
swap_descriptor_source_image_info.sampler = VK_NULL_HANDLE;
|
|
swap_descriptor_source_image_info.imageView = swap_texture_view;
|
|
swap_descriptor_source_image_info.imageLayout =
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
VkWriteDescriptorSet swap_descriptor_source_write;
|
|
swap_descriptor_source_write.sType =
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
swap_descriptor_source_write.pNext = nullptr;
|
|
swap_descriptor_source_write.dstSet = swap_descriptor_source;
|
|
swap_descriptor_source_write.dstBinding = 0;
|
|
swap_descriptor_source_write.dstArrayElement = 0;
|
|
swap_descriptor_source_write.descriptorCount = 1;
|
|
swap_descriptor_source_write.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
swap_descriptor_source_write.pImageInfo =
|
|
&swap_descriptor_source_image_info;
|
|
swap_descriptor_source_write.pBufferInfo = nullptr;
|
|
swap_descriptor_source_write.pTexelBufferView = nullptr;
|
|
dfn.vkUpdateDescriptorSets(device, 1, &swap_descriptor_source_write, 0,
|
|
nullptr);
|
|
|
|
std::array<VkDescriptorSet, kSwapApplyGammaDescriptorSetCount>
|
|
swap_descriptor_sets{};
|
|
swap_descriptor_sets[kSwapApplyGammaDescriptorSetRamp] =
|
|
swap_descriptors_gamma_ramp_[2 * gamma_ramp_frame_index_ref +
|
|
uint32_t(use_pwl_gamma_ramp)];
|
|
swap_descriptor_sets[kSwapApplyGammaDescriptorSetSource] =
|
|
swap_descriptor_source;
|
|
// TODO(Triang3l): Red / blue swap without imageViewFormatSwizzle.
|
|
deferred_command_buffer_.CmdVkBindDescriptorSets(
|
|
VK_PIPELINE_BIND_POINT_GRAPHICS, swap_apply_gamma_pipeline_layout_,
|
|
0, uint32_t(swap_descriptor_sets.size()),
|
|
swap_descriptor_sets.data(), 0, nullptr);
|
|
|
|
deferred_command_buffer_.CmdVkDraw(3, 1, 0, 0);
|
|
|
|
deferred_command_buffer_.CmdVkEndRenderPass();
|
|
current_render_pass_ = VK_NULL_HANDLE;
|
|
|
|
// Insert the release barrier.
|
|
PushImageMemoryBarrier(
|
|
vulkan_context.image(),
|
|
ui::vulkan::util::InitializeSubresourceRange(),
|
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
ui::vulkan::VulkanPresenter::kGuestOutputInternalStageMask,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
ui::vulkan::VulkanPresenter::kGuestOutputInternalAccessMask,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
ui::vulkan::VulkanPresenter::kGuestOutputInternalLayout);
|
|
|
|
// Need to submit all the commands before giving the image back to the
|
|
// presenter so it can submit its own commands for displaying it to the
|
|
// queue, and also need to submit the release barrier.
|
|
EndSubmission(true);
|
|
return true;
|
|
});
|
|
|
|
// End the frame even if did not present for any reason (the image refresher
|
|
// was not called), to prevent leaking per-frame resources.
|
|
EndSubmission(true);
|
|
}
|
|
|
|
bool VulkanCommandProcessor::PushBufferMemoryBarrier(
|
|
VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size,
|
|
VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
|
|
VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask,
|
|
uint32_t src_queue_family_index, uint32_t dst_queue_family_index,
|
|
bool skip_if_equal) {
|
|
if (skip_if_equal && src_stage_mask == dst_stage_mask &&
|
|
src_access_mask == dst_access_mask &&
|
|
src_queue_family_index == dst_queue_family_index) {
|
|
return false;
|
|
}
|
|
|
|
// Separate different barriers for overlapping buffer ranges into different
|
|
// pipeline barrier commands.
|
|
for (const VkBufferMemoryBarrier& other_buffer_memory_barrier :
|
|
pending_barriers_buffer_memory_barriers_) {
|
|
if (other_buffer_memory_barrier.buffer != buffer ||
|
|
(size != VK_WHOLE_SIZE &&
|
|
offset + size <= other_buffer_memory_barrier.offset) ||
|
|
(other_buffer_memory_barrier.size != VK_WHOLE_SIZE &&
|
|
other_buffer_memory_barrier.offset +
|
|
other_buffer_memory_barrier.size <=
|
|
offset)) {
|
|
continue;
|
|
}
|
|
if (other_buffer_memory_barrier.offset == offset &&
|
|
other_buffer_memory_barrier.size == size &&
|
|
other_buffer_memory_barrier.srcAccessMask == src_access_mask &&
|
|
other_buffer_memory_barrier.dstAccessMask == dst_access_mask &&
|
|
other_buffer_memory_barrier.srcQueueFamilyIndex ==
|
|
src_queue_family_index &&
|
|
other_buffer_memory_barrier.dstQueueFamilyIndex ==
|
|
dst_queue_family_index) {
|
|
// The barrier is already pending.
|
|
current_pending_barrier_.src_stage_mask |= src_stage_mask;
|
|
current_pending_barrier_.dst_stage_mask |= dst_stage_mask;
|
|
return true;
|
|
}
|
|
SplitPendingBarrier();
|
|
break;
|
|
}
|
|
|
|
current_pending_barrier_.src_stage_mask |= src_stage_mask;
|
|
current_pending_barrier_.dst_stage_mask |= dst_stage_mask;
|
|
VkBufferMemoryBarrier& buffer_memory_barrier =
|
|
pending_barriers_buffer_memory_barriers_.emplace_back();
|
|
buffer_memory_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
|
buffer_memory_barrier.pNext = nullptr;
|
|
buffer_memory_barrier.srcAccessMask = src_access_mask;
|
|
buffer_memory_barrier.dstAccessMask = dst_access_mask;
|
|
buffer_memory_barrier.srcQueueFamilyIndex = src_queue_family_index;
|
|
buffer_memory_barrier.dstQueueFamilyIndex = dst_queue_family_index;
|
|
buffer_memory_barrier.buffer = buffer;
|
|
buffer_memory_barrier.offset = offset;
|
|
buffer_memory_barrier.size = size;
|
|
return true;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::PushImageMemoryBarrier(
|
|
VkImage image, const VkImageSubresourceRange& subresource_range,
|
|
VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
|
|
VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask,
|
|
VkImageLayout old_layout, VkImageLayout new_layout,
|
|
uint32_t src_queue_family_index, uint32_t dst_queue_family_index,
|
|
bool skip_if_equal) {
|
|
if (skip_if_equal && src_stage_mask == dst_stage_mask &&
|
|
src_access_mask == dst_access_mask && old_layout == new_layout &&
|
|
src_queue_family_index == dst_queue_family_index) {
|
|
return false;
|
|
}
|
|
|
|
// Separate different barriers for overlapping image subresource ranges into
|
|
// different pipeline barrier commands.
|
|
for (const VkImageMemoryBarrier& other_image_memory_barrier :
|
|
pending_barriers_image_memory_barriers_) {
|
|
if (other_image_memory_barrier.image != image ||
|
|
!(other_image_memory_barrier.subresourceRange.aspectMask &
|
|
subresource_range.aspectMask) ||
|
|
(subresource_range.levelCount != VK_REMAINING_MIP_LEVELS &&
|
|
subresource_range.baseMipLevel + subresource_range.levelCount <=
|
|
other_image_memory_barrier.subresourceRange.baseMipLevel) ||
|
|
(other_image_memory_barrier.subresourceRange.levelCount !=
|
|
VK_REMAINING_MIP_LEVELS &&
|
|
other_image_memory_barrier.subresourceRange.baseMipLevel +
|
|
other_image_memory_barrier.subresourceRange.levelCount <=
|
|
subresource_range.baseMipLevel) ||
|
|
(subresource_range.layerCount != VK_REMAINING_ARRAY_LAYERS &&
|
|
subresource_range.baseArrayLayer + subresource_range.layerCount <=
|
|
other_image_memory_barrier.subresourceRange.baseArrayLayer) ||
|
|
(other_image_memory_barrier.subresourceRange.layerCount !=
|
|
VK_REMAINING_ARRAY_LAYERS &&
|
|
other_image_memory_barrier.subresourceRange.baseArrayLayer +
|
|
other_image_memory_barrier.subresourceRange.layerCount <=
|
|
subresource_range.baseArrayLayer)) {
|
|
continue;
|
|
}
|
|
if (other_image_memory_barrier.subresourceRange.aspectMask ==
|
|
subresource_range.aspectMask &&
|
|
other_image_memory_barrier.subresourceRange.baseMipLevel ==
|
|
subresource_range.baseMipLevel &&
|
|
other_image_memory_barrier.subresourceRange.levelCount ==
|
|
subresource_range.levelCount &&
|
|
other_image_memory_barrier.subresourceRange.baseArrayLayer ==
|
|
subresource_range.baseArrayLayer &&
|
|
other_image_memory_barrier.subresourceRange.layerCount ==
|
|
subresource_range.layerCount &&
|
|
other_image_memory_barrier.srcAccessMask == src_access_mask &&
|
|
other_image_memory_barrier.dstAccessMask == dst_access_mask &&
|
|
other_image_memory_barrier.oldLayout == old_layout &&
|
|
other_image_memory_barrier.newLayout == new_layout &&
|
|
other_image_memory_barrier.srcQueueFamilyIndex ==
|
|
src_queue_family_index &&
|
|
other_image_memory_barrier.dstQueueFamilyIndex ==
|
|
dst_queue_family_index) {
|
|
// The barrier is already pending.
|
|
current_pending_barrier_.src_stage_mask |= src_stage_mask;
|
|
current_pending_barrier_.dst_stage_mask |= dst_stage_mask;
|
|
return true;
|
|
}
|
|
SplitPendingBarrier();
|
|
break;
|
|
}
|
|
|
|
current_pending_barrier_.src_stage_mask |= src_stage_mask;
|
|
current_pending_barrier_.dst_stage_mask |= dst_stage_mask;
|
|
VkImageMemoryBarrier& image_memory_barrier =
|
|
pending_barriers_image_memory_barriers_.emplace_back();
|
|
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
image_memory_barrier.pNext = nullptr;
|
|
image_memory_barrier.srcAccessMask = src_access_mask;
|
|
image_memory_barrier.dstAccessMask = dst_access_mask;
|
|
image_memory_barrier.oldLayout = old_layout;
|
|
image_memory_barrier.newLayout = new_layout;
|
|
image_memory_barrier.srcQueueFamilyIndex = src_queue_family_index;
|
|
image_memory_barrier.dstQueueFamilyIndex = dst_queue_family_index;
|
|
image_memory_barrier.image = image;
|
|
image_memory_barrier.subresourceRange = subresource_range;
|
|
return true;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::SubmitBarriers(bool force_end_render_pass) {
|
|
assert_true(submission_open_);
|
|
SplitPendingBarrier();
|
|
if (pending_barriers_.empty()) {
|
|
if (force_end_render_pass) {
|
|
EndRenderPass();
|
|
}
|
|
return false;
|
|
}
|
|
EndRenderPass();
|
|
for (auto it = pending_barriers_.cbegin(); it != pending_barriers_.cend();
|
|
++it) {
|
|
auto it_next = std::next(it);
|
|
bool is_last = it_next == pending_barriers_.cend();
|
|
// .data() + offset, not &[offset], for buffer and image barriers, because
|
|
// if there are no buffer or image memory barriers in the last pipeline
|
|
// barriers, the offsets may be equal to the sizes of the vectors.
|
|
deferred_command_buffer_.CmdVkPipelineBarrier(
|
|
it->src_stage_mask ? it->src_stage_mask
|
|
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
|
it->dst_stage_mask ? it->dst_stage_mask
|
|
: VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
|
0, 0, nullptr,
|
|
uint32_t((is_last ? pending_barriers_buffer_memory_barriers_.size()
|
|
: it_next->buffer_memory_barriers_offset) -
|
|
it->buffer_memory_barriers_offset),
|
|
pending_barriers_buffer_memory_barriers_.data() +
|
|
it->buffer_memory_barriers_offset,
|
|
uint32_t((is_last ? pending_barriers_image_memory_barriers_.size()
|
|
: it_next->image_memory_barriers_offset) -
|
|
it->image_memory_barriers_offset),
|
|
pending_barriers_image_memory_barriers_.data() +
|
|
it->image_memory_barriers_offset);
|
|
}
|
|
pending_barriers_.clear();
|
|
pending_barriers_buffer_memory_barriers_.clear();
|
|
pending_barriers_image_memory_barriers_.clear();
|
|
current_pending_barrier_.buffer_memory_barriers_offset = 0;
|
|
current_pending_barrier_.image_memory_barriers_offset = 0;
|
|
return true;
|
|
}
|
|
|
|
void VulkanCommandProcessor::SubmitBarriersAndEnterRenderTargetCacheRenderPass(
|
|
VkRenderPass render_pass,
|
|
const VulkanRenderTargetCache::Framebuffer* framebuffer) {
|
|
SubmitBarriers(false);
|
|
if (current_render_pass_ == render_pass &&
|
|
current_framebuffer_ == framebuffer) {
|
|
return;
|
|
}
|
|
if (current_render_pass_ != VK_NULL_HANDLE) {
|
|
deferred_command_buffer_.CmdVkEndRenderPass();
|
|
}
|
|
current_render_pass_ = render_pass;
|
|
current_framebuffer_ = framebuffer;
|
|
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;
|
|
render_pass_begin_info.framebuffer = framebuffer->framebuffer;
|
|
render_pass_begin_info.renderArea.offset.x = 0;
|
|
render_pass_begin_info.renderArea.offset.y = 0;
|
|
// TODO(Triang3l): Actual dirty width / height in the deferred command
|
|
// buffer.
|
|
render_pass_begin_info.renderArea.extent = framebuffer->host_extent;
|
|
render_pass_begin_info.clearValueCount = 0;
|
|
render_pass_begin_info.pClearValues = nullptr;
|
|
deferred_command_buffer_.CmdVkBeginRenderPass(&render_pass_begin_info,
|
|
VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
// Resume any pending ZPD segment now that the pass is open.
|
|
OpenQuerySegment(false);
|
|
}
|
|
|
|
void VulkanCommandProcessor::EndRenderPass() {
|
|
assert_true(submission_open_);
|
|
|
|
// Close native Vulkan occlusion queries before ending the pass. FSI counter
|
|
// segments don't use vkCmdBeginQuery / vkCmdEndQuery and can stay logically
|
|
// open across render passes.
|
|
if (GetZPDMode() != ZPDMode::kFake && zpd_active_segment_.segment_active &&
|
|
!zpd_active_query_is_fsi_) {
|
|
CloseQuerySegment();
|
|
if (zpd_active_segment_.logical_active) {
|
|
zpd_active_segment_.segment_pending_begin = true;
|
|
}
|
|
}
|
|
|
|
if (current_render_pass_ == VK_NULL_HANDLE) {
|
|
return;
|
|
}
|
|
deferred_command_buffer_.CmdVkEndRenderPass();
|
|
current_render_pass_ = VK_NULL_HANDLE;
|
|
current_framebuffer_ = nullptr;
|
|
}
|
|
|
|
VkDescriptorSet VulkanCommandProcessor::AllocateSingleTransientDescriptor(
|
|
SingleTransientDescriptorLayout transient_descriptor_layout) {
|
|
assert_true(frame_open_);
|
|
VkDescriptorSet descriptor_set;
|
|
std::vector<VkDescriptorSet>& transient_descriptors_free =
|
|
single_transient_descriptors_free_[size_t(transient_descriptor_layout)];
|
|
if (!transient_descriptors_free.empty()) {
|
|
descriptor_set = transient_descriptors_free.back();
|
|
transient_descriptors_free.pop_back();
|
|
} else {
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
bool is_storage_buffer =
|
|
transient_descriptor_layout ==
|
|
SingleTransientDescriptorLayout::kStorageBufferCompute;
|
|
ui::vulkan::LinkedTypeDescriptorSetAllocator&
|
|
transient_descriptor_allocator =
|
|
is_storage_buffer ? transient_descriptor_allocator_storage_buffer_
|
|
: transient_descriptor_allocator_uniform_buffer_;
|
|
VkDescriptorPoolSize descriptor_count;
|
|
descriptor_count.type = is_storage_buffer
|
|
? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
|
|
: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
descriptor_count.descriptorCount = 1;
|
|
descriptor_set = transient_descriptor_allocator.Allocate(
|
|
GetSingleTransientDescriptorLayout(transient_descriptor_layout),
|
|
&descriptor_count, 1);
|
|
if (descriptor_set == VK_NULL_HANDLE) {
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
UsedSingleTransientDescriptor used_descriptor;
|
|
used_descriptor.frame = frame_current_;
|
|
used_descriptor.layout = transient_descriptor_layout;
|
|
used_descriptor.set = descriptor_set;
|
|
single_transient_descriptors_used_.emplace_back(used_descriptor);
|
|
return descriptor_set;
|
|
}
|
|
|
|
VkDescriptorSetLayout VulkanCommandProcessor::GetTextureDescriptorSetLayout(
|
|
bool is_vertex, size_t texture_count, size_t sampler_count) {
|
|
size_t binding_count = texture_count + sampler_count;
|
|
if (!binding_count) {
|
|
return descriptor_set_layout_empty_;
|
|
}
|
|
|
|
TextureDescriptorSetLayoutKey texture_descriptor_set_layout_key;
|
|
texture_descriptor_set_layout_key.texture_count = uint32_t(texture_count);
|
|
texture_descriptor_set_layout_key.sampler_count = uint32_t(sampler_count);
|
|
texture_descriptor_set_layout_key.is_vertex = uint32_t(is_vertex);
|
|
auto it_existing =
|
|
descriptor_set_layouts_textures_.find(texture_descriptor_set_layout_key);
|
|
if (it_existing != descriptor_set_layouts_textures_.end()) {
|
|
return it_existing->second;
|
|
}
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
descriptor_set_layout_bindings_.clear();
|
|
descriptor_set_layout_bindings_.reserve(binding_count);
|
|
VkShaderStageFlags stage_flags =
|
|
is_vertex ? guest_shader_vertex_stages_ : VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
for (size_t i = 0; i < texture_count; ++i) {
|
|
VkDescriptorSetLayoutBinding& descriptor_set_layout_binding =
|
|
descriptor_set_layout_bindings_.emplace_back();
|
|
descriptor_set_layout_binding.binding = uint32_t(i);
|
|
descriptor_set_layout_binding.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
descriptor_set_layout_binding.descriptorCount = 1;
|
|
descriptor_set_layout_binding.stageFlags = stage_flags;
|
|
}
|
|
for (size_t i = 0; i < sampler_count; ++i) {
|
|
VkDescriptorSetLayoutBinding& descriptor_set_layout_binding =
|
|
descriptor_set_layout_bindings_.emplace_back();
|
|
descriptor_set_layout_binding.binding = uint32_t(texture_count + i);
|
|
descriptor_set_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
|
|
descriptor_set_layout_binding.descriptorCount = 1;
|
|
descriptor_set_layout_binding.stageFlags = stage_flags;
|
|
}
|
|
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info;
|
|
descriptor_set_layout_create_info.sType =
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
descriptor_set_layout_create_info.pNext = nullptr;
|
|
descriptor_set_layout_create_info.flags = 0;
|
|
descriptor_set_layout_create_info.bindingCount = uint32_t(binding_count);
|
|
descriptor_set_layout_create_info.pBindings =
|
|
descriptor_set_layout_bindings_.data();
|
|
VkDescriptorSetLayout texture_descriptor_set_layout;
|
|
if (dfn.vkCreateDescriptorSetLayout(
|
|
device, &descriptor_set_layout_create_info, nullptr,
|
|
&texture_descriptor_set_layout) != VK_SUCCESS) {
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
descriptor_set_layouts_textures_.emplace(texture_descriptor_set_layout_key,
|
|
texture_descriptor_set_layout);
|
|
return texture_descriptor_set_layout;
|
|
}
|
|
|
|
const VulkanPipelineCache::PipelineLayoutProvider*
|
|
VulkanCommandProcessor::GetPipelineLayout(size_t texture_count_pixel,
|
|
size_t sampler_count_pixel,
|
|
size_t texture_count_vertex,
|
|
size_t sampler_count_vertex) {
|
|
PipelineLayoutKey pipeline_layout_key;
|
|
pipeline_layout_key.texture_count_pixel = uint16_t(texture_count_pixel);
|
|
pipeline_layout_key.sampler_count_pixel = uint16_t(sampler_count_pixel);
|
|
pipeline_layout_key.texture_count_vertex = uint16_t(texture_count_vertex);
|
|
pipeline_layout_key.sampler_count_vertex = uint16_t(sampler_count_vertex);
|
|
{
|
|
auto it = pipeline_layouts_.find(pipeline_layout_key);
|
|
if (it != pipeline_layouts_.end()) {
|
|
return &it->second;
|
|
}
|
|
}
|
|
|
|
VkDescriptorSetLayout descriptor_set_layout_textures_vertex =
|
|
GetTextureDescriptorSetLayout(true, texture_count_vertex,
|
|
sampler_count_vertex);
|
|
if (descriptor_set_layout_textures_vertex == VK_NULL_HANDLE) {
|
|
XELOGE(
|
|
"Failed to obtain a Vulkan descriptor set layout for {} sampled images "
|
|
"and {} samplers for guest vertex shaders",
|
|
texture_count_vertex, sampler_count_vertex);
|
|
return nullptr;
|
|
}
|
|
VkDescriptorSetLayout descriptor_set_layout_textures_pixel =
|
|
GetTextureDescriptorSetLayout(false, texture_count_pixel,
|
|
sampler_count_pixel);
|
|
if (descriptor_set_layout_textures_pixel == VK_NULL_HANDLE) {
|
|
XELOGE(
|
|
"Failed to obtain a Vulkan descriptor set layout for {} sampled images "
|
|
"and {} samplers for guest pixel shaders",
|
|
texture_count_pixel, sampler_count_pixel);
|
|
return nullptr;
|
|
}
|
|
|
|
VkDescriptorSetLayout
|
|
descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetCount];
|
|
// Immutable layouts.
|
|
descriptor_set_layouts
|
|
[SpirvShaderTranslator::kDescriptorSetSharedMemoryAndEdram] =
|
|
descriptor_set_layout_shared_memory_and_edram_;
|
|
descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetConstants] =
|
|
descriptor_set_layout_constants_;
|
|
// Mutable layouts.
|
|
descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetTexturesVertex] =
|
|
descriptor_set_layout_textures_vertex;
|
|
descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetTexturesPixel] =
|
|
descriptor_set_layout_textures_pixel;
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
VkPipelineLayoutCreateInfo pipeline_layout_create_info;
|
|
pipeline_layout_create_info.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
pipeline_layout_create_info.pNext = nullptr;
|
|
pipeline_layout_create_info.flags = 0;
|
|
pipeline_layout_create_info.setLayoutCount =
|
|
uint32_t(xe::countof(descriptor_set_layouts));
|
|
pipeline_layout_create_info.pSetLayouts = descriptor_set_layouts;
|
|
pipeline_layout_create_info.pushConstantRangeCount = 0;
|
|
pipeline_layout_create_info.pPushConstantRanges = nullptr;
|
|
VkPipelineLayout pipeline_layout;
|
|
if (dfn.vkCreatePipelineLayout(device, &pipeline_layout_create_info, nullptr,
|
|
&pipeline_layout) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"Failed to create a Vulkan pipeline layout for guest drawing with {} "
|
|
"pixel shader and {} vertex shader textures",
|
|
texture_count_pixel, texture_count_vertex);
|
|
return nullptr;
|
|
}
|
|
auto emplaced_pair = pipeline_layouts_.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(pipeline_layout_key),
|
|
std::forward_as_tuple(pipeline_layout,
|
|
descriptor_set_layout_textures_vertex,
|
|
descriptor_set_layout_textures_pixel));
|
|
// unordered_map insertion doesn't invalidate element references.
|
|
return &emplaced_pair.first->second;
|
|
}
|
|
|
|
VulkanCommandProcessor::ScratchBufferAcquisition
|
|
VulkanCommandProcessor::AcquireScratchGpuBuffer(
|
|
VkDeviceSize size, VkPipelineStageFlags initial_stage_mask,
|
|
VkAccessFlags initial_access_mask) {
|
|
assert_true(submission_open_);
|
|
assert_false(scratch_buffer_used_);
|
|
if (!submission_open_ || scratch_buffer_used_ || !size) {
|
|
return ScratchBufferAcquisition();
|
|
}
|
|
|
|
uint64_t submission_current = GetCurrentSubmission();
|
|
|
|
if (scratch_buffer_ != VK_NULL_HANDLE && size <= scratch_buffer_size_) {
|
|
// Already used previously - transition.
|
|
PushBufferMemoryBarrier(scratch_buffer_, 0, VK_WHOLE_SIZE,
|
|
scratch_buffer_last_stage_mask_, initial_stage_mask,
|
|
scratch_buffer_last_access_mask_,
|
|
initial_access_mask);
|
|
scratch_buffer_last_stage_mask_ = initial_stage_mask;
|
|
scratch_buffer_last_access_mask_ = initial_access_mask;
|
|
scratch_buffer_last_usage_submission_ = submission_current;
|
|
scratch_buffer_used_ = true;
|
|
return ScratchBufferAcquisition(*this, scratch_buffer_, initial_stage_mask,
|
|
initial_access_mask);
|
|
}
|
|
|
|
size = xe::align(size, kScratchBufferSizeIncrement);
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
|
|
VkDeviceMemory new_scratch_buffer_memory;
|
|
VkBuffer new_scratch_buffer;
|
|
// VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT for
|
|
// texture loading.
|
|
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
|
|
vulkan_device, size,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
|
ui::vulkan::util::MemoryPurpose::kDeviceLocal, new_scratch_buffer,
|
|
new_scratch_buffer_memory)) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to create a {} MB scratch GPU buffer",
|
|
size >> 20);
|
|
return ScratchBufferAcquisition();
|
|
}
|
|
|
|
if (GetCompletedSubmission() >= scratch_buffer_last_usage_submission_) {
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
if (scratch_buffer_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyBuffer(device, scratch_buffer_, nullptr);
|
|
}
|
|
if (scratch_buffer_memory_ != VK_NULL_HANDLE) {
|
|
dfn.vkFreeMemory(device, scratch_buffer_memory_, nullptr);
|
|
}
|
|
} else {
|
|
if (scratch_buffer_ != VK_NULL_HANDLE) {
|
|
destroy_buffers_.emplace_back(scratch_buffer_last_usage_submission_,
|
|
scratch_buffer_);
|
|
}
|
|
if (scratch_buffer_memory_ != VK_NULL_HANDLE) {
|
|
destroy_memory_.emplace_back(scratch_buffer_last_usage_submission_,
|
|
scratch_buffer_memory_);
|
|
}
|
|
}
|
|
|
|
scratch_buffer_memory_ = new_scratch_buffer_memory;
|
|
scratch_buffer_ = new_scratch_buffer;
|
|
scratch_buffer_size_ = size;
|
|
// Not used yet, no need for a barrier.
|
|
scratch_buffer_last_stage_mask_ = initial_access_mask;
|
|
scratch_buffer_last_access_mask_ = initial_stage_mask;
|
|
scratch_buffer_last_usage_submission_ = submission_current;
|
|
scratch_buffer_used_ = true;
|
|
return ScratchBufferAcquisition(*this, new_scratch_buffer, initial_stage_mask,
|
|
initial_access_mask);
|
|
}
|
|
|
|
void VulkanCommandProcessor::BindExternalGraphicsPipeline(
|
|
VkPipeline pipeline, bool keep_dynamic_depth_bias,
|
|
bool keep_dynamic_blend_constants, bool keep_dynamic_stencil_mask_ref) {
|
|
if (!keep_dynamic_depth_bias) {
|
|
dynamic_depth_bias_update_needed_ = true;
|
|
}
|
|
if (!keep_dynamic_blend_constants) {
|
|
dynamic_blend_constants_update_needed_ = true;
|
|
}
|
|
if (!keep_dynamic_stencil_mask_ref) {
|
|
dynamic_stencil_compare_mask_front_update_needed_ = true;
|
|
dynamic_stencil_compare_mask_back_update_needed_ = true;
|
|
dynamic_stencil_write_mask_front_update_needed_ = true;
|
|
dynamic_stencil_write_mask_back_update_needed_ = true;
|
|
dynamic_stencil_reference_front_update_needed_ = true;
|
|
dynamic_stencil_reference_back_update_needed_ = true;
|
|
}
|
|
if (current_external_graphics_pipeline_ == pipeline) {
|
|
return;
|
|
}
|
|
deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
pipeline);
|
|
current_external_graphics_pipeline_ = pipeline;
|
|
current_guest_graphics_pipeline_ = VK_NULL_HANDLE;
|
|
current_guest_graphics_pipeline_layout_ = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void VulkanCommandProcessor::BindExternalComputePipeline(VkPipeline pipeline) {
|
|
if (current_external_compute_pipeline_ == pipeline) {
|
|
return;
|
|
}
|
|
deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
pipeline);
|
|
current_external_compute_pipeline_ = pipeline;
|
|
}
|
|
|
|
void VulkanCommandProcessor::SetViewport(const VkViewport& viewport) {
|
|
if (!dynamic_viewport_update_needed_) {
|
|
dynamic_viewport_update_needed_ |= dynamic_viewport_.x != viewport.x;
|
|
dynamic_viewport_update_needed_ |= dynamic_viewport_.y != viewport.y;
|
|
dynamic_viewport_update_needed_ |=
|
|
dynamic_viewport_.width != viewport.width;
|
|
dynamic_viewport_update_needed_ |=
|
|
dynamic_viewport_.height != viewport.height;
|
|
dynamic_viewport_update_needed_ |=
|
|
dynamic_viewport_.minDepth != viewport.minDepth;
|
|
dynamic_viewport_update_needed_ |=
|
|
dynamic_viewport_.maxDepth != viewport.maxDepth;
|
|
}
|
|
if (dynamic_viewport_update_needed_) {
|
|
dynamic_viewport_ = viewport;
|
|
deferred_command_buffer_.CmdVkSetViewport(0, 1, &dynamic_viewport_);
|
|
dynamic_viewport_update_needed_ = false;
|
|
}
|
|
}
|
|
|
|
void VulkanCommandProcessor::SetScissor(const VkRect2D& scissor) {
|
|
if (!dynamic_scissor_update_needed_) {
|
|
dynamic_scissor_update_needed_ |=
|
|
dynamic_scissor_.offset.x != scissor.offset.x;
|
|
dynamic_scissor_update_needed_ |=
|
|
dynamic_scissor_.offset.y != scissor.offset.y;
|
|
dynamic_scissor_update_needed_ |=
|
|
dynamic_scissor_.extent.width != scissor.extent.width;
|
|
dynamic_scissor_update_needed_ |=
|
|
dynamic_scissor_.extent.height != scissor.extent.height;
|
|
}
|
|
if (dynamic_scissor_update_needed_) {
|
|
dynamic_scissor_ = scissor;
|
|
deferred_command_buffer_.CmdVkSetScissor(0, 1, &dynamic_scissor_);
|
|
dynamic_scissor_update_needed_ = false;
|
|
}
|
|
}
|
|
|
|
Shader* VulkanCommandProcessor::LoadShader(xenos::ShaderType shader_type,
|
|
uint32_t guest_address,
|
|
const uint32_t* host_address,
|
|
uint32_t dword_count) {
|
|
return pipeline_cache_->LoadShader(shader_type, host_address, dword_count);
|
|
}
|
|
|
|
bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|
uint32_t index_count,
|
|
IndexBufferInfo* index_buffer_info,
|
|
bool major_mode_explicit) {
|
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
|
|
const RegisterFile& regs = *register_file_;
|
|
|
|
xenos::EdramMode edram_mode = regs.Get<reg::RB_MODECONTROL>().edram_mode;
|
|
if (edram_mode == xenos::EdramMode::kCopy) {
|
|
// Special copy handling.
|
|
return IssueCopy();
|
|
}
|
|
|
|
const ui::vulkan::VulkanDevice::Properties& device_properties =
|
|
GetVulkanDevice()->properties();
|
|
|
|
memexport_ranges_.clear();
|
|
|
|
// Vertex shader analysis.
|
|
auto vertex_shader = static_cast<VulkanShader*>(active_vertex_shader());
|
|
if (!vertex_shader) {
|
|
// Always need a vertex shader.
|
|
return false;
|
|
}
|
|
pipeline_cache_->AnalyzeShaderUcode(*vertex_shader);
|
|
// TODO(Triang3l): If the shader uses memory export, but
|
|
// vertexPipelineStoresAndAtomics is not supported, convert the vertex shader
|
|
// to a compute shader and dispatch it after the draw if the draw doesn't use
|
|
// tessellation.
|
|
if (vertex_shader->memexport_eM_written() != 0 &&
|
|
device_properties.vertexPipelineStoresAndAtomics) {
|
|
draw_util::AddMemExportRanges(regs, *vertex_shader, memexport_ranges_);
|
|
}
|
|
|
|
// Pixel shader analysis.
|
|
bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs);
|
|
bool is_rasterization_done =
|
|
draw_util::IsRasterizationPotentiallyDone(regs, primitive_polygonal);
|
|
VulkanShader* pixel_shader = nullptr;
|
|
if (is_rasterization_done) {
|
|
// See xenos::EdramMode for explanation why the pixel shader is only used
|
|
// when it's kColorDepth here.
|
|
if (edram_mode == xenos::EdramMode::kColorDepth) {
|
|
pixel_shader = static_cast<VulkanShader*>(active_pixel_shader());
|
|
if (pixel_shader) {
|
|
pipeline_cache_->AnalyzeShaderUcode(*pixel_shader);
|
|
if (!draw_util::IsPixelShaderNeededWithRasterization(*pixel_shader,
|
|
regs)) {
|
|
pixel_shader = nullptr;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Disabling pixel shader for this case is also required by the pipeline
|
|
// cache.
|
|
if (memexport_ranges_.empty()) {
|
|
// This draw has no effect.
|
|
return true;
|
|
}
|
|
}
|
|
if (pixel_shader && pixel_shader->memexport_eM_written() != 0 &&
|
|
device_properties.fragmentStoresAndAtomics) {
|
|
draw_util::AddMemExportRanges(regs, *pixel_shader, memexport_ranges_);
|
|
}
|
|
|
|
uint32_t ps_param_gen_pos = UINT32_MAX;
|
|
uint32_t interpolator_mask =
|
|
pixel_shader ? (vertex_shader->writes_interpolators() &
|
|
pixel_shader->GetInterpolatorInputMask(
|
|
regs.Get<reg::SQ_PROGRAM_CNTL>(),
|
|
regs.Get<reg::SQ_CONTEXT_MISC>(), ps_param_gen_pos))
|
|
: 0;
|
|
|
|
PrimitiveProcessor::ProcessingResult primitive_processing_result;
|
|
SpirvShaderTranslator::Modification vertex_shader_modification;
|
|
SpirvShaderTranslator::Modification pixel_shader_modification;
|
|
VulkanShader::VulkanTranslation* vertex_shader_translation;
|
|
VulkanShader::VulkanTranslation* pixel_shader_translation;
|
|
|
|
// Two iterations because a submission (even the current one - in which case
|
|
// it needs to be ended, and a new one must be started) may need to be awaited
|
|
// in case of a sampler count overflow, and if that happens, all subsystem
|
|
// updates done previously must be performed again because the updates done
|
|
// before the awaiting may be referencing objects destroyed by
|
|
// CompletedSubmissionUpdated.
|
|
for (uint32_t i = 0; i < 2; ++i) {
|
|
if (!BeginSubmission(true)) {
|
|
return false;
|
|
}
|
|
|
|
// Process primitives.
|
|
if (!primitive_processor_->Process(primitive_processing_result)) {
|
|
return false;
|
|
}
|
|
if (!primitive_processing_result.host_draw_vertex_count) {
|
|
// Nothing to draw.
|
|
return true;
|
|
}
|
|
// TODO(Triang3l): Geometry-type-specific vertex shader, vertex shader as
|
|
// compute.
|
|
// Skip unsupported host vertex shader types (but allow tessellation types
|
|
// through - they will be handled in pipeline creation or rejected there if
|
|
// not fully supported yet).
|
|
if (primitive_processing_result.host_vertex_shader_type !=
|
|
Shader::HostVertexShaderType::kVertex &&
|
|
primitive_processing_result.host_vertex_shader_type !=
|
|
Shader::HostVertexShaderType::kPointListAsTriangleStrip &&
|
|
!Shader::IsHostVertexShaderTypeDomain(
|
|
primitive_processing_result.host_vertex_shader_type)) {
|
|
return false;
|
|
}
|
|
|
|
// Shader modifications.
|
|
vertex_shader_modification =
|
|
pipeline_cache_->GetCurrentVertexShaderModification(
|
|
*vertex_shader, primitive_processing_result.host_vertex_shader_type,
|
|
interpolator_mask, ps_param_gen_pos != UINT32_MAX);
|
|
pixel_shader_modification =
|
|
pixel_shader ? pipeline_cache_->GetCurrentPixelShaderModification(
|
|
*pixel_shader, interpolator_mask, ps_param_gen_pos)
|
|
: SpirvShaderTranslator::Modification(0);
|
|
|
|
// Translate the shaders now to obtain the sampler bindings.
|
|
vertex_shader_translation = static_cast<VulkanShader::VulkanTranslation*>(
|
|
vertex_shader->GetOrCreateTranslation(
|
|
vertex_shader_modification.value));
|
|
pixel_shader_translation =
|
|
pixel_shader ? static_cast<VulkanShader::VulkanTranslation*>(
|
|
pixel_shader->GetOrCreateTranslation(
|
|
pixel_shader_modification.value))
|
|
: nullptr;
|
|
if (!pipeline_cache_->EnsureShadersTranslated(vertex_shader_translation,
|
|
pixel_shader_translation)) {
|
|
return false;
|
|
}
|
|
|
|
// Obtain the samplers. Note that the bindings don't depend on the shader
|
|
// modification, so if on the second iteration of this loop it becomes
|
|
// different for some reason (like a race condition with the guest in index
|
|
// buffer processing in the primitive processor resulting in different host
|
|
// vertex shader types), the bindings will stay the same.
|
|
// TODO(Triang3l): Sampler caching and reuse for adjacent draws within one
|
|
// submission.
|
|
uint32_t samplers_overflowed_count = 0;
|
|
for (uint32_t j = 0; j < 2; ++j) {
|
|
std::vector<std::pair<VulkanTextureCache::SamplerParameters, VkSampler>>&
|
|
shader_samplers =
|
|
j ? current_samplers_pixel_ : current_samplers_vertex_;
|
|
if (!i) {
|
|
shader_samplers.clear();
|
|
}
|
|
const VulkanShader* shader = j ? pixel_shader : vertex_shader;
|
|
if (!shader) {
|
|
continue;
|
|
}
|
|
const std::vector<VulkanShader::SamplerBinding>& shader_sampler_bindings =
|
|
shader->GetSamplerBindingsAfterTranslation();
|
|
if (!i) {
|
|
shader_samplers.reserve(shader_sampler_bindings.size());
|
|
for (const VulkanShader::SamplerBinding& shader_sampler_binding :
|
|
shader_sampler_bindings) {
|
|
shader_samplers.emplace_back(
|
|
texture_cache_->GetSamplerParameters(shader_sampler_binding),
|
|
VK_NULL_HANDLE);
|
|
}
|
|
}
|
|
for (std::pair<VulkanTextureCache::SamplerParameters, VkSampler>&
|
|
shader_sampler_pair : shader_samplers) {
|
|
// UseSampler calls are needed even on the second iteration in case the
|
|
// submission was broken (and thus the last usage submission indices for
|
|
// the used samplers need to be updated) due to an overflow within one
|
|
// submission. Though sampler overflow is a very rare situation overall.
|
|
bool sampler_overflowed;
|
|
VkSampler shader_sampler = texture_cache_->UseSampler(
|
|
shader_sampler_pair.first, sampler_overflowed);
|
|
shader_sampler_pair.second = shader_sampler;
|
|
if (shader_sampler == VK_NULL_HANDLE) {
|
|
if (!sampler_overflowed || i) {
|
|
// If !sampler_overflowed, just failed to create a sampler for some
|
|
// reason.
|
|
// If i == 1, an overflow has happened twice, can't recover from it
|
|
// anymore (would enter an infinite loop otherwise if the number of
|
|
// attempts was not limited to 2). Possibly too many unique samplers
|
|
// in one draw, or failed to await submission completion.
|
|
return false;
|
|
}
|
|
++samplers_overflowed_count;
|
|
}
|
|
}
|
|
}
|
|
if (!samplers_overflowed_count) {
|
|
break;
|
|
}
|
|
assert_zero(i);
|
|
// Free space for as many samplers as how many haven't been allocated
|
|
// successfully - obtain the submission index that needs to be awaited to
|
|
// reuse `samplers_overflowed_count` slots. This must be done after all the
|
|
// UseSampler calls, not inside the loop calling UseSampler, because earlier
|
|
// UseSampler calls may "mark for deletion" some samplers that later
|
|
// UseSampler calls in the loop may actually demand.
|
|
uint64_t sampler_overflow_await_submission =
|
|
texture_cache_->GetSubmissionToAwaitOnSamplerOverflow(
|
|
samplers_overflowed_count);
|
|
assert_true(sampler_overflow_await_submission <= GetCurrentSubmission());
|
|
CheckSubmissionCompletionAndDeviceLoss(sampler_overflow_await_submission);
|
|
}
|
|
|
|
// Set up the render targets - this may perform dispatches and draws.
|
|
reg::RB_DEPTHCONTROL normalized_depth_control =
|
|
draw_util::GetNormalizedDepthControl(regs);
|
|
uint32_t normalized_color_mask =
|
|
pixel_shader ? draw_util::GetNormalizedColorMask(
|
|
regs, pixel_shader->writes_color_targets())
|
|
: 0;
|
|
if (!render_target_cache_->Update(is_rasterization_done,
|
|
normalized_depth_control,
|
|
normalized_color_mask, *vertex_shader)) {
|
|
return false;
|
|
}
|
|
|
|
// Create the pipeline (for this, need the render pass from the render target
|
|
// cache), translating the shaders - doing this now to obtain the used
|
|
// textures.
|
|
VulkanPipelineCache::Pipeline* pipeline;
|
|
if (!pipeline_cache_->ConfigurePipeline(
|
|
vertex_shader_translation, pixel_shader_translation,
|
|
primitive_processing_result, normalized_depth_control,
|
|
normalized_color_mask,
|
|
render_target_cache_->last_update_render_pass_key(), &pipeline)) {
|
|
return false;
|
|
}
|
|
|
|
VkPipeline current_pipeline =
|
|
pipeline->pipeline.load(std::memory_order_acquire);
|
|
if (current_pipeline == VK_NULL_HANDLE) {
|
|
// Pipeline is not ready yet - wait for it to be created.
|
|
pipeline_cache_->EndSubmission();
|
|
current_pipeline = pipeline->pipeline.load(std::memory_order_acquire);
|
|
if (current_pipeline == VK_NULL_HANDLE) {
|
|
// Still not ready - something is wrong.
|
|
return false;
|
|
}
|
|
}
|
|
// If async mode is active, this may be a placeholder pipeline. The real
|
|
// pipeline will be swapped in by the creation thread when ready.
|
|
// We re-load the handle to pick up any swap that may have happened.
|
|
current_pipeline = pipeline->pipeline.load(std::memory_order_acquire);
|
|
|
|
// Update the textures before most other work in the submission because
|
|
// samplers depend on this (and in case of sampler overflow in a submission,
|
|
// submissions must be split) - may perform dispatches and copying.
|
|
uint32_t used_texture_mask =
|
|
vertex_shader->GetUsedTextureMaskAfterTranslation() |
|
|
(pixel_shader != nullptr
|
|
? pixel_shader->GetUsedTextureMaskAfterTranslation()
|
|
: 0);
|
|
texture_cache_->RequestTextures(used_texture_mask);
|
|
|
|
// Update the graphics pipeline, and if the new graphics pipeline has a
|
|
// different layout, invalidate incompatible descriptor sets before updating
|
|
// current_guest_graphics_pipeline_layout_.
|
|
// The pipeline may be not ready yet if created asynchronously.
|
|
// EndSubmission must be called before submitting the command buffer to
|
|
// await its creation.
|
|
if (current_guest_graphics_pipeline_ != current_pipeline) {
|
|
deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
current_pipeline);
|
|
current_guest_graphics_pipeline_ = current_pipeline;
|
|
current_external_graphics_pipeline_ = VK_NULL_HANDLE;
|
|
}
|
|
auto pipeline_layout =
|
|
static_cast<const PipelineLayout*>(pipeline->pipeline_layout);
|
|
if (current_guest_graphics_pipeline_layout_ != pipeline_layout) {
|
|
if (current_guest_graphics_pipeline_layout_) {
|
|
// Keep descriptor set layouts for which the new pipeline layout is
|
|
// compatible with the previous one (pipeline layouts are compatible for
|
|
// set N if set layouts 0 through N are compatible).
|
|
uint32_t descriptor_sets_kept =
|
|
uint32_t(SpirvShaderTranslator::kDescriptorSetCount);
|
|
if (current_guest_graphics_pipeline_layout_
|
|
->descriptor_set_layout_textures_vertex_ref() !=
|
|
pipeline_layout->descriptor_set_layout_textures_vertex_ref()) {
|
|
descriptor_sets_kept = std::min(
|
|
descriptor_sets_kept,
|
|
uint32_t(SpirvShaderTranslator::kDescriptorSetTexturesVertex));
|
|
}
|
|
if (current_guest_graphics_pipeline_layout_
|
|
->descriptor_set_layout_textures_pixel_ref() !=
|
|
pipeline_layout->descriptor_set_layout_textures_pixel_ref()) {
|
|
descriptor_sets_kept = std::min(
|
|
descriptor_sets_kept,
|
|
uint32_t(SpirvShaderTranslator::kDescriptorSetTexturesPixel));
|
|
}
|
|
// Invalidate descriptor set bindings for incompatible sets.
|
|
current_graphics_descriptor_sets_bound_up_to_date_ &=
|
|
(UINT32_C(1) << descriptor_sets_kept) - 1;
|
|
} else {
|
|
// No or unknown pipeline layout previously bound - all bindings are in an
|
|
// indeterminate state.
|
|
current_graphics_descriptor_sets_bound_up_to_date_ = 0;
|
|
}
|
|
current_guest_graphics_pipeline_layout_ = pipeline_layout;
|
|
}
|
|
|
|
bool host_render_targets_used = render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kHostRenderTargets;
|
|
|
|
// Get dynamic rasterizer state.
|
|
draw_util::ViewportInfo viewport_info;
|
|
|
|
// Just handling maxViewportDimensions is enough - viewportBoundsRange[1] must
|
|
// be at least 2 * max(maxViewportDimensions[0...1]) - 1, and
|
|
// maxViewportDimensions must be greater than or equal to the size of the
|
|
// largest possible framebuffer attachment (if the viewport has positive
|
|
// offset and is between maxViewportDimensions and viewportBoundsRange[1],
|
|
// GetHostViewportInfo will adjust ndc_scale/ndc_offset to clamp it, and the
|
|
// clamped range will be outside the largest possible framebuffer anyway.
|
|
// FIXME(Triang3l): Possibly handle maxViewportDimensions and
|
|
// viewportBoundsRange separately because when using fragment shader
|
|
// interlocks, framebuffers are not used, while the range may be wider than
|
|
// dimensions? Though viewport bigger than 4096 - the smallest possible
|
|
// maximum dimension (which is below the 8192 texture size limit on the Xbox
|
|
// 360) - and with offset, is probably a situation that never happens in real
|
|
// life. Or even disregard the viewport bounds range in the fragment shader
|
|
// interlocks case completely - apply the viewport and the scissor offset
|
|
// directly to pixel address and to things like ps_param_gen.
|
|
uint32_t draw_resolution_scale_x = texture_cache_->draw_resolution_scale_x();
|
|
uint32_t draw_resolution_scale_y = texture_cache_->draw_resolution_scale_y();
|
|
draw_util::GetViewportInfoArgs gviargs{};
|
|
gviargs.Setup(draw_resolution_scale_x, draw_resolution_scale_y,
|
|
texture_cache_->draw_resolution_scale_x_divisor(),
|
|
texture_cache_->draw_resolution_scale_y_divisor(), false,
|
|
device_properties.maxViewportDimensions[0],
|
|
device_properties.maxViewportDimensions[1], true,
|
|
normalized_depth_control, false, host_render_targets_used,
|
|
pixel_shader && pixel_shader->writes_depth());
|
|
gviargs.SetupRegisterValues(regs);
|
|
|
|
draw_util::GetHostViewportInfo(&gviargs, viewport_info);
|
|
// Update dynamic graphics pipeline state.
|
|
UpdateDynamicState(viewport_info, primitive_polygonal,
|
|
normalized_depth_control, draw_resolution_scale_x,
|
|
draw_resolution_scale_y);
|
|
|
|
auto vgt_draw_initiator = regs.Get<reg::VGT_DRAW_INITIATOR>();
|
|
|
|
// Whether to load the guest 32-bit (usually big-endian) vertex index
|
|
// indirectly in the vertex shader if full 32-bit indices are not supported by
|
|
// the host.
|
|
bool shader_32bit_index_dma =
|
|
!device_properties.fullDrawIndexUint32 &&
|
|
primitive_processing_result.index_buffer_type ==
|
|
PrimitiveProcessor::ProcessedIndexBufferType::kGuestDMA &&
|
|
vgt_draw_initiator.index_size == xenos::IndexFormat::kInt32 &&
|
|
primitive_processing_result.host_vertex_shader_type ==
|
|
Shader::HostVertexShaderType::kVertex;
|
|
|
|
// Update system constants before uploading them.
|
|
UpdateSystemConstantValues(primitive_polygonal, primitive_processing_result,
|
|
shader_32bit_index_dma, viewport_info,
|
|
used_texture_mask, normalized_depth_control,
|
|
normalized_color_mask);
|
|
|
|
// Update uniform buffers and descriptor sets after binding the pipeline with
|
|
// the new layout.
|
|
if (!UpdateBindings(vertex_shader, pixel_shader)) {
|
|
return false;
|
|
}
|
|
|
|
// Ensure vertex buffers are resident.
|
|
//
|
|
// Use the vertex_fetch_bitmap instead of vertex_bindings() to avoid using
|
|
// cached/stale vertex binding indices. The bitmap is populated during shader
|
|
// translation and represents which fetch constant indices the shader actually
|
|
// references, allowing us to check the current register values at draw time.
|
|
const Shader::ConstantRegisterMap& constant_map_vertex =
|
|
vertex_shader->constant_register_map();
|
|
{
|
|
uint32_t vfetch_addresses[96];
|
|
uint32_t vfetch_sizes[96];
|
|
uint32_t vfetch_current_queued = 0;
|
|
for (uint32_t i = 0;
|
|
i < xe::countof(constant_map_vertex.vertex_fetch_bitmap); ++i) {
|
|
uint32_t vfetch_bits_remaining =
|
|
constant_map_vertex.vertex_fetch_bitmap[i];
|
|
uint32_t j;
|
|
while (xe::bit_scan_forward(vfetch_bits_remaining, &j)) {
|
|
vfetch_bits_remaining = xe::clear_lowest_bit(vfetch_bits_remaining);
|
|
uint32_t vfetch_index = i * 32 + j;
|
|
xenos::xe_gpu_vertex_fetch_t vfetch_constant =
|
|
regs.GetVertexFetch(vfetch_index);
|
|
switch (vfetch_constant.type) {
|
|
case xenos::FetchConstantType::kVertex:
|
|
break;
|
|
case xenos::FetchConstantType::kInvalidVertex:
|
|
if (cvars::gpu_allow_invalid_fetch_constants) {
|
|
break;
|
|
}
|
|
XELOGW(
|
|
"Vertex fetch constant {} ({:08X} {:08X}) has \"invalid\" "
|
|
"type! "
|
|
"This is incorrect behavior, but you can try bypassing this by "
|
|
"launching Xenia with "
|
|
"--gpu_allow_invalid_fetch_constants=true.",
|
|
vfetch_index, vfetch_constant.dword_0, vfetch_constant.dword_1);
|
|
return false;
|
|
default:
|
|
// Type is kTexture (2) or kInvalidTexture (3) - completely wrong
|
|
// for vertex data
|
|
if (cvars::gpu_allow_invalid_fetch_constants) {
|
|
XELOGW(
|
|
"Vertex fetch constant {} ({:08X} {:08X}) has wrong type {} "
|
|
"(texture fetch constant in vertex slot) - allowing due to "
|
|
"--gpu_allow_invalid_fetch_constants=true. This will likely "
|
|
"crash or produce garbage!",
|
|
vfetch_index, vfetch_constant.dword_0,
|
|
vfetch_constant.dword_1,
|
|
static_cast<uint32_t>(vfetch_constant.type));
|
|
break;
|
|
}
|
|
XELOGW(
|
|
"Vertex fetch constant {} ({:08X} {:08X}) is completely "
|
|
"invalid! Type={} - this slot contains a texture fetch "
|
|
"constant (type 2), not a vertex fetch constant (type 0). "
|
|
"This may indicate the shader is reading from the wrong fetch "
|
|
"constant index, or the game has a bug.",
|
|
vfetch_index, vfetch_constant.dword_0, vfetch_constant.dword_1,
|
|
static_cast<uint32_t>(vfetch_constant.type));
|
|
return false;
|
|
}
|
|
vfetch_addresses[vfetch_current_queued] = vfetch_constant.address;
|
|
vfetch_sizes[vfetch_current_queued++] = vfetch_constant.size;
|
|
}
|
|
}
|
|
|
|
if (vfetch_current_queued) {
|
|
// Pre-acquire the critical region so we're not repeatedly re-acquiring
|
|
// it in RequestRange - SharedMemory tracks dirty pages and only uploads
|
|
// what actually changed, making redundant calls cheap under a hoisted
|
|
// lock.
|
|
auto shared_memory_request_range_hoisted =
|
|
global_critical_region::Acquire();
|
|
|
|
for (uint32_t i = 0; i < vfetch_current_queued; ++i) {
|
|
if (!shared_memory_->RequestRange(vfetch_addresses[i] << 2,
|
|
vfetch_sizes[i] << 2)) {
|
|
XELOGE(
|
|
"Failed to request vertex buffer at 0x{:08X} (size {}) in the "
|
|
"shared memory",
|
|
vfetch_addresses[i] << 2, vfetch_sizes[i] << 2);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Synchronize the memory pages backing memory scatter export streams, and
|
|
// calculate the range that includes the streams for the buffer barrier.
|
|
uint32_t memexport_extent_start = UINT32_MAX, memexport_extent_end = 0;
|
|
for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) {
|
|
uint32_t memexport_range_base_bytes = memexport_range.base_address_dwords
|
|
<< 2;
|
|
if (!shared_memory_->RequestRange(memexport_range_base_bytes,
|
|
memexport_range.size_bytes)) {
|
|
XELOGE(
|
|
"Failed to request memexport stream at 0x{:08X} (size {}) in the "
|
|
"shared memory",
|
|
memexport_range_base_bytes, memexport_range.size_bytes);
|
|
return false;
|
|
}
|
|
memexport_extent_start =
|
|
std::min(memexport_extent_start, memexport_range_base_bytes);
|
|
memexport_extent_end =
|
|
std::max(memexport_extent_end,
|
|
memexport_range_base_bytes + memexport_range.size_bytes);
|
|
}
|
|
|
|
// Insert the shared memory barrier if needed.
|
|
// TODO(Triang3l): Find some PM4 command that can be used for indication of
|
|
// when memexports should be awaited instead of inserting the barrier in Use
|
|
// every time if memory export was done in the previous draw?
|
|
if (memexport_extent_start < memexport_extent_end) {
|
|
shared_memory_->Use(
|
|
VulkanSharedMemory::Usage::kGuestDrawReadWrite,
|
|
std::make_pair(memexport_extent_start,
|
|
memexport_extent_end - memexport_extent_start));
|
|
} else {
|
|
shared_memory_->Use(VulkanSharedMemory::Usage::kRead);
|
|
}
|
|
|
|
// After all commands that may dispatch, copy or insert barriers, submit the
|
|
// barriers (may end the render pass), and (re)enter the render pass before
|
|
// drawing.
|
|
SubmitBarriersAndEnterRenderTargetCacheRenderPass(
|
|
render_target_cache_->last_update_render_pass(),
|
|
render_target_cache_->last_update_framebuffer());
|
|
|
|
// Draw.
|
|
if (primitive_processing_result.index_buffer_type ==
|
|
PrimitiveProcessor::ProcessedIndexBufferType::kNone ||
|
|
shader_32bit_index_dma) {
|
|
deferred_command_buffer_.CmdVkDraw(
|
|
primitive_processing_result.host_draw_vertex_count, 1, 0, 0);
|
|
} else {
|
|
std::pair<VkBuffer, VkDeviceSize> index_buffer;
|
|
switch (primitive_processing_result.index_buffer_type) {
|
|
case PrimitiveProcessor::ProcessedIndexBufferType::kGuestDMA:
|
|
index_buffer.first = shared_memory_->buffer();
|
|
index_buffer.second = primitive_processing_result.guest_index_base;
|
|
break;
|
|
case PrimitiveProcessor::ProcessedIndexBufferType::kHostConverted:
|
|
index_buffer = primitive_processor_->GetConvertedIndexBuffer(
|
|
primitive_processing_result.host_index_buffer_handle);
|
|
break;
|
|
case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForAuto:
|
|
case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForDMA:
|
|
index_buffer = primitive_processor_->GetBuiltinIndexBuffer(
|
|
primitive_processing_result.host_index_buffer_handle);
|
|
break;
|
|
default:
|
|
assert_unhandled_case(primitive_processing_result.index_buffer_type);
|
|
return false;
|
|
}
|
|
deferred_command_buffer_.CmdVkBindIndexBuffer(
|
|
index_buffer.first, index_buffer.second,
|
|
primitive_processing_result.host_index_format ==
|
|
xenos::IndexFormat::kInt16
|
|
? VK_INDEX_TYPE_UINT16
|
|
: VK_INDEX_TYPE_UINT32);
|
|
deferred_command_buffer_.CmdVkDrawIndexed(
|
|
primitive_processing_result.host_draw_vertex_count, 1, 0, 0, 0);
|
|
}
|
|
|
|
// Invalidate textures in memexported memory and watch for changes.
|
|
for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) {
|
|
shared_memory_->RangeWrittenByGpu(memexport_range.base_address_dwords << 2,
|
|
memexport_range.size_bytes);
|
|
}
|
|
|
|
// CPU readback for memexport data (if enabled).
|
|
if (GetGPUSetting(GPUSetting::ReadbackMemexport) &&
|
|
!memexport_ranges_.empty()) {
|
|
// Calculate total size of all memexport ranges.
|
|
uint32_t memexport_total_size = 0;
|
|
for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) {
|
|
memexport_total_size += memexport_range.size_bytes;
|
|
}
|
|
|
|
if (memexport_total_size > 0) {
|
|
VkBuffer readback_buffer = RequestReadbackBuffer(memexport_total_size);
|
|
if (readback_buffer != VK_NULL_HANDLE) {
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn =
|
|
vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
VkBuffer shared_memory_buffer = shared_memory_->buffer();
|
|
|
|
// Ensure shared memory is ready for transfer.
|
|
shared_memory_->Use(VulkanSharedMemory::Usage::kRead);
|
|
|
|
// Copy each memexport range to the readback buffer.
|
|
uint32_t readback_buffer_offset = 0;
|
|
for (const draw_util::MemExportRange& memexport_range :
|
|
memexport_ranges_) {
|
|
VkBufferCopy copy_region = {};
|
|
copy_region.srcOffset = memexport_range.base_address_dwords << 2;
|
|
copy_region.dstOffset = readback_buffer_offset;
|
|
copy_region.size = memexport_range.size_bytes;
|
|
|
|
deferred_command_buffer_.CmdVkCopyBuffer(
|
|
shared_memory_buffer, readback_buffer, 1, ©_region);
|
|
|
|
readback_buffer_offset += memexport_range.size_bytes;
|
|
}
|
|
|
|
// Wait for GPU to finish (SYNCHRONIZATION STALL)
|
|
if (AwaitAllQueueOperationsCompletion()) {
|
|
// Map staging buffer and copy to guest memory.
|
|
void* mapped_data;
|
|
if (dfn.vkMapMemory(device, memexport_readback_buffer_memory_, 0,
|
|
memexport_total_size, 0,
|
|
&mapped_data) == VK_SUCCESS) {
|
|
if (mapped_data) {
|
|
const uint8_t* readback_bytes =
|
|
static_cast<const uint8_t*>(mapped_data);
|
|
for (const draw_util::MemExportRange& memexport_range :
|
|
memexport_ranges_) {
|
|
std::memcpy(memory_->TranslatePhysical(
|
|
memexport_range.base_address_dwords << 2),
|
|
readback_bytes, memexport_range.size_bytes);
|
|
readback_bytes += memexport_range.size_bytes;
|
|
}
|
|
} else {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to map readback buffer "
|
|
"(mapped_data is null)");
|
|
}
|
|
dfn.vkUnmapMemory(device, memexport_readback_buffer_memory_);
|
|
} else {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to map readback buffer memory "
|
|
"for memexport");
|
|
}
|
|
} else {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to complete queue operations for "
|
|
"memexport readback");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::IssueCopy() {
|
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
|
|
if (!BeginSubmission(true)) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t written_address, written_length;
|
|
if (!render_target_cache_->Resolve(*memory_, *shared_memory_, *texture_cache_,
|
|
written_address, written_length)) {
|
|
return false;
|
|
}
|
|
|
|
// CPU readback resolve path (if not disabled).
|
|
ReadbackResolveMode readback_mode = GetReadbackResolveMode();
|
|
if (readback_mode != ReadbackResolveMode::kDisabled &&
|
|
!texture_cache_->IsDrawResolutionScaled() && written_length > 0) {
|
|
// Early check: if destination memory is not accessible, skip all the
|
|
// expensive GPU readback work.
|
|
VirtualHeap* physical_heap = memory_->GetPhysicalHeap();
|
|
bool memory_accessible = false;
|
|
if (physical_heap) {
|
|
HeapAllocationInfo alloc_info;
|
|
if (physical_heap->QueryRegionInfo(written_address, &alloc_info) &&
|
|
(alloc_info.state & kMemoryAllocationCommit) &&
|
|
(alloc_info.protect & kMemoryProtectWrite)) {
|
|
uint32_t end_address = written_address + written_length;
|
|
uint32_t region_end = alloc_info.base_address + alloc_info.region_size;
|
|
if (end_address <= region_end) {
|
|
memory_accessible = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!memory_accessible) {
|
|
// Destination memory not accessible, skip readback entirely
|
|
return true;
|
|
}
|
|
|
|
// Create a key for this specific resolve operation
|
|
uint64_t resolve_key =
|
|
MakeReadbackResolveKey(written_address, written_length);
|
|
ReadbackBuffer& rb = readback_buffers_[resolve_key];
|
|
rb.last_used_frame = frame_current_;
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
uint32_t write_index = rb.current_index;
|
|
uint32_t size = AlignReadbackBufferSize(written_length);
|
|
|
|
// Allocate/resize write buffer if needed
|
|
if (size > rb.sizes[write_index]) {
|
|
// Create buffer with TRANSFER_DST usage for copying from GPU.
|
|
VkBufferCreateInfo buffer_info = {};
|
|
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
buffer_info.size = size;
|
|
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
|
|
VkBuffer new_buffer;
|
|
if (dfn.vkCreateBuffer(device, &buffer_info, nullptr, &new_buffer) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to create readback buffer of {} MB",
|
|
size >> 20);
|
|
return true;
|
|
}
|
|
|
|
// Get memory requirements.
|
|
VkMemoryRequirements memory_requirements;
|
|
dfn.vkGetBufferMemoryRequirements(device, new_buffer,
|
|
&memory_requirements);
|
|
|
|
// Allocate HOST_VISIBLE | HOST_CACHED | HOST_COHERENT memory for
|
|
// readback.
|
|
const uint32_t memory_type_index = ui::vulkan::util::ChooseMemoryType(
|
|
vulkan_device->memory_types(), memory_requirements.memoryTypeBits,
|
|
ui::vulkan::util::MemoryPurpose::kReadback);
|
|
|
|
if (memory_type_index == UINT32_MAX) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to find memory type for readback "
|
|
"buffer");
|
|
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
|
return true;
|
|
}
|
|
|
|
VkMemoryAllocateInfo memory_info = {};
|
|
memory_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
memory_info.allocationSize = memory_requirements.size;
|
|
memory_info.memoryTypeIndex = memory_type_index;
|
|
|
|
VkDeviceMemory new_memory;
|
|
if (dfn.vkAllocateMemory(device, &memory_info, nullptr, &new_memory) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to allocate readback buffer "
|
|
"memory");
|
|
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
|
return true;
|
|
}
|
|
|
|
// Bind memory to buffer.
|
|
if (dfn.vkBindBufferMemory(device, new_buffer, new_memory, 0) !=
|
|
VK_SUCCESS) {
|
|
XELOGE("VulkanCommandProcessor: Failed to bind readback buffer memory");
|
|
dfn.vkFreeMemory(device, new_memory, nullptr);
|
|
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
|
return true;
|
|
}
|
|
|
|
// Clean up old buffer if exists
|
|
if (rb.buffers[write_index] != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyBuffer(device, rb.buffers[write_index], nullptr);
|
|
}
|
|
if (rb.memories[write_index] != VK_NULL_HANDLE) {
|
|
dfn.vkFreeMemory(device, rb.memories[write_index], nullptr);
|
|
}
|
|
|
|
rb.buffers[write_index] = new_buffer;
|
|
rb.memories[write_index] = new_memory;
|
|
rb.sizes[write_index] = size;
|
|
}
|
|
|
|
VkBuffer shared_memory_buffer = shared_memory_->buffer();
|
|
|
|
// Ensure shared memory is ready for transfer.
|
|
shared_memory_->Use(VulkanSharedMemory::Usage::kRead);
|
|
|
|
// Copy GPU buffer → staging buffer.
|
|
VkBufferCopy copy_region = {};
|
|
copy_region.srcOffset = written_address;
|
|
copy_region.dstOffset = 0;
|
|
copy_region.size = written_length;
|
|
|
|
deferred_command_buffer_.CmdVkCopyBuffer(
|
|
shared_memory_buffer, rb.buffers[write_index], 1, ©_region);
|
|
|
|
bool use_delayed_sync = (readback_mode == ReadbackResolveMode::kFast);
|
|
uint32_t read_index = write_index;
|
|
|
|
if (use_delayed_sync) {
|
|
// Use previous frame's data (avoid stall)
|
|
read_index = 1 - write_index;
|
|
} else {
|
|
// Wait for GPU to finish (accurate but slow)
|
|
if (!AwaitAllQueueOperationsCompletion()) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to complete queue operations for "
|
|
"resolve readback");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Read from the appropriate buffer
|
|
// If using delayed sync but previous buffer doesn't exist, use current
|
|
// buffer with sync as fallback
|
|
if (use_delayed_sync && (rb.buffers[read_index] == VK_NULL_HANDLE ||
|
|
written_length > rb.sizes[read_index])) {
|
|
read_index = write_index;
|
|
if (!AwaitAllQueueOperationsCompletion()) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to complete queue operations for "
|
|
"resolve readback fallback");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (rb.buffers[read_index] != VK_NULL_HANDLE &&
|
|
written_length <= rb.sizes[read_index]) {
|
|
void* mapped_data;
|
|
if (dfn.vkMapMemory(device, rb.memories[read_index], 0, written_length, 0,
|
|
&mapped_data) == VK_SUCCESS) {
|
|
if (mapped_data) {
|
|
// Memory accessibility already checked at the start of this function
|
|
uint8_t* dest_ptr = memory_->TranslatePhysical(written_address);
|
|
memory::vastcpy(dest_ptr, static_cast<uint8_t*>(mapped_data),
|
|
written_length);
|
|
} else {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to map readback buffer "
|
|
"(mapped_data is null)");
|
|
}
|
|
dfn.vkUnmapMemory(device, rb.memories[read_index]);
|
|
} else {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to map readback buffer memory for "
|
|
"resolve");
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VkBuffer VulkanCommandProcessor::RequestReadbackBuffer(uint32_t size) {
|
|
if (size == 0) {
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
size = AlignReadbackBufferSize(size);
|
|
|
|
if (size > memexport_readback_buffer_size_) {
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
// Create buffer with TRANSFER_DST usage for copying from GPU.
|
|
VkBufferCreateInfo buffer_info = {};
|
|
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
buffer_info.size = size;
|
|
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
|
|
VkBuffer new_buffer;
|
|
if (dfn.vkCreateBuffer(device, &buffer_info, nullptr, &new_buffer) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to create readback buffer of {} MB",
|
|
size >> 20);
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
// Get memory requirements.
|
|
VkMemoryRequirements memory_requirements;
|
|
dfn.vkGetBufferMemoryRequirements(device, new_buffer, &memory_requirements);
|
|
|
|
// Allocate HOST_VISIBLE | HOST_CACHED | HOST_COHERENT memory for readback.
|
|
const uint32_t memory_type_index = ui::vulkan::util::ChooseMemoryType(
|
|
vulkan_device->memory_types(), memory_requirements.memoryTypeBits,
|
|
ui::vulkan::util::MemoryPurpose::kReadback);
|
|
if (memory_type_index == UINT32_MAX) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to find suitable memory type for "
|
|
"readback buffer");
|
|
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
VkMemoryAllocateInfo alloc_info = {};
|
|
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
alloc_info.allocationSize = memory_requirements.size;
|
|
alloc_info.memoryTypeIndex = memory_type_index;
|
|
|
|
VkDeviceMemory new_memory;
|
|
if (dfn.vkAllocateMemory(device, &alloc_info, nullptr, &new_memory) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to allocate memory for readback "
|
|
"buffer");
|
|
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
// Bind memory to buffer.
|
|
if (dfn.vkBindBufferMemory(device, new_buffer, new_memory, 0) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanCommandProcessor: Failed to bind memory to readback buffer");
|
|
dfn.vkFreeMemory(device, new_memory, nullptr);
|
|
dfn.vkDestroyBuffer(device, new_buffer, nullptr);
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
// Destroy old buffer if it exists.
|
|
if (memexport_readback_buffer_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyBuffer(device, memexport_readback_buffer_, nullptr);
|
|
dfn.vkFreeMemory(device, memexport_readback_buffer_memory_, nullptr);
|
|
}
|
|
|
|
memexport_readback_buffer_ = new_buffer;
|
|
memexport_readback_buffer_memory_ = new_memory;
|
|
memexport_readback_buffer_size_ = size;
|
|
}
|
|
|
|
return memexport_readback_buffer_;
|
|
}
|
|
|
|
void VulkanCommandProcessor::EnsureZPDQueryResources() {
|
|
if (GetZPDMode() == ZPDMode::kFake || !zpd_host_query_pool_) {
|
|
return;
|
|
}
|
|
|
|
bool can_recreate =
|
|
!zpd_active_segment_.logical_active &&
|
|
!zpd_active_segment_.segment_active &&
|
|
zpd_active_query_index_ == UINT32_MAX && !zpd_active_query_is_fsi_ &&
|
|
!zpd_host_query_pool_->has_pending_resolve_batch() &&
|
|
zpd_resolves_in_flight_.empty() && zpd_deferred_releases_.empty();
|
|
|
|
bool needs_fsi_counter = render_target_cache_ &&
|
|
render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kPixelShaderInterlock;
|
|
zpd_query_pool_needs_fsi_counter_ = needs_fsi_counter;
|
|
|
|
bool resources_initialized = zpd_host_query_pool_->EnsureInitialized(
|
|
GetVulkanDevice(), kZPDQueryPoolCapacity, can_recreate,
|
|
needs_fsi_counter);
|
|
|
|
if (resources_initialized && needs_fsi_counter &&
|
|
zpd_host_query_pool_->fsi_initialized()) {
|
|
VkBuffer fsi_counter_buffer = zpd_host_query_pool_->fsi_counter_buffer();
|
|
VkDeviceSize fsi_counter_range =
|
|
sizeof(uint32_t) * zpd_host_query_pool_->capacity();
|
|
if (zpd_fsi_counter_descriptor_buffer_ != fsi_counter_buffer ||
|
|
zpd_fsi_counter_descriptor_range_ != fsi_counter_range) {
|
|
VkDescriptorBufferInfo fsi_counter_descriptor_buffer_info;
|
|
fsi_counter_descriptor_buffer_info.buffer = fsi_counter_buffer;
|
|
fsi_counter_descriptor_buffer_info.offset = 0;
|
|
fsi_counter_descriptor_buffer_info.range = fsi_counter_range;
|
|
|
|
VkWriteDescriptorSet fsi_counter_descriptor_write;
|
|
fsi_counter_descriptor_write.sType =
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
fsi_counter_descriptor_write.pNext = nullptr;
|
|
fsi_counter_descriptor_write.dstSet =
|
|
shared_memory_and_edram_descriptor_set_;
|
|
fsi_counter_descriptor_write.dstBinding = 2;
|
|
fsi_counter_descriptor_write.dstArrayElement = 0;
|
|
fsi_counter_descriptor_write.descriptorCount = 1;
|
|
fsi_counter_descriptor_write.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
fsi_counter_descriptor_write.pImageInfo = nullptr;
|
|
fsi_counter_descriptor_write.pBufferInfo =
|
|
&fsi_counter_descriptor_buffer_info;
|
|
fsi_counter_descriptor_write.pTexelBufferView = nullptr;
|
|
|
|
const ui::vulkan::VulkanDevice::Functions& dfn =
|
|
GetVulkanDevice()->functions();
|
|
dfn.vkUpdateDescriptorSets(GetVulkanDevice()->device(), 1,
|
|
&fsi_counter_descriptor_write, 0, nullptr);
|
|
|
|
zpd_fsi_counter_descriptor_buffer_ = fsi_counter_buffer;
|
|
zpd_fsi_counter_descriptor_range_ = fsi_counter_range;
|
|
}
|
|
} else if (!IsZPDQueryPoolReady()) {
|
|
XELOGW(
|
|
"ZPD/Vulkan: FSI counter resources unavailable; keeping counter "
|
|
"index sentinel active");
|
|
}
|
|
zpd_fsi_counter_index_force_update_ = true;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::IsZPDQueryPoolReady() const {
|
|
if (!zpd_host_query_pool_ || !zpd_host_query_pool_->fbo_initialized()) {
|
|
return false;
|
|
}
|
|
if (!zpd_query_pool_needs_fsi_counter_) {
|
|
return true;
|
|
}
|
|
VkDeviceSize fsi_counter_range =
|
|
sizeof(uint32_t) * zpd_host_query_pool_->capacity();
|
|
return zpd_host_query_pool_->fsi_initialized() &&
|
|
zpd_fsi_counter_descriptor_buffer_ ==
|
|
zpd_host_query_pool_->fsi_counter_buffer() &&
|
|
zpd_fsi_counter_descriptor_range_ == fsi_counter_range;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::CanOpenZPDQuery() const {
|
|
if (!submission_open_) {
|
|
return false;
|
|
}
|
|
bool use_fsi_counter_path =
|
|
render_target_cache_ &&
|
|
render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kPixelShaderInterlock;
|
|
return use_fsi_counter_path || current_render_pass_ != VK_NULL_HANDLE;
|
|
}
|
|
|
|
CommandProcessor::QueryOpenResult VulkanCommandProcessor::OpenZPDQuery(
|
|
ReportHandle report_handle, bool can_close_submission) {
|
|
bool use_fsi_counter_path = zpd_query_pool_needs_fsi_counter_ &&
|
|
zpd_host_query_pool_->fsi_initialized();
|
|
|
|
if (!BeginSubmission(true)) {
|
|
return QueryOpenResult::kFailed;
|
|
}
|
|
|
|
if (!use_fsi_counter_path && current_render_pass_ == VK_NULL_HANDLE) {
|
|
return QueryOpenResult::kDeferred;
|
|
}
|
|
|
|
bool retried_after_submission_flip = false;
|
|
while (true) {
|
|
bool is_pool_exhausted = !zpd_host_query_pool_->has_free_indices();
|
|
|
|
if (is_pool_exhausted) {
|
|
PumpQueryResolves();
|
|
is_pool_exhausted = !zpd_host_query_pool_->has_free_indices();
|
|
}
|
|
|
|
if (is_pool_exhausted && GetZPDMode() == ZPDMode::kFast) {
|
|
return QueryOpenResult::kPoolExhausted;
|
|
}
|
|
|
|
uint64_t wait_for = 0;
|
|
if (is_pool_exhausted && !zpd_resolves_in_flight_.empty()) {
|
|
wait_for = zpd_resolves_in_flight_.front().submission;
|
|
}
|
|
|
|
if (wait_for == 0) {
|
|
break;
|
|
}
|
|
|
|
if (submission_open_ && wait_for == GetCurrentSubmission()) {
|
|
if (retried_after_submission_flip || !can_close_submission ||
|
|
!CanEndSubmissionImmediately()) {
|
|
return QueryOpenResult::kDeferred;
|
|
}
|
|
|
|
VkRenderPass saved_render_pass = VK_NULL_HANDLE;
|
|
const VulkanRenderTargetCache::Framebuffer* saved_framebuffer = nullptr;
|
|
if (current_render_pass_ != VK_NULL_HANDLE) {
|
|
saved_render_pass = current_render_pass_;
|
|
saved_framebuffer = current_framebuffer_;
|
|
}
|
|
|
|
if (current_render_pass_ != VK_NULL_HANDLE) {
|
|
EndRenderPass();
|
|
}
|
|
if (!EndSubmission(false)) {
|
|
return QueryOpenResult::kFailed;
|
|
}
|
|
if (!BeginSubmission(true)) {
|
|
return QueryOpenResult::kFailed;
|
|
}
|
|
|
|
if (saved_framebuffer) {
|
|
bool saved_pending_begin = zpd_active_segment_.segment_pending_begin;
|
|
if (use_fsi_counter_path) {
|
|
zpd_active_segment_.segment_pending_begin = false;
|
|
}
|
|
SubmitBarriersAndEnterRenderTargetCacheRenderPass(saved_render_pass,
|
|
saved_framebuffer);
|
|
zpd_active_segment_.segment_pending_begin = saved_pending_begin;
|
|
if (current_render_pass_ == VK_NULL_HANDLE) {
|
|
return QueryOpenResult::kDeferred;
|
|
}
|
|
}
|
|
|
|
retried_after_submission_flip = true;
|
|
continue;
|
|
}
|
|
|
|
uint64_t completed_submission = GetCompletedSubmission();
|
|
if (wait_for > completed_submission) {
|
|
completion_timeline_.AwaitSubmissionAndUpdateCompleted(wait_for);
|
|
PumpQueryResolves();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (!use_fsi_counter_path && current_render_pass_ == VK_NULL_HANDLE) {
|
|
return QueryOpenResult::kDeferred;
|
|
}
|
|
|
|
if (!zpd_host_query_pool_->AcquireQueryIndex(zpd_active_query_index_,
|
|
zpd_active_query_generation_)) {
|
|
return QueryOpenResult::kFailed;
|
|
}
|
|
|
|
zpd_active_query_is_fsi_ = use_fsi_counter_path;
|
|
|
|
// FSI queries don't use Vulkan occlusion queries at all.
|
|
// While the segment is open, the translated pixel shader accumulates passed
|
|
// MSAA samples into one counter slot selected via zpd_fsi_counter_index.
|
|
// Clear the slot here so a recycled index never inherits old counts.
|
|
if (zpd_active_query_is_fsi_) {
|
|
bool fsi_counter_cleared = false;
|
|
if (zpd_host_query_pool_->fsi_initialized()) {
|
|
if (current_render_pass_ != VK_NULL_HANDLE) {
|
|
VkRenderPass saved_render_pass = current_render_pass_;
|
|
const VulkanRenderTargetCache::Framebuffer* saved_framebuffer =
|
|
current_framebuffer_;
|
|
EndRenderPass();
|
|
zpd_host_query_pool_->ClearFSICounter(deferred_command_buffer_,
|
|
zpd_active_query_index_);
|
|
|
|
bool saved_pending_begin = zpd_active_segment_.segment_pending_begin;
|
|
zpd_active_segment_.segment_pending_begin = false;
|
|
SubmitBarriersAndEnterRenderTargetCacheRenderPass(saved_render_pass,
|
|
saved_framebuffer);
|
|
zpd_active_segment_.segment_pending_begin = saved_pending_begin;
|
|
fsi_counter_cleared = current_render_pass_ != VK_NULL_HANDLE;
|
|
} else {
|
|
zpd_host_query_pool_->ClearFSICounter(deferred_command_buffer_,
|
|
zpd_active_query_index_);
|
|
fsi_counter_cleared = true;
|
|
}
|
|
}
|
|
if (!fsi_counter_cleared) {
|
|
zpd_host_query_pool_->ReleaseQueryIndex(zpd_active_query_index_,
|
|
zpd_active_query_generation_);
|
|
zpd_active_query_index_ = UINT32_MAX;
|
|
zpd_active_query_generation_ = 0;
|
|
zpd_active_query_is_fsi_ = false;
|
|
zpd_fsi_counter_index_force_update_ = true;
|
|
return QueryOpenResult::kFailed;
|
|
}
|
|
zpd_fsi_counter_index_force_update_ = true;
|
|
return QueryOpenResult::kOpened;
|
|
}
|
|
|
|
zpd_host_query_pool_->BeginQuery(deferred_command_buffer_,
|
|
zpd_active_query_index_);
|
|
return QueryOpenResult::kOpened;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::CloseZPDQuery(ReportHandle report_handle,
|
|
uint64_t& out_submission) {
|
|
if (!zpd_active_query_is_fsi_ && current_render_pass_ == VK_NULL_HANDLE) {
|
|
return false;
|
|
}
|
|
|
|
if (zpd_active_query_is_fsi_) {
|
|
zpd_host_query_pool_->QueueQueryResolve(zpd_active_query_index_, true);
|
|
} else {
|
|
zpd_host_query_pool_->EndQuery(deferred_command_buffer_,
|
|
zpd_active_query_index_);
|
|
zpd_host_query_pool_->QueueQueryResolve(zpd_active_query_index_, false);
|
|
}
|
|
|
|
PendingQueryResolve resolve;
|
|
resolve.submission = GetCurrentSubmission();
|
|
resolve.query_index = zpd_active_query_index_;
|
|
resolve.query_generation = zpd_active_query_generation_;
|
|
resolve.uses_fsi_counter = zpd_active_query_is_fsi_;
|
|
resolve.report_handle = report_handle;
|
|
zpd_resolves_in_flight_.push_back(resolve);
|
|
|
|
out_submission = resolve.submission;
|
|
|
|
zpd_active_query_index_ = UINT32_MAX;
|
|
zpd_active_query_generation_ = 0;
|
|
|
|
bool closed_fsi_counter = zpd_active_query_is_fsi_;
|
|
zpd_active_query_is_fsi_ = false;
|
|
|
|
if (closed_fsi_counter) {
|
|
zpd_fsi_counter_index_force_update_ = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::DiscardZPDQuery() {
|
|
if (zpd_active_query_is_fsi_) {
|
|
// The slot counter may be dirty if draws ran between OpenZPDQuery and
|
|
// here, but the next OpenZPDQuery clears it before any new shader adds.
|
|
zpd_host_query_pool_->ReleaseQueryIndex(zpd_active_query_index_,
|
|
zpd_active_query_generation_);
|
|
zpd_active_query_index_ = UINT32_MAX;
|
|
zpd_active_query_generation_ = 0;
|
|
zpd_active_query_is_fsi_ = false;
|
|
zpd_fsi_counter_index_force_update_ = true;
|
|
return true;
|
|
}
|
|
|
|
if (current_render_pass_ == VK_NULL_HANDLE) {
|
|
// vkCmdEndQuery is invalid outside a render pass for occlusion queries.
|
|
// Defer the release until the submission containing the stale BeginQuery
|
|
// completes on the GPU.
|
|
zpd_deferred_releases_.push_back({GetCurrentSubmission(),
|
|
zpd_active_query_index_,
|
|
zpd_active_query_generation_});
|
|
zpd_active_query_index_ = UINT32_MAX;
|
|
zpd_active_query_generation_ = 0;
|
|
zpd_active_query_is_fsi_ = false;
|
|
return true;
|
|
}
|
|
|
|
// Inside a render pass, EndQuery must be issued before releasing the slot.
|
|
zpd_host_query_pool_->EndQuery(deferred_command_buffer_,
|
|
zpd_active_query_index_);
|
|
zpd_host_query_pool_->ReleaseQueryIndex(zpd_active_query_index_,
|
|
zpd_active_query_generation_);
|
|
zpd_active_query_index_ = UINT32_MAX;
|
|
zpd_active_query_generation_ = 0;
|
|
zpd_active_query_is_fsi_ = false;
|
|
return true;
|
|
}
|
|
|
|
void VulkanCommandProcessor::PumpQueryResolves() {
|
|
if (GetZPDMode() == ZPDMode::kFake || !zpd_host_query_pool_) {
|
|
return;
|
|
}
|
|
|
|
uint64_t completed = GetCompletedSubmission();
|
|
if (completed == 0) {
|
|
return;
|
|
}
|
|
|
|
// Drain deferred releases first.
|
|
while (!zpd_deferred_releases_.empty()) {
|
|
auto& entry = zpd_deferred_releases_.front();
|
|
if (entry.submission > completed) {
|
|
break;
|
|
}
|
|
zpd_host_query_pool_->ReleaseQueryIndex(entry.query_index,
|
|
entry.query_generation);
|
|
zpd_deferred_releases_.pop_front();
|
|
}
|
|
|
|
// Invalidate CPU cache before reading results on non-coherent memory.
|
|
if (!zpd_resolves_in_flight_.empty() &&
|
|
zpd_resolves_in_flight_.front().submission <= completed) {
|
|
zpd_host_query_pool_->InvalidateReadback();
|
|
}
|
|
|
|
while (!zpd_resolves_in_flight_.empty()) {
|
|
if (zpd_resolves_in_flight_.front().submission > completed) {
|
|
break;
|
|
}
|
|
PendingQueryResolve resolve = zpd_resolves_in_flight_.front();
|
|
zpd_resolves_in_flight_.pop_front();
|
|
|
|
if (zpd_host_query_pool_->GenerationMatches(resolve.query_index,
|
|
resolve.query_generation)) {
|
|
uint64_t raw_samples = zpd_host_query_pool_->GetQueryReadbackValue(
|
|
resolve.query_index, resolve.uses_fsi_counter);
|
|
zpd_host_query_pool_->ReleaseQueryIndex(resolve.query_index,
|
|
resolve.query_generation);
|
|
OnZPDQueryResolved(resolve.report_handle, raw_samples);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VulkanCommandProcessor::AwaitQueryResolve(ReportHandle report_handle,
|
|
uint64_t wait_for_submission) {
|
|
if (GetZPDMode() == ZPDMode::kFake) {
|
|
return false;
|
|
}
|
|
|
|
PumpQueryResolves();
|
|
|
|
auto it = logical_zpd_reports_.find(report_handle);
|
|
if (it == logical_zpd_reports_.end()) {
|
|
return true;
|
|
}
|
|
if (it->second.pending_segments == 0 && it->second.ended) {
|
|
return true;
|
|
}
|
|
if (wait_for_submission == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Ensure the submission is flushed.
|
|
if (wait_for_submission >= GetCurrentSubmission()) {
|
|
if (!submission_open_) {
|
|
return false;
|
|
}
|
|
if (!CanEndSubmissionImmediately()) {
|
|
pipeline_cache_->AwaitPipelineCompletion();
|
|
}
|
|
EndRenderPass();
|
|
if (!EndSubmission(false)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (wait_for_submission > GetCompletedSubmission()) {
|
|
completion_timeline_.AwaitSubmissionAndUpdateCompleted(wait_for_submission);
|
|
}
|
|
|
|
PumpQueryResolves();
|
|
|
|
it = logical_zpd_reports_.find(report_handle);
|
|
return it == logical_zpd_reports_.end() ||
|
|
(it->second.pending_segments == 0 && it->second.ended);
|
|
}
|
|
|
|
void VulkanCommandProcessor::InitializeTrace() {
|
|
CommandProcessor::InitializeTrace();
|
|
|
|
if (!BeginSubmission(true)) {
|
|
return;
|
|
}
|
|
// TODO(Triang3l): Write the EDRAM.
|
|
bool shared_memory_submitted =
|
|
shared_memory_->InitializeTraceSubmitDownloads();
|
|
if (!shared_memory_submitted) {
|
|
return;
|
|
}
|
|
AwaitAllQueueOperationsCompletion();
|
|
if (shared_memory_submitted) {
|
|
shared_memory_->InitializeTraceCompleteDownloads();
|
|
}
|
|
}
|
|
|
|
void VulkanCommandProcessor::PollCompletedSubmission() {
|
|
// Strict ZPD can skip unnecessary work here that can wait for the next full
|
|
// CheckSubmissionCompletionAndDeviceLoss and it's not needed for retirement.
|
|
if (device_lost_) {
|
|
return;
|
|
}
|
|
completion_timeline_.AwaitSubmissionAndUpdateCompleted(
|
|
GetCompletedSubmission());
|
|
PumpQueryResolves();
|
|
}
|
|
|
|
void VulkanCommandProcessor::CheckSubmissionCompletionAndDeviceLoss(
|
|
uint64_t await_submission) {
|
|
// Only report once, no need to retry a wait that won't succeed anyway.
|
|
if (device_lost_) {
|
|
return;
|
|
}
|
|
|
|
if (await_submission >= GetCurrentSubmission()) {
|
|
if (submission_open_) {
|
|
EndSubmission(false);
|
|
}
|
|
// A submission won't be ended if it hasn't been started, or if ending
|
|
// has failed - clamp the index.
|
|
await_submission = GetCurrentSubmission() - 1;
|
|
}
|
|
|
|
completion_timeline_.AwaitSubmissionAndUpdateCompleted(await_submission);
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
|
|
if (vulkan_device->IsLost()) {
|
|
device_lost_ = true;
|
|
graphics_system_->OnHostGpuLossFromAnyThread(true);
|
|
return;
|
|
}
|
|
|
|
const uint64_t completed_submission = GetCompletedSubmission();
|
|
|
|
// Reclaim semaphores.
|
|
while (!submissions_in_flight_semaphores_.empty()) {
|
|
const auto& semaphore_submission =
|
|
submissions_in_flight_semaphores_.front();
|
|
if (semaphore_submission.first > completed_submission) {
|
|
break;
|
|
}
|
|
semaphores_free_.push_back(semaphore_submission.second);
|
|
submissions_in_flight_semaphores_.pop_front();
|
|
}
|
|
|
|
// Reclaim command pools.
|
|
while (!command_buffers_submitted_.empty()) {
|
|
const auto& command_buffer_pair = command_buffers_submitted_.front();
|
|
if (command_buffer_pair.first > completed_submission) {
|
|
break;
|
|
}
|
|
command_buffers_writable_.push_back(command_buffer_pair.second);
|
|
command_buffers_submitted_.pop_front();
|
|
}
|
|
|
|
shared_memory_->CompletedSubmissionUpdated();
|
|
|
|
primitive_processor_->CompletedSubmissionUpdated();
|
|
|
|
render_target_cache_->CompletedSubmissionUpdated();
|
|
|
|
texture_cache_->CompletedSubmissionUpdated(completed_submission);
|
|
|
|
PumpQueryResolves();
|
|
|
|
// Destroy objects scheduled for destruction.
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
while (!destroy_framebuffers_.empty()) {
|
|
const auto& destroy_pair = destroy_framebuffers_.front();
|
|
if (destroy_pair.first > completed_submission) {
|
|
break;
|
|
}
|
|
dfn.vkDestroyFramebuffer(device, destroy_pair.second, nullptr);
|
|
destroy_framebuffers_.pop_front();
|
|
}
|
|
while (!destroy_buffers_.empty()) {
|
|
const auto& destroy_pair = destroy_buffers_.front();
|
|
if (destroy_pair.first > completed_submission) {
|
|
break;
|
|
}
|
|
dfn.vkDestroyBuffer(device, destroy_pair.second, nullptr);
|
|
destroy_buffers_.pop_front();
|
|
}
|
|
while (!destroy_memory_.empty()) {
|
|
const auto& destroy_pair = destroy_memory_.front();
|
|
if (destroy_pair.first > completed_submission) {
|
|
break;
|
|
}
|
|
dfn.vkFreeMemory(device, destroy_pair.second, nullptr);
|
|
destroy_memory_.pop_front();
|
|
}
|
|
}
|
|
|
|
bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
|
|
if (device_lost_) {
|
|
return false;
|
|
}
|
|
|
|
bool is_opening_frame = is_guest_command && !frame_open_;
|
|
if (submission_open_ && !is_opening_frame) {
|
|
return true;
|
|
}
|
|
|
|
// Check the fence - needed for all kinds of submissions (to reclaim transient
|
|
// resources early) and specifically for frames (not to queue too many), and
|
|
// await the availability of the current frame. Also check whether the device
|
|
// is still available, and whether the await was successful.
|
|
uint64_t await_submission =
|
|
is_opening_frame
|
|
? closed_frame_submissions_[frame_current_ % kMaxFramesInFlight]
|
|
: 0;
|
|
CheckSubmissionCompletionAndDeviceLoss(await_submission);
|
|
const uint64_t completed_submission = GetCompletedSubmission();
|
|
if (device_lost_ || completed_submission < await_submission) {
|
|
return false;
|
|
}
|
|
|
|
if (is_opening_frame) {
|
|
// Update the completed frame index, also obtaining the actual completed
|
|
// frame number (since the CPU may be actually less than 3 frames behind)
|
|
// before reclaiming resources tracked with the frame number.
|
|
frame_completed_ = std::max(frame_current_, uint64_t(kMaxFramesInFlight)) -
|
|
kMaxFramesInFlight;
|
|
for (uint64_t frame = frame_completed_ + 1; frame < frame_current_;
|
|
++frame) {
|
|
if (closed_frame_submissions_[frame % kMaxFramesInFlight] >
|
|
completed_submission) {
|
|
break;
|
|
}
|
|
frame_completed_ = frame;
|
|
}
|
|
}
|
|
|
|
if (!submission_open_) {
|
|
submission_open_ = true;
|
|
|
|
// Start a new deferred command buffer - will submit it to the real one in
|
|
// the end of the submission (when async pipeline object creation requests
|
|
// are fulfilled).
|
|
deferred_command_buffer_.Reset();
|
|
|
|
// Reset cached state of the command buffer.
|
|
dynamic_viewport_update_needed_ = true;
|
|
dynamic_scissor_update_needed_ = true;
|
|
dynamic_depth_bias_update_needed_ = true;
|
|
dynamic_blend_constants_update_needed_ = true;
|
|
dynamic_stencil_compare_mask_front_update_needed_ = true;
|
|
dynamic_stencil_compare_mask_back_update_needed_ = true;
|
|
dynamic_stencil_write_mask_front_update_needed_ = true;
|
|
dynamic_stencil_write_mask_back_update_needed_ = true;
|
|
dynamic_stencil_reference_front_update_needed_ = true;
|
|
dynamic_stencil_reference_back_update_needed_ = true;
|
|
current_render_pass_ = VK_NULL_HANDLE;
|
|
current_framebuffer_ = nullptr;
|
|
current_guest_graphics_pipeline_ = VK_NULL_HANDLE;
|
|
current_external_graphics_pipeline_ = VK_NULL_HANDLE;
|
|
current_external_compute_pipeline_ = VK_NULL_HANDLE;
|
|
current_guest_graphics_pipeline_layout_ = nullptr;
|
|
current_graphics_descriptor_sets_bound_up_to_date_ = 0;
|
|
|
|
primitive_processor_->BeginSubmission();
|
|
|
|
texture_cache_->BeginSubmission(GetCurrentSubmission());
|
|
}
|
|
|
|
if (is_opening_frame) {
|
|
frame_open_ = true;
|
|
|
|
// Swap all readback buffers for delayed sync (one frame behind)
|
|
for (auto& pair : readback_buffers_) {
|
|
pair.second.current_index = 1 - pair.second.current_index;
|
|
}
|
|
|
|
// Evict old readback buffers only when map gets too large to prevent
|
|
// unbounded memory growth. Don't do this every frame as it's expensive.
|
|
if (readback_buffers_.size() > kMaxReadbackBuffers) {
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn =
|
|
vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
for (auto it = readback_buffers_.begin();
|
|
it != readback_buffers_.end();) {
|
|
// Evict if not used recently
|
|
if (frame_current_ > kReadbackBufferEvictionAgeFrames &&
|
|
it->second.last_used_frame <
|
|
frame_current_ - kReadbackBufferEvictionAgeFrames) {
|
|
// Release both buffers and memories
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
it->second.buffers[0]);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
it->second.memories[0]);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
it->second.buffers[1]);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
it->second.memories[1]);
|
|
it = readback_buffers_.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset bindings that depend on transient data.
|
|
std::memset(current_float_constant_map_vertex_, 0,
|
|
sizeof(current_float_constant_map_vertex_));
|
|
std::memset(current_float_constant_map_pixel_, 0,
|
|
sizeof(current_float_constant_map_pixel_));
|
|
std::memset(current_graphics_descriptor_sets_, 0,
|
|
sizeof(current_graphics_descriptor_sets_));
|
|
current_constant_buffers_up_to_date_ = 0;
|
|
current_graphics_descriptor_sets_
|
|
[SpirvShaderTranslator::kDescriptorSetSharedMemoryAndEdram] =
|
|
shared_memory_and_edram_descriptor_set_;
|
|
current_graphics_descriptor_set_values_up_to_date_ =
|
|
UINT32_C(1)
|
|
<< SpirvShaderTranslator::kDescriptorSetSharedMemoryAndEdram;
|
|
|
|
// Reclaim pool pages - no need to do this every small submission since some
|
|
// may be reused.
|
|
// FIXME(Triang3l): This will result in a memory leak if the guest is not
|
|
// presenting.
|
|
uniform_buffer_pool_->Reclaim(frame_completed_);
|
|
while (!single_transient_descriptors_used_.empty()) {
|
|
const UsedSingleTransientDescriptor& used_transient_descriptor =
|
|
single_transient_descriptors_used_.front();
|
|
if (used_transient_descriptor.frame > frame_completed_) {
|
|
break;
|
|
}
|
|
single_transient_descriptors_free_[size_t(
|
|
used_transient_descriptor.layout)]
|
|
.push_back(used_transient_descriptor.set);
|
|
single_transient_descriptors_used_.pop_front();
|
|
}
|
|
while (!constants_transient_descriptors_used_.empty()) {
|
|
const std::pair<uint64_t, VkDescriptorSet>& used_transient_descriptor =
|
|
constants_transient_descriptors_used_.front();
|
|
if (used_transient_descriptor.first > frame_completed_) {
|
|
break;
|
|
}
|
|
constants_transient_descriptors_free_.push_back(
|
|
used_transient_descriptor.second);
|
|
constants_transient_descriptors_used_.pop_front();
|
|
}
|
|
while (!texture_transient_descriptor_sets_used_.empty()) {
|
|
const UsedTextureTransientDescriptorSet& used_transient_descriptor_set =
|
|
texture_transient_descriptor_sets_used_.front();
|
|
if (used_transient_descriptor_set.frame > frame_completed_) {
|
|
break;
|
|
}
|
|
auto it = texture_transient_descriptor_sets_free_.find(
|
|
used_transient_descriptor_set.layout);
|
|
if (it == texture_transient_descriptor_sets_free_.end()) {
|
|
it =
|
|
texture_transient_descriptor_sets_free_
|
|
.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(used_transient_descriptor_set.layout),
|
|
std::forward_as_tuple())
|
|
.first;
|
|
}
|
|
it->second.push_back(used_transient_descriptor_set.set);
|
|
texture_transient_descriptor_sets_used_.pop_front();
|
|
}
|
|
|
|
primitive_processor_->BeginFrame();
|
|
|
|
texture_cache_->BeginFrame();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::EndSubmission(bool is_swap) {
|
|
ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
// Make sure everything needed for submitting exist.
|
|
if (submission_open_) {
|
|
if (!sparse_memory_binds_.empty() && semaphores_free_.empty()) {
|
|
VkSemaphoreCreateInfo semaphore_create_info;
|
|
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
|
semaphore_create_info.pNext = nullptr;
|
|
semaphore_create_info.flags = 0;
|
|
VkSemaphore semaphore;
|
|
if (dfn.vkCreateSemaphore(device, &semaphore_create_info, nullptr,
|
|
&semaphore) != VK_SUCCESS) {
|
|
XELOGE("Failed to create a Vulkan semaphore");
|
|
return false;
|
|
}
|
|
semaphores_free_.push_back(semaphore);
|
|
}
|
|
if (command_buffers_writable_.empty()) {
|
|
CommandBuffer command_buffer;
|
|
VkCommandPoolCreateInfo command_pool_create_info;
|
|
command_pool_create_info.sType =
|
|
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
command_pool_create_info.pNext = nullptr;
|
|
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
|
|
command_pool_create_info.queueFamilyIndex =
|
|
vulkan_device->queue_family_graphics_compute();
|
|
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr,
|
|
&command_buffer.pool) != VK_SUCCESS) {
|
|
XELOGE("Failed to create a Vulkan command pool");
|
|
return false;
|
|
}
|
|
VkCommandBufferAllocateInfo command_buffer_allocate_info;
|
|
command_buffer_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
command_buffer_allocate_info.pNext = nullptr;
|
|
command_buffer_allocate_info.commandPool = command_buffer.pool;
|
|
command_buffer_allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
command_buffer_allocate_info.commandBufferCount = 1;
|
|
if (dfn.vkAllocateCommandBuffers(device, &command_buffer_allocate_info,
|
|
&command_buffer.buffer) != VK_SUCCESS) {
|
|
XELOGE("Failed to allocate a Vulkan command buffer");
|
|
dfn.vkDestroyCommandPool(device, command_buffer.pool, nullptr);
|
|
return false;
|
|
}
|
|
command_buffers_writable_.push_back(command_buffer);
|
|
}
|
|
}
|
|
|
|
bool is_closing_frame = is_swap && frame_open_;
|
|
|
|
if (is_closing_frame) {
|
|
primitive_processor_->EndFrame();
|
|
}
|
|
|
|
if (submission_open_) {
|
|
assert_false(scratch_buffer_used_);
|
|
|
|
EndRenderPass();
|
|
|
|
render_target_cache_->EndSubmission();
|
|
|
|
primitive_processor_->EndSubmission();
|
|
|
|
shared_memory_->EndSubmission();
|
|
|
|
uniform_buffer_pool_->FlushWrites();
|
|
|
|
// Submit sparse binds earlier, before executing the deferred command
|
|
// buffer, to reduce latency.
|
|
if (!sparse_memory_binds_.empty()) {
|
|
sparse_buffer_bind_infos_temp_.clear();
|
|
sparse_buffer_bind_infos_temp_.reserve(sparse_buffer_binds_.size());
|
|
for (const SparseBufferBind& sparse_buffer_bind : sparse_buffer_binds_) {
|
|
VkSparseBufferMemoryBindInfo& sparse_buffer_bind_info =
|
|
sparse_buffer_bind_infos_temp_.emplace_back();
|
|
sparse_buffer_bind_info.buffer = sparse_buffer_bind.buffer;
|
|
sparse_buffer_bind_info.bindCount = sparse_buffer_bind.bind_count;
|
|
sparse_buffer_bind_info.pBinds =
|
|
sparse_memory_binds_.data() + sparse_buffer_bind.bind_offset;
|
|
}
|
|
assert_false(semaphores_free_.empty());
|
|
VkSemaphore bind_sparse_semaphore = semaphores_free_.back();
|
|
VkBindSparseInfo bind_sparse_info;
|
|
bind_sparse_info.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO;
|
|
bind_sparse_info.pNext = nullptr;
|
|
bind_sparse_info.waitSemaphoreCount = 0;
|
|
bind_sparse_info.pWaitSemaphores = nullptr;
|
|
bind_sparse_info.bufferBindCount =
|
|
uint32_t(sparse_buffer_bind_infos_temp_.size());
|
|
bind_sparse_info.pBufferBinds =
|
|
!sparse_buffer_bind_infos_temp_.empty()
|
|
? sparse_buffer_bind_infos_temp_.data()
|
|
: nullptr;
|
|
bind_sparse_info.imageOpaqueBindCount = 0;
|
|
bind_sparse_info.pImageOpaqueBinds = nullptr;
|
|
bind_sparse_info.imageBindCount = 0;
|
|
bind_sparse_info.pImageBinds = 0;
|
|
bind_sparse_info.signalSemaphoreCount = 1;
|
|
bind_sparse_info.pSignalSemaphores = &bind_sparse_semaphore;
|
|
VkResult bind_sparse_result;
|
|
{
|
|
ui::vulkan::VulkanDevice::Queue::Acquisition queue_acquisition =
|
|
vulkan_device->AcquireQueue(
|
|
vulkan_device->queue_family_sparse_binding(), 0);
|
|
bind_sparse_result = dfn.vkQueueBindSparse(
|
|
queue_acquisition.queue(), 1, &bind_sparse_info, VK_NULL_HANDLE);
|
|
}
|
|
if (bind_sparse_result != VK_SUCCESS) {
|
|
XELOGE("Failed to submit Vulkan sparse binds");
|
|
return false;
|
|
}
|
|
current_submission_wait_semaphores_.push_back(bind_sparse_semaphore);
|
|
semaphores_free_.pop_back();
|
|
current_submission_wait_stage_masks_.push_back(
|
|
sparse_bind_wait_stage_mask_);
|
|
sparse_bind_wait_stage_mask_ = 0;
|
|
sparse_buffer_binds_.clear();
|
|
sparse_memory_binds_.clear();
|
|
}
|
|
|
|
// Can't cross command buffer boundaries. Close the active segment first.
|
|
CloseQuerySegment();
|
|
|
|
SubmitBarriers(true);
|
|
|
|
assert_false(command_buffers_writable_.empty());
|
|
CommandBuffer command_buffer = command_buffers_writable_.back();
|
|
if (dfn.vkResetCommandPool(device, command_buffer.pool, 0) != VK_SUCCESS) {
|
|
XELOGE("Failed to reset a Vulkan command pool");
|
|
return false;
|
|
}
|
|
VkCommandBufferBeginInfo command_buffer_begin_info;
|
|
command_buffer_begin_info.sType =
|
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
command_buffer_begin_info.pNext = nullptr;
|
|
command_buffer_begin_info.flags =
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
command_buffer_begin_info.pInheritanceInfo = nullptr;
|
|
if (dfn.vkBeginCommandBuffer(command_buffer.buffer,
|
|
&command_buffer_begin_info) != VK_SUCCESS) {
|
|
XELOGE("Failed to begin a Vulkan command buffer");
|
|
return false;
|
|
}
|
|
deferred_command_buffer_.Execute(command_buffer.buffer);
|
|
|
|
// Record ZPD resolves before submitting.
|
|
if (zpd_host_query_pool_) {
|
|
zpd_host_query_pool_->RecordResolveBatch(command_buffer.buffer);
|
|
}
|
|
|
|
if (dfn.vkEndCommandBuffer(command_buffer.buffer) != VK_SUCCESS) {
|
|
XELOGE("Failed to end a Vulkan command buffer");
|
|
return false;
|
|
}
|
|
|
|
const uint64_t submission_index = GetCurrentSubmission();
|
|
|
|
VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
if (!current_submission_wait_semaphores_.empty()) {
|
|
submit_info.waitSemaphoreCount =
|
|
uint32_t(current_submission_wait_semaphores_.size());
|
|
submit_info.pWaitSemaphores = current_submission_wait_semaphores_.data();
|
|
submit_info.pWaitDstStageMask =
|
|
current_submission_wait_stage_masks_.data();
|
|
}
|
|
submit_info.commandBufferCount = 1;
|
|
submit_info.pCommandBuffers = &command_buffer.buffer;
|
|
const VkResult submit_result = completion_timeline_.AcquireFenceAndSubmit(
|
|
vulkan_device->queue_family_graphics_compute(), 0, 1, &submit_info);
|
|
if (submit_result != VK_SUCCESS) {
|
|
XELOGE("Failed to submit a GPU emulation Vulkan command buffer: {}",
|
|
vk::to_string(vk::Result(submit_result)));
|
|
if (vulkan_device->IsLost() && !device_lost_) {
|
|
device_lost_ = true;
|
|
graphics_system_->OnHostGpuLossFromAnyThread(true);
|
|
}
|
|
return false;
|
|
}
|
|
current_submission_wait_stage_masks_.clear();
|
|
for (VkSemaphore semaphore : current_submission_wait_semaphores_) {
|
|
submissions_in_flight_semaphores_.emplace_back(submission_index,
|
|
semaphore);
|
|
}
|
|
current_submission_wait_semaphores_.clear();
|
|
command_buffers_submitted_.emplace_back(submission_index, command_buffer);
|
|
command_buffers_writable_.pop_back();
|
|
|
|
submission_open_ = false;
|
|
|
|
// Process any ZPD resolves that completed with this submission.
|
|
// Block if strict mode has a pending result waiting on the guest sentinel.
|
|
PumpQueryResolves();
|
|
PumpPendingRetire();
|
|
}
|
|
|
|
if (is_closing_frame) {
|
|
if (cvars::clear_memory_page_state) {
|
|
shared_memory_->SetSystemPageBlocksValidWithGpuDataWritten();
|
|
}
|
|
|
|
frame_open_ = false;
|
|
// Submission already closed now, so minus 1.
|
|
closed_frame_submissions_[(frame_current_++) % kMaxFramesInFlight] =
|
|
GetCurrentSubmission() - 1;
|
|
|
|
if (cache_clear_requested_ && AwaitAllQueueOperationsCompletion()) {
|
|
cache_clear_requested_ = false;
|
|
|
|
DestroyScratchBuffer();
|
|
|
|
for (SwapFramebuffer& swap_framebuffer : swap_framebuffers_) {
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyFramebuffer, device,
|
|
swap_framebuffer.framebuffer);
|
|
}
|
|
|
|
assert_true(command_buffers_submitted_.empty());
|
|
for (const CommandBuffer& command_buffer : command_buffers_writable_) {
|
|
dfn.vkDestroyCommandPool(device, command_buffer.pool, nullptr);
|
|
}
|
|
command_buffers_writable_.clear();
|
|
|
|
ClearTransientDescriptorPools();
|
|
|
|
uniform_buffer_pool_->ClearCache();
|
|
|
|
texture_cache_->ClearCache();
|
|
|
|
render_target_cache_->ClearCache();
|
|
|
|
// Not clearing the pipeline layouts and the descriptor set layouts as
|
|
// they're referenced by pipelines, which are not destroyed.
|
|
|
|
primitive_processor_->ClearCache();
|
|
|
|
shared_memory_->ClearCache();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VulkanCommandProcessor::CanEndSubmissionImmediately() const {
|
|
return !submission_open_ || !pipeline_cache_->IsCreatingPipelines();
|
|
}
|
|
|
|
void VulkanCommandProcessor::ClearTransientDescriptorPools() {
|
|
texture_transient_descriptor_sets_free_.clear();
|
|
texture_transient_descriptor_sets_used_.clear();
|
|
transient_descriptor_allocator_textures_.Reset();
|
|
|
|
constants_transient_descriptors_free_.clear();
|
|
constants_transient_descriptors_used_.clear();
|
|
for (std::vector<VkDescriptorSet>& transient_descriptors_free :
|
|
single_transient_descriptors_free_) {
|
|
transient_descriptors_free.clear();
|
|
}
|
|
single_transient_descriptors_used_.clear();
|
|
transient_descriptor_allocator_storage_buffer_.Reset();
|
|
transient_descriptor_allocator_uniform_buffer_.Reset();
|
|
}
|
|
|
|
void VulkanCommandProcessor::SplitPendingBarrier() {
|
|
size_t pending_buffer_memory_barrier_count =
|
|
pending_barriers_buffer_memory_barriers_.size();
|
|
size_t pending_image_memory_barrier_count =
|
|
pending_barriers_image_memory_barriers_.size();
|
|
if (!current_pending_barrier_.src_stage_mask &&
|
|
!current_pending_barrier_.dst_stage_mask &&
|
|
current_pending_barrier_.buffer_memory_barriers_offset >=
|
|
pending_buffer_memory_barrier_count &&
|
|
current_pending_barrier_.image_memory_barriers_offset >=
|
|
pending_image_memory_barrier_count) {
|
|
return;
|
|
}
|
|
pending_barriers_.emplace_back(current_pending_barrier_);
|
|
current_pending_barrier_.src_stage_mask = 0;
|
|
current_pending_barrier_.dst_stage_mask = 0;
|
|
current_pending_barrier_.buffer_memory_barriers_offset =
|
|
pending_buffer_memory_barrier_count;
|
|
current_pending_barrier_.image_memory_barriers_offset =
|
|
pending_image_memory_barrier_count;
|
|
}
|
|
|
|
void VulkanCommandProcessor::DestroyScratchBuffer() {
|
|
assert_false(scratch_buffer_used_);
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
scratch_buffer_last_usage_submission_ = 0;
|
|
scratch_buffer_last_access_mask_ = 0;
|
|
scratch_buffer_last_stage_mask_ = 0;
|
|
scratch_buffer_size_ = 0;
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
|
|
scratch_buffer_);
|
|
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
|
|
scratch_buffer_memory_);
|
|
}
|
|
|
|
void VulkanCommandProcessor::UpdateDynamicState(
|
|
const draw_util::ViewportInfo& viewport_info, bool primitive_polygonal,
|
|
reg::RB_DEPTHCONTROL normalized_depth_control,
|
|
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y) {
|
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
|
|
const RegisterFile& regs = *register_file_;
|
|
|
|
// Window parameters.
|
|
// http://ftp.tku.edu.tw/NetBSD/NetBSD-current/xsrc/external/mit/xf86-video-ati/dist/src/r600_reg_auto_r6xx.h
|
|
// See r200UpdateWindow:
|
|
// https://github.com/freedreno/mesa/blob/master/src/mesa/drivers/dri/r200/r200_state.c
|
|
auto pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
|
|
|
|
// Viewport.
|
|
VkViewport viewport;
|
|
if (viewport_info.xy_extent[0] && viewport_info.xy_extent[1]) {
|
|
viewport.x = float(viewport_info.xy_offset[0]);
|
|
viewport.y = float(viewport_info.xy_offset[1]);
|
|
viewport.width = float(viewport_info.xy_extent[0]);
|
|
viewport.height = float(viewport_info.xy_extent[1]);
|
|
} else {
|
|
// Vulkan viewport width must be greater than 0.0f, but the Xenia viewport
|
|
// may be empty for various reasons - set the viewport to outside the
|
|
// framebuffer.
|
|
viewport.x = -1.0f;
|
|
viewport.y = -1.0f;
|
|
viewport.width = 1.0f;
|
|
viewport.height = 1.0f;
|
|
}
|
|
viewport.minDepth = viewport_info.z_min;
|
|
viewport.maxDepth = viewport_info.z_max;
|
|
SetViewport(viewport);
|
|
|
|
// Scissor.
|
|
draw_util::Scissor scissor;
|
|
draw_util::GetScissor(regs, scissor);
|
|
// Scale the scissor to match the render target resolution scale
|
|
scissor.offset[0] *= draw_resolution_scale_x;
|
|
scissor.offset[1] *= draw_resolution_scale_y;
|
|
scissor.extent[0] *= draw_resolution_scale_x;
|
|
scissor.extent[1] *= draw_resolution_scale_y;
|
|
VkRect2D scissor_rect;
|
|
scissor_rect.offset.x = int32_t(scissor.offset[0]);
|
|
scissor_rect.offset.y = int32_t(scissor.offset[1]);
|
|
scissor_rect.extent.width = scissor.extent[0];
|
|
scissor_rect.extent.height = scissor.extent[1];
|
|
SetScissor(scissor_rect);
|
|
|
|
if (render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kHostRenderTargets) {
|
|
// Depth bias.
|
|
float depth_bias_constant_factor, depth_bias_slope_factor;
|
|
draw_util::GetPreferredFacePolygonOffset(regs, primitive_polygonal,
|
|
depth_bias_slope_factor,
|
|
depth_bias_constant_factor);
|
|
depth_bias_constant_factor *=
|
|
regs.Get<reg::RB_DEPTH_INFO>().depth_format ==
|
|
xenos::DepthRenderTargetFormat::kD24S8
|
|
? draw_util::kD3D10PolygonOffsetFactorUnorm24
|
|
: draw_util::kD3D10PolygonOffsetFactorFloat24;
|
|
// With non-square resolution scaling, make sure the worst-case impact is
|
|
// reverted (slope only along the scaled axis), thus max. More bias is
|
|
// better than less bias, because less bias means Z fighting with the
|
|
// background is more likely.
|
|
depth_bias_slope_factor *=
|
|
xenos::kPolygonOffsetScaleSubpixelUnit *
|
|
float(std::max(render_target_cache_->draw_resolution_scale_x(),
|
|
render_target_cache_->draw_resolution_scale_y()));
|
|
// std::memcmp instead of != so in case of NaN, every draw won't be
|
|
// invalidating it.
|
|
dynamic_depth_bias_update_needed_ |=
|
|
std::memcmp(&dynamic_depth_bias_constant_factor_,
|
|
&depth_bias_constant_factor, sizeof(float)) != 0;
|
|
dynamic_depth_bias_update_needed_ |=
|
|
std::memcmp(&dynamic_depth_bias_slope_factor_, &depth_bias_slope_factor,
|
|
sizeof(float)) != 0;
|
|
if (dynamic_depth_bias_update_needed_) {
|
|
dynamic_depth_bias_constant_factor_ = depth_bias_constant_factor;
|
|
dynamic_depth_bias_slope_factor_ = depth_bias_slope_factor;
|
|
deferred_command_buffer_.CmdVkSetDepthBias(
|
|
dynamic_depth_bias_constant_factor_, 0.0f,
|
|
dynamic_depth_bias_slope_factor_);
|
|
dynamic_depth_bias_update_needed_ = false;
|
|
}
|
|
|
|
// Blend constants.
|
|
float blend_constants[] = {
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_RED),
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_GREEN),
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_BLUE),
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_ALPHA),
|
|
};
|
|
dynamic_blend_constants_update_needed_ |=
|
|
std::memcmp(dynamic_blend_constants_, blend_constants,
|
|
sizeof(float) * 4) != 0;
|
|
if (dynamic_blend_constants_update_needed_) {
|
|
std::memcpy(dynamic_blend_constants_, blend_constants, sizeof(float) * 4);
|
|
deferred_command_buffer_.CmdVkSetBlendConstants(dynamic_blend_constants_);
|
|
dynamic_blend_constants_update_needed_ = false;
|
|
}
|
|
|
|
// Stencil masks and references.
|
|
// Due to pretty complex conditions involving registers not directly related
|
|
// to stencil (primitive type, culling), changing the values only when
|
|
// stencil is actually needed. However, due to the way dynamic state needs
|
|
// to be set in Vulkan, which doesn't take into account whether the state
|
|
// actually has effect on drawing, and because the masks and the references
|
|
// are always dynamic in Xenia guest pipelines, they must be set in the
|
|
// command buffer before any draw.
|
|
if (normalized_depth_control.stencil_enable) {
|
|
Register stencil_ref_mask_front_reg, stencil_ref_mask_back_reg;
|
|
if (primitive_polygonal && normalized_depth_control.backface_enable) {
|
|
if (GetVulkanDevice()->properties().separateStencilMaskRef) {
|
|
stencil_ref_mask_front_reg = XE_GPU_REG_RB_STENCILREFMASK;
|
|
stencil_ref_mask_back_reg = XE_GPU_REG_RB_STENCILREFMASK_BF;
|
|
} else {
|
|
// Choose the back face values only if drawing only back faces.
|
|
stencil_ref_mask_front_reg =
|
|
regs.Get<reg::PA_SU_SC_MODE_CNTL>().cull_front
|
|
? XE_GPU_REG_RB_STENCILREFMASK_BF
|
|
: XE_GPU_REG_RB_STENCILREFMASK;
|
|
stencil_ref_mask_back_reg = stencil_ref_mask_front_reg;
|
|
}
|
|
} else {
|
|
stencil_ref_mask_front_reg = XE_GPU_REG_RB_STENCILREFMASK;
|
|
stencil_ref_mask_back_reg = XE_GPU_REG_RB_STENCILREFMASK;
|
|
}
|
|
auto stencil_ref_mask_front =
|
|
regs.Get<reg::RB_STENCILREFMASK>(stencil_ref_mask_front_reg);
|
|
auto stencil_ref_mask_back =
|
|
regs.Get<reg::RB_STENCILREFMASK>(stencil_ref_mask_back_reg);
|
|
// Compare mask.
|
|
dynamic_stencil_compare_mask_front_update_needed_ |=
|
|
dynamic_stencil_compare_mask_front_ !=
|
|
stencil_ref_mask_front.stencilmask;
|
|
dynamic_stencil_compare_mask_front_ = stencil_ref_mask_front.stencilmask;
|
|
dynamic_stencil_compare_mask_back_update_needed_ |=
|
|
dynamic_stencil_compare_mask_back_ !=
|
|
stencil_ref_mask_back.stencilmask;
|
|
dynamic_stencil_compare_mask_back_ = stencil_ref_mask_back.stencilmask;
|
|
// Write mask.
|
|
dynamic_stencil_write_mask_front_update_needed_ |=
|
|
dynamic_stencil_write_mask_front_ !=
|
|
stencil_ref_mask_front.stencilwritemask;
|
|
dynamic_stencil_write_mask_front_ =
|
|
stencil_ref_mask_front.stencilwritemask;
|
|
dynamic_stencil_write_mask_back_update_needed_ |=
|
|
dynamic_stencil_write_mask_back_ !=
|
|
stencil_ref_mask_back.stencilwritemask;
|
|
dynamic_stencil_write_mask_back_ = stencil_ref_mask_back.stencilwritemask;
|
|
// Reference.
|
|
dynamic_stencil_reference_front_update_needed_ |=
|
|
dynamic_stencil_reference_front_ != stencil_ref_mask_front.stencilref;
|
|
dynamic_stencil_reference_front_ = stencil_ref_mask_front.stencilref;
|
|
dynamic_stencil_reference_back_update_needed_ |=
|
|
dynamic_stencil_reference_back_ != stencil_ref_mask_back.stencilref;
|
|
dynamic_stencil_reference_back_ = stencil_ref_mask_back.stencilref;
|
|
}
|
|
// Using VK_STENCIL_FACE_FRONT_AND_BACK for higher safety when running on
|
|
// the Vulkan portability subset without separateStencilMaskRef.
|
|
if (dynamic_stencil_compare_mask_front_update_needed_ ||
|
|
dynamic_stencil_compare_mask_back_update_needed_) {
|
|
if (dynamic_stencil_compare_mask_front_ ==
|
|
dynamic_stencil_compare_mask_back_) {
|
|
deferred_command_buffer_.CmdVkSetStencilCompareMask(
|
|
VK_STENCIL_FACE_FRONT_AND_BACK,
|
|
dynamic_stencil_compare_mask_front_);
|
|
} else {
|
|
if (dynamic_stencil_compare_mask_front_update_needed_) {
|
|
deferred_command_buffer_.CmdVkSetStencilCompareMask(
|
|
VK_STENCIL_FACE_FRONT_BIT, dynamic_stencil_compare_mask_front_);
|
|
}
|
|
if (dynamic_stencil_compare_mask_back_update_needed_) {
|
|
deferred_command_buffer_.CmdVkSetStencilCompareMask(
|
|
VK_STENCIL_FACE_BACK_BIT, dynamic_stencil_compare_mask_back_);
|
|
}
|
|
}
|
|
dynamic_stencil_compare_mask_front_update_needed_ = false;
|
|
dynamic_stencil_compare_mask_back_update_needed_ = false;
|
|
}
|
|
if (dynamic_stencil_write_mask_front_update_needed_ ||
|
|
dynamic_stencil_write_mask_back_update_needed_) {
|
|
if (dynamic_stencil_write_mask_front_ ==
|
|
dynamic_stencil_write_mask_back_) {
|
|
deferred_command_buffer_.CmdVkSetStencilWriteMask(
|
|
VK_STENCIL_FACE_FRONT_AND_BACK, dynamic_stencil_write_mask_front_);
|
|
} else {
|
|
if (dynamic_stencil_write_mask_front_update_needed_) {
|
|
deferred_command_buffer_.CmdVkSetStencilWriteMask(
|
|
VK_STENCIL_FACE_FRONT_BIT, dynamic_stencil_write_mask_front_);
|
|
}
|
|
if (dynamic_stencil_write_mask_back_update_needed_) {
|
|
deferred_command_buffer_.CmdVkSetStencilWriteMask(
|
|
VK_STENCIL_FACE_BACK_BIT, dynamic_stencil_write_mask_back_);
|
|
}
|
|
}
|
|
dynamic_stencil_write_mask_front_update_needed_ = false;
|
|
dynamic_stencil_write_mask_back_update_needed_ = false;
|
|
}
|
|
if (dynamic_stencil_reference_front_update_needed_ ||
|
|
dynamic_stencil_reference_back_update_needed_) {
|
|
if (dynamic_stencil_reference_front_ == dynamic_stencil_reference_back_) {
|
|
deferred_command_buffer_.CmdVkSetStencilReference(
|
|
VK_STENCIL_FACE_FRONT_AND_BACK, dynamic_stencil_reference_front_);
|
|
} else {
|
|
if (dynamic_stencil_reference_front_update_needed_) {
|
|
deferred_command_buffer_.CmdVkSetStencilReference(
|
|
VK_STENCIL_FACE_FRONT_BIT, dynamic_stencil_reference_front_);
|
|
}
|
|
if (dynamic_stencil_reference_back_update_needed_) {
|
|
deferred_command_buffer_.CmdVkSetStencilReference(
|
|
VK_STENCIL_FACE_BACK_BIT, dynamic_stencil_reference_back_);
|
|
}
|
|
}
|
|
dynamic_stencil_reference_front_update_needed_ = false;
|
|
dynamic_stencil_reference_back_update_needed_ = false;
|
|
}
|
|
}
|
|
|
|
// TODO(Triang3l): VK_EXT_extended_dynamic_state and
|
|
// VK_EXT_extended_dynamic_state2.
|
|
}
|
|
|
|
void VulkanCommandProcessor::UpdateSystemConstantValues(
|
|
bool primitive_polygonal,
|
|
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
|
|
bool shader_32bit_index_dma, const draw_util::ViewportInfo& viewport_info,
|
|
uint32_t used_texture_mask, reg::RB_DEPTHCONTROL normalized_depth_control,
|
|
uint32_t normalized_color_mask) {
|
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
|
|
const RegisterFile& regs = *register_file_;
|
|
auto pa_cl_vte_cntl = regs.Get<reg::PA_CL_VTE_CNTL>();
|
|
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
|
|
auto rb_alpha_ref = regs.Get<float>(XE_GPU_REG_RB_ALPHA_REF);
|
|
auto rb_colorcontrol = regs.Get<reg::RB_COLORCONTROL>();
|
|
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
|
|
auto rb_stencilrefmask = regs.Get<reg::RB_STENCILREFMASK>();
|
|
auto rb_stencilrefmask_bf =
|
|
regs.Get<reg::RB_STENCILREFMASK>(XE_GPU_REG_RB_STENCILREFMASK_BF);
|
|
auto rb_surface_info = regs.Get<reg::RB_SURFACE_INFO>();
|
|
auto vgt_draw_initiator = regs.Get<reg::VGT_DRAW_INITIATOR>();
|
|
auto vgt_indx_offset = regs.Get<int32_t>(XE_GPU_REG_VGT_INDX_OFFSET);
|
|
|
|
bool edram_fragment_shader_interlock =
|
|
render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kPixelShaderInterlock;
|
|
uint32_t draw_resolution_scale_x = texture_cache_->draw_resolution_scale_x();
|
|
uint32_t draw_resolution_scale_y = texture_cache_->draw_resolution_scale_y();
|
|
|
|
// Get the color info register values for each render target. Also, for FSI,
|
|
// exclude components that don't exist in the format from the write mask.
|
|
// Don't exclude fully overlapping render targets, however - two render
|
|
// targets with the same base address are used in the lighting pass of
|
|
// 4D5307E6, for example, with the needed one picked with dynamic control
|
|
// flow.
|
|
reg::RB_COLOR_INFO color_infos[xenos::kMaxColorRenderTargets];
|
|
float rt_clamp[4][4];
|
|
// Two UINT32_MAX if no components actually existing in the RT are written.
|
|
uint32_t rt_keep_masks[4][2];
|
|
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
|
|
auto color_info = regs.Get<reg::RB_COLOR_INFO>(
|
|
reg::RB_COLOR_INFO::rt_register_indices[i]);
|
|
color_infos[i] = color_info;
|
|
if (edram_fragment_shader_interlock) {
|
|
RenderTargetCache::GetPSIColorFormatInfo(
|
|
color_info.color_format, (normalized_color_mask >> (i * 4)) & 0b1111,
|
|
rt_clamp[i][0], rt_clamp[i][1], rt_clamp[i][2], rt_clamp[i][3],
|
|
rt_keep_masks[i][0], rt_keep_masks[i][1]);
|
|
}
|
|
}
|
|
|
|
// Disable depth and stencil if it aliases a color render target (for
|
|
// instance, during the XBLA logo in 58410954, though depth writing is already
|
|
// disabled there).
|
|
bool depth_stencil_enabled = normalized_depth_control.stencil_enable ||
|
|
normalized_depth_control.z_enable;
|
|
if (edram_fragment_shader_interlock && depth_stencil_enabled) {
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
if (rb_depth_info.depth_base == color_infos[i].color_base &&
|
|
(rt_keep_masks[i][0] != UINT32_MAX ||
|
|
rt_keep_masks[i][1] != UINT32_MAX)) {
|
|
depth_stencil_enabled = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool dirty = false;
|
|
|
|
// Flags.
|
|
uint32_t flags = 0;
|
|
// Vertex index shader loading.
|
|
if (shader_32bit_index_dma) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_VertexIndexLoad;
|
|
}
|
|
if (primitive_processing_result.index_buffer_type ==
|
|
PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForDMA) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_ComputeOrPrimitiveVertexIndexLoad;
|
|
if (vgt_draw_initiator.index_size == xenos::IndexFormat::kInt32) {
|
|
flags |= SpirvShaderTranslator ::
|
|
kSysFlag_ComputeOrPrimitiveVertexIndexLoad32Bit;
|
|
}
|
|
}
|
|
// W0 division control.
|
|
// http://www.x.org/docs/AMD/old/evergreen_3D_registers_v2.pdf
|
|
// 8: VTX_XY_FMT = true: the incoming XY have already been multiplied by 1/W0.
|
|
// = false: multiply the X, Y coordinates by 1/W0.
|
|
// 9: VTX_Z_FMT = true: the incoming Z has already been multiplied by 1/W0.
|
|
// = false: multiply the Z coordinate by 1/W0.
|
|
// 10: VTX_W0_FMT = true: the incoming W0 is not 1/W0. Perform the reciprocal
|
|
// to get 1/W0.
|
|
if (pa_cl_vte_cntl.vtx_xy_fmt) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_XYDividedByW;
|
|
}
|
|
if (pa_cl_vte_cntl.vtx_z_fmt) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_ZDividedByW;
|
|
}
|
|
if (pa_cl_vte_cntl.vtx_w0_fmt) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_WNotReciprocal;
|
|
}
|
|
// Whether the primitive is polygonal, and gl_FrontFacing matters.
|
|
if (primitive_polygonal) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_PrimitivePolygonal;
|
|
}
|
|
// Primitive type.
|
|
if (draw_util::IsPrimitiveLine(regs)) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_PrimitiveLine;
|
|
}
|
|
// MSAA sample count.
|
|
flags |= uint32_t(rb_surface_info.msaa_samples)
|
|
<< SpirvShaderTranslator::kSysFlag_MsaaSamples_Shift;
|
|
// Depth format.
|
|
if (rb_depth_info.depth_format == xenos::DepthRenderTargetFormat::kD24FS8) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_DepthFloat24;
|
|
}
|
|
// Alpha test.
|
|
xenos::CompareFunction alpha_test_function =
|
|
rb_colorcontrol.alpha_test_enable ? rb_colorcontrol.alpha_func
|
|
: xenos::CompareFunction::kAlways;
|
|
flags |= uint32_t(alpha_test_function)
|
|
<< SpirvShaderTranslator::kSysFlag_AlphaPassIfLess_Shift;
|
|
// Gamma writing.
|
|
// TODO(Triang3l): Gamma as unorm8 check.
|
|
if (!edram_fragment_shader_interlock) {
|
|
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
|
|
if (color_infos[i].color_format ==
|
|
xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_ConvertColor0ToGamma << i;
|
|
}
|
|
}
|
|
}
|
|
if (edram_fragment_shader_interlock && depth_stencil_enabled) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthStencil;
|
|
if (normalized_depth_control.z_enable) {
|
|
flags |= uint32_t(normalized_depth_control.zfunc)
|
|
<< SpirvShaderTranslator::kSysFlag_FSIDepthPassIfLess_Shift;
|
|
if (normalized_depth_control.z_write_enable) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthWrite;
|
|
}
|
|
} else {
|
|
// In case stencil is used without depth testing - always pass, and
|
|
// don't modify the stored depth.
|
|
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthPassIfLess |
|
|
SpirvShaderTranslator::kSysFlag_FSIDepthPassIfEqual |
|
|
SpirvShaderTranslator::kSysFlag_FSIDepthPassIfGreater;
|
|
}
|
|
if (normalized_depth_control.stencil_enable) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_FSIStencilTest;
|
|
}
|
|
// Hint - if not applicable to the shader, will not have effect.
|
|
if (alpha_test_function == xenos::CompareFunction::kAlways &&
|
|
!rb_colorcontrol.alpha_to_mask_enable) {
|
|
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthStencilEarlyWrite;
|
|
}
|
|
}
|
|
dirty |= system_constants_.flags != flags;
|
|
system_constants_.flags = flags;
|
|
|
|
// Index buffer address for loading in the shaders.
|
|
if (flags &
|
|
(SpirvShaderTranslator::kSysFlag_VertexIndexLoad |
|
|
SpirvShaderTranslator::kSysFlag_ComputeOrPrimitiveVertexIndexLoad)) {
|
|
dirty |= system_constants_.vertex_index_load_address !=
|
|
primitive_processing_result.guest_index_base;
|
|
system_constants_.vertex_index_load_address =
|
|
primitive_processing_result.guest_index_base;
|
|
}
|
|
|
|
// Index or tessellation edge factor buffer endianness.
|
|
dirty |= system_constants_.vertex_index_endian !=
|
|
primitive_processing_result.host_shader_index_endian;
|
|
system_constants_.vertex_index_endian =
|
|
primitive_processing_result.host_shader_index_endian;
|
|
|
|
// Vertex index offset.
|
|
dirty |= system_constants_.vertex_base_index != vgt_indx_offset;
|
|
system_constants_.vertex_base_index = vgt_indx_offset;
|
|
|
|
// Conversion to host normalized device coordinates.
|
|
for (uint32_t i = 0; i < 3; ++i) {
|
|
dirty |= system_constants_.ndc_scale[i] != viewport_info.ndc_scale[i];
|
|
dirty |= system_constants_.ndc_offset[i] != viewport_info.ndc_offset[i];
|
|
system_constants_.ndc_scale[i] = viewport_info.ndc_scale[i];
|
|
system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i];
|
|
}
|
|
|
|
// User clip planes (for vertex shaders)
|
|
auto pa_cl_clip_cntl = regs.Get<reg::PA_CL_CLIP_CNTL>();
|
|
if (!pa_cl_clip_cntl.clip_disable && pa_cl_clip_cntl.ucp_ena) {
|
|
float* user_clip_plane_write_ptr =
|
|
clip_plane_constants_.user_clip_planes[0];
|
|
uint32_t user_clip_planes_remaining = pa_cl_clip_cntl.ucp_ena;
|
|
uint32_t user_clip_plane_index;
|
|
while (xe::bit_scan_forward(user_clip_planes_remaining,
|
|
&user_clip_plane_index)) {
|
|
user_clip_planes_remaining =
|
|
xe::clear_lowest_bit(user_clip_planes_remaining);
|
|
// Validate plane index is within bounds (0-5).
|
|
assert(user_clip_plane_index < 6);
|
|
if (user_clip_plane_index >= 6) {
|
|
continue;
|
|
}
|
|
const void* user_clip_plane_regs =
|
|
®s[XE_GPU_REG_PA_CL_UCP_0_X + user_clip_plane_index * 4];
|
|
if (std::memcmp(user_clip_plane_write_ptr, user_clip_plane_regs,
|
|
4 * sizeof(float))) {
|
|
dirty = true;
|
|
std::memcpy(user_clip_plane_write_ptr, user_clip_plane_regs,
|
|
4 * sizeof(float));
|
|
}
|
|
user_clip_plane_write_ptr += 4;
|
|
}
|
|
}
|
|
|
|
// Point size.
|
|
if (vgt_draw_initiator.prim_type == xenos::PrimitiveType::kPointList) {
|
|
auto pa_su_point_minmax = regs.Get<reg::PA_SU_POINT_MINMAX>();
|
|
auto pa_su_point_size = regs.Get<reg::PA_SU_POINT_SIZE>();
|
|
float point_vertex_diameter_min =
|
|
float(pa_su_point_minmax.min_size) * (2.0f / 16.0f);
|
|
float point_vertex_diameter_max =
|
|
float(pa_su_point_minmax.max_size) * (2.0f / 16.0f);
|
|
float point_constant_diameter_x =
|
|
float(pa_su_point_size.width) * (2.0f / 16.0f);
|
|
float point_constant_diameter_y =
|
|
float(pa_su_point_size.height) * (2.0f / 16.0f);
|
|
dirty |= system_constants_.point_vertex_diameter_min !=
|
|
point_vertex_diameter_min;
|
|
dirty |= system_constants_.point_vertex_diameter_max !=
|
|
point_vertex_diameter_max;
|
|
dirty |= system_constants_.point_constant_diameter[0] !=
|
|
point_constant_diameter_x;
|
|
dirty |= system_constants_.point_constant_diameter[1] !=
|
|
point_constant_diameter_y;
|
|
system_constants_.point_vertex_diameter_min = point_vertex_diameter_min;
|
|
system_constants_.point_vertex_diameter_max = point_vertex_diameter_max;
|
|
system_constants_.point_constant_diameter[0] = point_constant_diameter_x;
|
|
system_constants_.point_constant_diameter[1] = point_constant_diameter_y;
|
|
// 2 because 1 in the NDC is half of the viewport's axis, 0.5 for diameter
|
|
// to radius conversion to avoid multiplying the per-vertex diameter by an
|
|
// additional constant in the shader.
|
|
float point_screen_diameter_to_ndc_radius_x =
|
|
(/* 0.5f * 2.0f * */ float(draw_resolution_scale_x)) /
|
|
std::max(viewport_info.xy_extent[0], uint32_t(1));
|
|
float point_screen_diameter_to_ndc_radius_y =
|
|
(/* 0.5f * 2.0f * */ float(draw_resolution_scale_y)) /
|
|
std::max(viewport_info.xy_extent[1], uint32_t(1));
|
|
dirty |= system_constants_.point_screen_diameter_to_ndc_radius[0] !=
|
|
point_screen_diameter_to_ndc_radius_x;
|
|
dirty |= system_constants_.point_screen_diameter_to_ndc_radius[1] !=
|
|
point_screen_diameter_to_ndc_radius_y;
|
|
system_constants_.point_screen_diameter_to_ndc_radius[0] =
|
|
point_screen_diameter_to_ndc_radius_x;
|
|
system_constants_.point_screen_diameter_to_ndc_radius[1] =
|
|
point_screen_diameter_to_ndc_radius_y;
|
|
}
|
|
|
|
// Texture signedness / gamma.
|
|
{
|
|
uint32_t textures_remaining = used_texture_mask;
|
|
uint32_t texture_index;
|
|
while (xe::bit_scan_forward(textures_remaining, &texture_index)) {
|
|
textures_remaining &= ~(UINT32_C(1) << texture_index);
|
|
uint32_t& texture_signs_uint =
|
|
system_constants_.texture_swizzled_signs[texture_index >> 2];
|
|
uint32_t texture_signs_shift = 8 * (texture_index & 3);
|
|
uint8_t texture_signs =
|
|
texture_cache_->GetActiveTextureSwizzledSigns(texture_index);
|
|
uint32_t texture_signs_shifted = uint32_t(texture_signs)
|
|
<< texture_signs_shift;
|
|
uint32_t texture_signs_mask = ((UINT32_C(1) << 8) - 1)
|
|
<< texture_signs_shift;
|
|
dirty |=
|
|
(texture_signs_uint & texture_signs_mask) != texture_signs_shifted;
|
|
texture_signs_uint =
|
|
(texture_signs_uint & ~texture_signs_mask) | texture_signs_shifted;
|
|
}
|
|
}
|
|
|
|
// Texture host swizzle in the shader.
|
|
if (!GetVulkanDevice()->properties().imageViewFormatSwizzle) {
|
|
uint32_t textures_remaining = used_texture_mask;
|
|
uint32_t texture_index;
|
|
while (xe::bit_scan_forward(textures_remaining, &texture_index)) {
|
|
textures_remaining &= ~(UINT32_C(1) << texture_index);
|
|
uint32_t& texture_swizzles_uint =
|
|
system_constants_.texture_swizzles[texture_index >> 1];
|
|
uint32_t texture_swizzle_shift = 12 * (texture_index & 1);
|
|
uint32_t texture_swizzle =
|
|
texture_cache_->GetActiveTextureHostSwizzle(texture_index);
|
|
uint32_t texture_swizzle_shifted = uint32_t(texture_swizzle)
|
|
<< texture_swizzle_shift;
|
|
uint32_t texture_swizzle_mask = ((UINT32_C(1) << 12) - 1)
|
|
<< texture_swizzle_shift;
|
|
dirty |= (texture_swizzles_uint & texture_swizzle_mask) !=
|
|
texture_swizzle_shifted;
|
|
texture_swizzles_uint = (texture_swizzles_uint & ~texture_swizzle_mask) |
|
|
texture_swizzle_shifted;
|
|
}
|
|
}
|
|
|
|
// Textures resolved - which textures are from resolve operations (scaled).
|
|
{
|
|
uint32_t textures_resolved = 0;
|
|
uint32_t textures_remaining = used_texture_mask;
|
|
uint32_t texture_index;
|
|
while (xe::bit_scan_forward(textures_remaining, &texture_index)) {
|
|
textures_remaining &= ~(UINT32_C(1) << texture_index);
|
|
textures_resolved |=
|
|
uint32_t(
|
|
texture_cache_->IsActiveTextureResolutionScaled(texture_index))
|
|
<< texture_index;
|
|
}
|
|
dirty |= system_constants_.textures_resolved != textures_resolved;
|
|
system_constants_.textures_resolved = textures_resolved;
|
|
}
|
|
|
|
// Alpha test.
|
|
dirty |= system_constants_.alpha_test_reference != rb_alpha_ref;
|
|
system_constants_.alpha_test_reference = rb_alpha_ref;
|
|
|
|
// Alpha to coverage.
|
|
uint32_t alpha_to_mask = rb_colorcontrol.alpha_to_mask_enable
|
|
? (rb_colorcontrol.value >> 24) | (1 << 8)
|
|
: 0;
|
|
dirty |= system_constants_.alpha_to_mask != alpha_to_mask;
|
|
system_constants_.alpha_to_mask = alpha_to_mask;
|
|
|
|
// FSI ZPD counter.
|
|
uint32_t zpd_fsi_counter_index = UINT32_MAX;
|
|
if (edram_fragment_shader_interlock &&
|
|
zpd_active_query_index_ != UINT32_MAX && zpd_active_query_is_fsi_ &&
|
|
zpd_host_query_pool_->fsi_initialized()) {
|
|
zpd_fsi_counter_index = zpd_active_query_index_;
|
|
}
|
|
dirty |= zpd_fsi_counter_index_force_update_ ||
|
|
system_constants_.zpd_fsi_counter_index != zpd_fsi_counter_index;
|
|
system_constants_.zpd_fsi_counter_index = zpd_fsi_counter_index;
|
|
zpd_fsi_counter_index_force_update_ = false;
|
|
|
|
uint32_t edram_tile_dwords_scaled =
|
|
xenos::kEdramTileWidthSamples * xenos::kEdramTileHeightSamples *
|
|
(draw_resolution_scale_x * draw_resolution_scale_y);
|
|
|
|
// EDRAM pitch for FSI render target writing.
|
|
if (edram_fragment_shader_interlock) {
|
|
// Align, then multiply by 32bpp tile size in dwords.
|
|
uint32_t edram_32bpp_tile_pitch_dwords_scaled =
|
|
((rb_surface_info.surface_pitch *
|
|
(rb_surface_info.msaa_samples >= xenos::MsaaSamples::k4X ? 2 : 1)) +
|
|
(xenos::kEdramTileWidthSamples - 1)) /
|
|
xenos::kEdramTileWidthSamples * edram_tile_dwords_scaled;
|
|
dirty |= system_constants_.edram_32bpp_tile_pitch_dwords_scaled !=
|
|
edram_32bpp_tile_pitch_dwords_scaled;
|
|
system_constants_.edram_32bpp_tile_pitch_dwords_scaled =
|
|
edram_32bpp_tile_pitch_dwords_scaled;
|
|
}
|
|
|
|
// Color exponent bias and FSI render target writing.
|
|
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
|
|
reg::RB_COLOR_INFO color_info = color_infos[i];
|
|
// Exponent bias is in bits 20:25 of RB_COLOR_INFO.
|
|
int32_t color_exp_bias = color_info.color_exp_bias;
|
|
if (render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kHostRenderTargets &&
|
|
(color_info.color_format == xenos::ColorRenderTargetFormat::k_16_16 &&
|
|
!render_target_cache_->IsFixedRG16TruncatedToMinus1To1() ||
|
|
color_info.color_format ==
|
|
xenos::ColorRenderTargetFormat::k_16_16_16_16 &&
|
|
!render_target_cache_->IsFixedRGBA16TruncatedToMinus1To1())) {
|
|
// Remap from -32...32 to -1...1 by dividing the output values by 32,
|
|
// losing blending correctness, but getting the full range.
|
|
color_exp_bias -= 5;
|
|
}
|
|
float color_exp_bias_scale;
|
|
*reinterpret_cast<int32_t*>(&color_exp_bias_scale) =
|
|
UINT32_C(0x3F800000) + (color_exp_bias << 23);
|
|
dirty |= system_constants_.color_exp_bias[i] != color_exp_bias_scale;
|
|
system_constants_.color_exp_bias[i] = color_exp_bias_scale;
|
|
if (edram_fragment_shader_interlock) {
|
|
dirty |=
|
|
system_constants_.edram_rt_keep_mask[i][0] != rt_keep_masks[i][0];
|
|
system_constants_.edram_rt_keep_mask[i][0] = rt_keep_masks[i][0];
|
|
dirty |=
|
|
system_constants_.edram_rt_keep_mask[i][1] != rt_keep_masks[i][1];
|
|
system_constants_.edram_rt_keep_mask[i][1] = rt_keep_masks[i][1];
|
|
if (rt_keep_masks[i][0] != UINT32_MAX ||
|
|
rt_keep_masks[i][1] != UINT32_MAX) {
|
|
uint32_t rt_base_dwords_scaled =
|
|
color_info.color_base * edram_tile_dwords_scaled;
|
|
dirty |= system_constants_.edram_rt_base_dwords_scaled[i] !=
|
|
rt_base_dwords_scaled;
|
|
system_constants_.edram_rt_base_dwords_scaled[i] =
|
|
rt_base_dwords_scaled;
|
|
uint32_t format_flags =
|
|
RenderTargetCache::AddPSIColorFormatFlags(color_info.color_format);
|
|
dirty |= system_constants_.edram_rt_format_flags[i] != format_flags;
|
|
system_constants_.edram_rt_format_flags[i] = format_flags;
|
|
uint32_t blend_factors_ops =
|
|
regs[reg::RB_BLENDCONTROL::rt_register_indices[i]] & 0x1FFF1FFF;
|
|
dirty |= system_constants_.edram_rt_blend_factors_ops[i] !=
|
|
blend_factors_ops;
|
|
system_constants_.edram_rt_blend_factors_ops[i] = blend_factors_ops;
|
|
// Can't do float comparisons here because NaNs would result in always
|
|
// setting the dirty flag.
|
|
dirty |= std::memcmp(system_constants_.edram_rt_clamp[i], rt_clamp[i],
|
|
4 * sizeof(float)) != 0;
|
|
std::memcpy(system_constants_.edram_rt_clamp[i], rt_clamp[i],
|
|
4 * sizeof(float));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (edram_fragment_shader_interlock) {
|
|
uint32_t depth_base_dwords_scaled =
|
|
rb_depth_info.depth_base * edram_tile_dwords_scaled;
|
|
dirty |= system_constants_.edram_depth_base_dwords_scaled !=
|
|
depth_base_dwords_scaled;
|
|
system_constants_.edram_depth_base_dwords_scaled = depth_base_dwords_scaled;
|
|
|
|
// For non-polygons, front polygon offset is used, and it's enabled if
|
|
// POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back
|
|
// are used.
|
|
float poly_offset_front_scale = 0.0f, poly_offset_front_offset = 0.0f;
|
|
float poly_offset_back_scale = 0.0f, poly_offset_back_offset = 0.0f;
|
|
if (primitive_polygonal) {
|
|
if (pa_su_sc_mode_cntl.poly_offset_front_enable) {
|
|
poly_offset_front_scale =
|
|
regs.Get<float>(XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE);
|
|
poly_offset_front_offset =
|
|
regs.Get<float>(XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_OFFSET);
|
|
}
|
|
if (pa_su_sc_mode_cntl.poly_offset_back_enable) {
|
|
poly_offset_back_scale =
|
|
regs.Get<float>(XE_GPU_REG_PA_SU_POLY_OFFSET_BACK_SCALE);
|
|
poly_offset_back_offset =
|
|
regs.Get<float>(XE_GPU_REG_PA_SU_POLY_OFFSET_BACK_OFFSET);
|
|
}
|
|
} else {
|
|
if (pa_su_sc_mode_cntl.poly_offset_para_enable) {
|
|
poly_offset_front_scale =
|
|
regs.Get<float>(XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE);
|
|
poly_offset_front_offset =
|
|
regs.Get<float>(XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_OFFSET);
|
|
poly_offset_back_scale = poly_offset_front_scale;
|
|
poly_offset_back_offset = poly_offset_front_offset;
|
|
}
|
|
}
|
|
// With non-square resolution scaling, make sure the worst-case impact is
|
|
// reverted (slope only along the scaled axis), thus max. More bias is
|
|
// better than less bias, because less bias means Z fighting with the
|
|
// background is more likely.
|
|
float poly_offset_scale_factor =
|
|
xenos::kPolygonOffsetScaleSubpixelUnit *
|
|
std::max(draw_resolution_scale_x, draw_resolution_scale_y);
|
|
poly_offset_front_scale *= poly_offset_scale_factor;
|
|
poly_offset_back_scale *= poly_offset_scale_factor;
|
|
dirty |= system_constants_.edram_poly_offset_front_scale !=
|
|
poly_offset_front_scale;
|
|
system_constants_.edram_poly_offset_front_scale = poly_offset_front_scale;
|
|
dirty |= system_constants_.edram_poly_offset_front_offset !=
|
|
poly_offset_front_offset;
|
|
system_constants_.edram_poly_offset_front_offset = poly_offset_front_offset;
|
|
dirty |= system_constants_.edram_poly_offset_back_scale !=
|
|
poly_offset_back_scale;
|
|
system_constants_.edram_poly_offset_back_scale = poly_offset_back_scale;
|
|
dirty |= system_constants_.edram_poly_offset_back_offset !=
|
|
poly_offset_back_offset;
|
|
system_constants_.edram_poly_offset_back_offset = poly_offset_back_offset;
|
|
|
|
if (depth_stencil_enabled && normalized_depth_control.stencil_enable) {
|
|
uint32_t stencil_front_reference_masks =
|
|
rb_stencilrefmask.value & 0xFFFFFF;
|
|
dirty |= system_constants_.edram_stencil_front_reference_masks !=
|
|
stencil_front_reference_masks;
|
|
system_constants_.edram_stencil_front_reference_masks =
|
|
stencil_front_reference_masks;
|
|
uint32_t stencil_func_ops =
|
|
(normalized_depth_control.value >> 8) & ((1 << 12) - 1);
|
|
dirty |=
|
|
system_constants_.edram_stencil_front_func_ops != stencil_func_ops;
|
|
system_constants_.edram_stencil_front_func_ops = stencil_func_ops;
|
|
|
|
if (primitive_polygonal && normalized_depth_control.backface_enable) {
|
|
uint32_t stencil_back_reference_masks =
|
|
rb_stencilrefmask_bf.value & 0xFFFFFF;
|
|
dirty |= system_constants_.edram_stencil_back_reference_masks !=
|
|
stencil_back_reference_masks;
|
|
system_constants_.edram_stencil_back_reference_masks =
|
|
stencil_back_reference_masks;
|
|
uint32_t stencil_func_ops_bf =
|
|
(normalized_depth_control.value >> 20) & ((1 << 12) - 1);
|
|
dirty |= system_constants_.edram_stencil_back_func_ops !=
|
|
stencil_func_ops_bf;
|
|
system_constants_.edram_stencil_back_func_ops = stencil_func_ops_bf;
|
|
} else {
|
|
dirty |= std::memcmp(system_constants_.edram_stencil_back,
|
|
system_constants_.edram_stencil_front,
|
|
2 * sizeof(uint32_t)) != 0;
|
|
std::memcpy(system_constants_.edram_stencil_back,
|
|
system_constants_.edram_stencil_front,
|
|
2 * sizeof(uint32_t));
|
|
}
|
|
}
|
|
|
|
dirty |= system_constants_.edram_blend_constant[0] !=
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_RED);
|
|
system_constants_.edram_blend_constant[0] =
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_RED);
|
|
dirty |= system_constants_.edram_blend_constant[1] !=
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_GREEN);
|
|
system_constants_.edram_blend_constant[1] =
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_GREEN);
|
|
dirty |= system_constants_.edram_blend_constant[2] !=
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_BLUE);
|
|
system_constants_.edram_blend_constant[2] =
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_BLUE);
|
|
dirty |= system_constants_.edram_blend_constant[3] !=
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_ALPHA);
|
|
system_constants_.edram_blend_constant[3] =
|
|
regs.Get<float>(XE_GPU_REG_RB_BLEND_ALPHA);
|
|
}
|
|
|
|
if (dirty) {
|
|
current_constant_buffers_up_to_date_ &=
|
|
~(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferSystem);
|
|
}
|
|
}
|
|
|
|
bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader,
|
|
const VulkanShader* pixel_shader) {
|
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
|
|
const RegisterFile& regs = *register_file_;
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
// Invalidate constant buffers and descriptors for changed data.
|
|
|
|
// Float constants.
|
|
// These are the constant base addresses/ranges for shaders.
|
|
// We have these hardcoded right now cause nothing seems to differ on the Xbox
|
|
// 360 (however, OpenGL ES on Adreno 200 on Android has different ranges).
|
|
assert_true(regs[XE_GPU_REG_SQ_VS_CONST] == 0x000FF000 ||
|
|
regs[XE_GPU_REG_SQ_VS_CONST] == 0x00000000);
|
|
assert_true(regs[XE_GPU_REG_SQ_PS_CONST] == 0x000FF100 ||
|
|
regs[XE_GPU_REG_SQ_PS_CONST] == 0x00000000);
|
|
// Check if the float constant layout is still the same and get the counts.
|
|
const Shader::ConstantRegisterMap& float_constant_map_vertex =
|
|
vertex_shader->constant_register_map();
|
|
uint32_t float_constant_count_vertex = float_constant_map_vertex.float_count;
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
if (current_float_constant_map_vertex_[i] !=
|
|
float_constant_map_vertex.float_bitmap[i]) {
|
|
current_float_constant_map_vertex_[i] =
|
|
float_constant_map_vertex.float_bitmap[i];
|
|
// If no float constants at all, any buffer can be reused for them, so not
|
|
// invalidating.
|
|
if (float_constant_count_vertex) {
|
|
current_constant_buffers_up_to_date_ &=
|
|
~(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatVertex);
|
|
}
|
|
}
|
|
}
|
|
uint32_t float_constant_count_pixel = 0;
|
|
if (pixel_shader != nullptr) {
|
|
const Shader::ConstantRegisterMap& float_constant_map_pixel =
|
|
pixel_shader->constant_register_map();
|
|
float_constant_count_pixel = float_constant_map_pixel.float_count;
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
if (current_float_constant_map_pixel_[i] !=
|
|
float_constant_map_pixel.float_bitmap[i]) {
|
|
current_float_constant_map_pixel_[i] =
|
|
float_constant_map_pixel.float_bitmap[i];
|
|
if (float_constant_count_pixel) {
|
|
current_constant_buffers_up_to_date_ &= ~(
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatPixel);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
std::memset(current_float_constant_map_pixel_, 0,
|
|
sizeof(current_float_constant_map_pixel_));
|
|
}
|
|
|
|
// Write the new constant buffers.
|
|
constexpr uint32_t kAllConstantBuffersMask =
|
|
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferCount) - 1;
|
|
assert_zero(current_constant_buffers_up_to_date_ & ~kAllConstantBuffersMask);
|
|
if ((current_constant_buffers_up_to_date_ & kAllConstantBuffersMask) !=
|
|
kAllConstantBuffersMask) {
|
|
current_graphics_descriptor_set_values_up_to_date_ &=
|
|
~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetConstants);
|
|
size_t uniform_buffer_alignment =
|
|
size_t(vulkan_device->properties().minUniformBufferOffsetAlignment);
|
|
// System constants.
|
|
if (!(current_constant_buffers_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferSystem))) {
|
|
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
|
|
[SpirvShaderTranslator::kConstantBufferSystem];
|
|
uint8_t* mapping = uniform_buffer_pool_->Request(
|
|
frame_current_, sizeof(SpirvShaderTranslator::SystemConstants),
|
|
uniform_buffer_alignment, buffer_info.buffer, buffer_info.offset);
|
|
if (!mapping) {
|
|
return false;
|
|
}
|
|
buffer_info.range = sizeof(SpirvShaderTranslator::SystemConstants);
|
|
std::memcpy(mapping, &system_constants_,
|
|
sizeof(SpirvShaderTranslator::SystemConstants));
|
|
current_constant_buffers_up_to_date_ |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferSystem;
|
|
}
|
|
// Clip plane constants.
|
|
// Always initialize the buffer info, even if clip planes are disabled,
|
|
// because the descriptor set write always includes all constant buffers.
|
|
if (!(current_constant_buffers_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferClipPlanes))) {
|
|
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
|
|
[SpirvShaderTranslator::kConstantBufferClipPlanes];
|
|
uint8_t* mapping = uniform_buffer_pool_->Request(
|
|
frame_current_, sizeof(SpirvShaderTranslator::ClipPlaneConstants),
|
|
uniform_buffer_alignment, buffer_info.buffer, buffer_info.offset);
|
|
if (!mapping) {
|
|
return false;
|
|
}
|
|
buffer_info.range = sizeof(SpirvShaderTranslator::ClipPlaneConstants);
|
|
auto pa_cl_clip_cntl = regs.Get<reg::PA_CL_CLIP_CNTL>();
|
|
bool clip_planes_enabled =
|
|
!pa_cl_clip_cntl.clip_disable && pa_cl_clip_cntl.ucp_ena;
|
|
if (clip_planes_enabled) {
|
|
std::memcpy(mapping, &clip_plane_constants_,
|
|
sizeof(SpirvShaderTranslator::ClipPlaneConstants));
|
|
} else {
|
|
// Zero out the buffer when clip planes are disabled
|
|
std::memset(mapping, 0,
|
|
sizeof(SpirvShaderTranslator::ClipPlaneConstants));
|
|
}
|
|
current_constant_buffers_up_to_date_ |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferClipPlanes;
|
|
}
|
|
// Tessellation constants.
|
|
// Always initialize the buffer info, even if tessellation is not active,
|
|
// because the descriptor set write always includes all constant buffers.
|
|
if (!(current_constant_buffers_up_to_date_ &
|
|
(UINT32_C(1)
|
|
<< SpirvShaderTranslator::kConstantBufferTessellation))) {
|
|
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
|
|
[SpirvShaderTranslator::kConstantBufferTessellation];
|
|
uint8_t* mapping = uniform_buffer_pool_->Request(
|
|
frame_current_, sizeof(SpirvShaderTranslator::TessellationConstants),
|
|
uniform_buffer_alignment, buffer_info.buffer, buffer_info.offset);
|
|
if (!mapping) {
|
|
return false;
|
|
}
|
|
buffer_info.range = sizeof(SpirvShaderTranslator::TessellationConstants);
|
|
// Populate tessellation constants from registers.
|
|
SpirvShaderTranslator::TessellationConstants tessellation_constants;
|
|
// Tessellation factor range, plus 1.0 according to Xbox 360 docs.
|
|
// For fractional_even partitioning (continuous mode), minimum must be
|
|
// >= 2.0.
|
|
float tess_factor_min =
|
|
regs.Get<float>(XE_GPU_REG_VGT_HOS_MIN_TESS_LEVEL) + 1.0f;
|
|
float tess_factor_max =
|
|
regs.Get<float>(XE_GPU_REG_VGT_HOS_MAX_TESS_LEVEL) + 1.0f;
|
|
tessellation_constants.tessellation_factor_range[0] = tess_factor_min;
|
|
tessellation_constants.tessellation_factor_range[1] = tess_factor_max;
|
|
tessellation_constants.padding0[0] = 0.0f;
|
|
tessellation_constants.padding0[1] = 0.0f;
|
|
// Vertex index processing parameters for tessellation shaders.
|
|
auto vgt_dma_size = regs.Get<reg::VGT_DMA_SIZE>();
|
|
tessellation_constants.vertex_index_endian =
|
|
static_cast<uint32_t>(vgt_dma_size.swap_mode);
|
|
tessellation_constants.vertex_index_offset =
|
|
regs[XE_GPU_REG_VGT_INDX_OFFSET];
|
|
tessellation_constants.vertex_index_min_max[0] =
|
|
regs[XE_GPU_REG_VGT_MIN_VTX_INDX];
|
|
tessellation_constants.vertex_index_min_max[1] =
|
|
regs[XE_GPU_REG_VGT_MAX_VTX_INDX];
|
|
std::memcpy(mapping, &tessellation_constants,
|
|
sizeof(SpirvShaderTranslator::TessellationConstants));
|
|
current_constant_buffers_up_to_date_ |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferTessellation;
|
|
}
|
|
// Vertex shader float constants.
|
|
if (!(current_constant_buffers_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatVertex))) {
|
|
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
|
|
[SpirvShaderTranslator::kConstantBufferFloatVertex];
|
|
// Even if the shader doesn't need any float constants, a valid binding
|
|
// must still be provided (the pipeline layout always has float constants,
|
|
// for both the vertex shader and the pixel shader), so if the first draw
|
|
// in the frame doesn't have float constants at all, still allocate a
|
|
// dummy buffer.
|
|
size_t float_constants_size =
|
|
sizeof(float) * 4 *
|
|
std::max(float_constant_count_vertex, UINT32_C(1));
|
|
uint8_t* mapping = uniform_buffer_pool_->Request(
|
|
frame_current_, float_constants_size, uniform_buffer_alignment,
|
|
buffer_info.buffer, buffer_info.offset);
|
|
if (!mapping) {
|
|
return false;
|
|
}
|
|
buffer_info.range = VkDeviceSize(float_constants_size);
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
uint64_t float_constant_map_entry =
|
|
current_float_constant_map_vertex_[i];
|
|
uint32_t float_constant_index;
|
|
while (xe::bit_scan_forward(float_constant_map_entry,
|
|
&float_constant_index)) {
|
|
float_constant_map_entry &= ~(1ull << float_constant_index);
|
|
std::memcpy(mapping,
|
|
®s[XE_GPU_REG_SHADER_CONSTANT_000_X + (i << 8) +
|
|
(float_constant_index << 2)],
|
|
sizeof(float) * 4);
|
|
mapping += sizeof(float) * 4;
|
|
}
|
|
}
|
|
current_constant_buffers_up_to_date_ |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatVertex;
|
|
}
|
|
// Pixel shader float constants.
|
|
if (!(current_constant_buffers_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatPixel))) {
|
|
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
|
|
[SpirvShaderTranslator::kConstantBufferFloatPixel];
|
|
size_t float_constants_size =
|
|
sizeof(float) * 4 * std::max(float_constant_count_pixel, UINT32_C(1));
|
|
uint8_t* mapping = uniform_buffer_pool_->Request(
|
|
frame_current_, float_constants_size, uniform_buffer_alignment,
|
|
buffer_info.buffer, buffer_info.offset);
|
|
if (!mapping) {
|
|
return false;
|
|
}
|
|
buffer_info.range = VkDeviceSize(float_constants_size);
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
uint64_t float_constant_map_entry =
|
|
current_float_constant_map_pixel_[i];
|
|
uint32_t float_constant_index;
|
|
while (xe::bit_scan_forward(float_constant_map_entry,
|
|
&float_constant_index)) {
|
|
float_constant_map_entry &= ~(1ull << float_constant_index);
|
|
std::memcpy(mapping,
|
|
®s[XE_GPU_REG_SHADER_CONSTANT_256_X + (i << 8) +
|
|
(float_constant_index << 2)],
|
|
sizeof(float) * 4);
|
|
mapping += sizeof(float) * 4;
|
|
}
|
|
}
|
|
current_constant_buffers_up_to_date_ |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatPixel;
|
|
}
|
|
// Bool and loop constants.
|
|
if (!(current_constant_buffers_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferBoolLoop))) {
|
|
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
|
|
[SpirvShaderTranslator::kConstantBufferBoolLoop];
|
|
constexpr size_t kBoolLoopConstantsSize = sizeof(uint32_t) * (8 + 32);
|
|
uint8_t* mapping = uniform_buffer_pool_->Request(
|
|
frame_current_, kBoolLoopConstantsSize, uniform_buffer_alignment,
|
|
buffer_info.buffer, buffer_info.offset);
|
|
if (!mapping) {
|
|
return false;
|
|
}
|
|
buffer_info.range = VkDeviceSize(kBoolLoopConstantsSize);
|
|
std::memcpy(mapping, ®s[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031],
|
|
kBoolLoopConstantsSize);
|
|
current_constant_buffers_up_to_date_ |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferBoolLoop;
|
|
}
|
|
// Fetch constants.
|
|
if (!(current_constant_buffers_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFetch))) {
|
|
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
|
|
[SpirvShaderTranslator::kConstantBufferFetch];
|
|
constexpr size_t kFetchConstantsSize = sizeof(uint32_t) * 6 * 32;
|
|
uint8_t* mapping = uniform_buffer_pool_->Request(
|
|
frame_current_, kFetchConstantsSize, uniform_buffer_alignment,
|
|
buffer_info.buffer, buffer_info.offset);
|
|
if (!mapping) {
|
|
return false;
|
|
}
|
|
buffer_info.range = VkDeviceSize(kFetchConstantsSize);
|
|
std::memcpy(mapping, ®s[XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0],
|
|
kFetchConstantsSize);
|
|
current_constant_buffers_up_to_date_ |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFetch;
|
|
}
|
|
}
|
|
|
|
// Textures and samplers.
|
|
const std::vector<VulkanShader::SamplerBinding>& samplers_vertex =
|
|
vertex_shader->GetSamplerBindingsAfterTranslation();
|
|
const std::vector<VulkanShader::TextureBinding>& textures_vertex =
|
|
vertex_shader->GetTextureBindingsAfterTranslation();
|
|
uint32_t sampler_count_vertex = uint32_t(samplers_vertex.size());
|
|
uint32_t texture_count_vertex = uint32_t(textures_vertex.size());
|
|
const std::vector<VulkanShader::SamplerBinding>* samplers_pixel;
|
|
const std::vector<VulkanShader::TextureBinding>* textures_pixel;
|
|
uint32_t sampler_count_pixel, texture_count_pixel;
|
|
if (pixel_shader) {
|
|
samplers_pixel = &pixel_shader->GetSamplerBindingsAfterTranslation();
|
|
textures_pixel = &pixel_shader->GetTextureBindingsAfterTranslation();
|
|
sampler_count_pixel = uint32_t(samplers_pixel->size());
|
|
texture_count_pixel = uint32_t(textures_pixel->size());
|
|
} else {
|
|
samplers_pixel = nullptr;
|
|
textures_pixel = nullptr;
|
|
sampler_count_pixel = 0;
|
|
texture_count_pixel = 0;
|
|
}
|
|
// TODO(Triang3l): Reuse texture and sampler bindings if not changed.
|
|
current_graphics_descriptor_set_values_up_to_date_ &=
|
|
~((UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex) |
|
|
(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel));
|
|
|
|
// Make sure new descriptor sets are bound to the command buffer.
|
|
|
|
current_graphics_descriptor_sets_bound_up_to_date_ &=
|
|
current_graphics_descriptor_set_values_up_to_date_;
|
|
|
|
// Fill the texture and sampler write image infos.
|
|
|
|
bool write_vertex_textures =
|
|
(texture_count_vertex || sampler_count_vertex) &&
|
|
!(current_graphics_descriptor_set_values_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex));
|
|
bool write_pixel_textures =
|
|
(texture_count_pixel || sampler_count_pixel) &&
|
|
!(current_graphics_descriptor_set_values_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel));
|
|
descriptor_write_image_info_.clear();
|
|
descriptor_write_image_info_.reserve(
|
|
(write_vertex_textures ? texture_count_vertex + sampler_count_vertex
|
|
: 0) +
|
|
(write_pixel_textures ? texture_count_pixel + sampler_count_pixel : 0));
|
|
size_t vertex_texture_image_info_offset = descriptor_write_image_info_.size();
|
|
if (write_vertex_textures && texture_count_vertex) {
|
|
for (const VulkanShader::TextureBinding& texture_binding :
|
|
textures_vertex) {
|
|
VkDescriptorImageInfo& descriptor_image_info =
|
|
descriptor_write_image_info_.emplace_back();
|
|
descriptor_image_info.imageView =
|
|
texture_cache_->GetActiveBindingOrNullImageView(
|
|
texture_binding.fetch_constant, texture_binding.dimension,
|
|
bool(texture_binding.is_signed));
|
|
descriptor_image_info.imageLayout =
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
}
|
|
}
|
|
size_t vertex_sampler_image_info_offset = descriptor_write_image_info_.size();
|
|
if (write_vertex_textures && sampler_count_vertex) {
|
|
for (const std::pair<VulkanTextureCache::SamplerParameters, VkSampler>&
|
|
sampler_pair : current_samplers_vertex_) {
|
|
VkDescriptorImageInfo& descriptor_image_info =
|
|
descriptor_write_image_info_.emplace_back();
|
|
descriptor_image_info.sampler = sampler_pair.second;
|
|
}
|
|
}
|
|
size_t pixel_texture_image_info_offset = descriptor_write_image_info_.size();
|
|
if (write_pixel_textures && texture_count_pixel) {
|
|
for (const VulkanShader::TextureBinding& texture_binding :
|
|
*textures_pixel) {
|
|
VkDescriptorImageInfo& descriptor_image_info =
|
|
descriptor_write_image_info_.emplace_back();
|
|
descriptor_image_info.imageView =
|
|
texture_cache_->GetActiveBindingOrNullImageView(
|
|
texture_binding.fetch_constant, texture_binding.dimension,
|
|
bool(texture_binding.is_signed));
|
|
descriptor_image_info.imageLayout =
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
}
|
|
}
|
|
size_t pixel_sampler_image_info_offset = descriptor_write_image_info_.size();
|
|
if (write_pixel_textures && sampler_count_pixel) {
|
|
for (const std::pair<VulkanTextureCache::SamplerParameters, VkSampler>&
|
|
sampler_pair : current_samplers_pixel_) {
|
|
VkDescriptorImageInfo& descriptor_image_info =
|
|
descriptor_write_image_info_.emplace_back();
|
|
descriptor_image_info.sampler = sampler_pair.second;
|
|
}
|
|
}
|
|
|
|
// Write the new descriptor sets.
|
|
|
|
// Consecutive bindings updated via a single VkWriteDescriptorSet must have
|
|
// identical stage flags, but for the constants they vary. Plus vertex and
|
|
// pixel texture images and samplers.
|
|
std::array<VkWriteDescriptorSet,
|
|
SpirvShaderTranslator::kConstantBufferCount + 2 * 2>
|
|
write_descriptor_sets;
|
|
uint32_t write_descriptor_set_count = 0;
|
|
uint32_t write_descriptor_set_bits = 0;
|
|
assert_not_zero(
|
|
current_graphics_descriptor_set_values_up_to_date_ &
|
|
(UINT32_C(1)
|
|
<< SpirvShaderTranslator::kDescriptorSetSharedMemoryAndEdram));
|
|
// Constant buffers.
|
|
if (!(current_graphics_descriptor_set_values_up_to_date_ &
|
|
(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetConstants))) {
|
|
VkDescriptorSet constants_descriptor_set;
|
|
if (!constants_transient_descriptors_free_.empty()) {
|
|
constants_descriptor_set = constants_transient_descriptors_free_.back();
|
|
constants_transient_descriptors_free_.pop_back();
|
|
} else {
|
|
VkDescriptorPoolSize constants_descriptor_count;
|
|
constants_descriptor_count.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
constants_descriptor_count.descriptorCount =
|
|
SpirvShaderTranslator::kConstantBufferCount;
|
|
constants_descriptor_set =
|
|
transient_descriptor_allocator_uniform_buffer_.Allocate(
|
|
descriptor_set_layout_constants_, &constants_descriptor_count, 1);
|
|
if (constants_descriptor_set == VK_NULL_HANDLE) {
|
|
return false;
|
|
}
|
|
}
|
|
constants_transient_descriptors_used_.emplace_back(
|
|
frame_current_, constants_descriptor_set);
|
|
// Consecutive bindings updated via a single VkWriteDescriptorSet must have
|
|
// identical stage flags, but for the constants they vary.
|
|
for (uint32_t i = 0; i < SpirvShaderTranslator::kConstantBufferCount; ++i) {
|
|
VkWriteDescriptorSet& write_constants =
|
|
write_descriptor_sets[write_descriptor_set_count++];
|
|
write_constants.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write_constants.pNext = nullptr;
|
|
write_constants.dstSet = constants_descriptor_set;
|
|
write_constants.dstBinding = i;
|
|
write_constants.dstArrayElement = 0;
|
|
write_constants.descriptorCount = 1;
|
|
write_constants.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
write_constants.pImageInfo = nullptr;
|
|
write_constants.pBufferInfo = ¤t_constant_buffer_infos_[i];
|
|
write_constants.pTexelBufferView = nullptr;
|
|
}
|
|
write_descriptor_set_bits |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetConstants;
|
|
current_graphics_descriptor_sets_
|
|
[SpirvShaderTranslator::kDescriptorSetConstants] =
|
|
constants_descriptor_set;
|
|
}
|
|
// Vertex shader textures and samplers.
|
|
if (write_vertex_textures) {
|
|
VkWriteDescriptorSet* write_textures =
|
|
write_descriptor_sets.data() + write_descriptor_set_count;
|
|
uint32_t texture_descriptor_set_write_count = WriteTransientTextureBindings(
|
|
true, texture_count_vertex, sampler_count_vertex,
|
|
current_guest_graphics_pipeline_layout_
|
|
->descriptor_set_layout_textures_vertex_ref(),
|
|
descriptor_write_image_info_.data() + vertex_texture_image_info_offset,
|
|
descriptor_write_image_info_.data() + vertex_sampler_image_info_offset,
|
|
write_textures);
|
|
if (!texture_descriptor_set_write_count) {
|
|
return false;
|
|
}
|
|
write_descriptor_set_count += texture_descriptor_set_write_count;
|
|
write_descriptor_set_bits |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex;
|
|
current_graphics_descriptor_sets_
|
|
[SpirvShaderTranslator::kDescriptorSetTexturesVertex] =
|
|
write_textures[0].dstSet;
|
|
}
|
|
// Pixel shader textures and samplers.
|
|
if (write_pixel_textures) {
|
|
VkWriteDescriptorSet* write_textures =
|
|
write_descriptor_sets.data() + write_descriptor_set_count;
|
|
uint32_t texture_descriptor_set_write_count = WriteTransientTextureBindings(
|
|
false, texture_count_pixel, sampler_count_pixel,
|
|
current_guest_graphics_pipeline_layout_
|
|
->descriptor_set_layout_textures_pixel_ref(),
|
|
descriptor_write_image_info_.data() + pixel_texture_image_info_offset,
|
|
descriptor_write_image_info_.data() + pixel_sampler_image_info_offset,
|
|
write_textures);
|
|
if (!texture_descriptor_set_write_count) {
|
|
return false;
|
|
}
|
|
write_descriptor_set_count += texture_descriptor_set_write_count;
|
|
write_descriptor_set_bits |=
|
|
UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel;
|
|
current_graphics_descriptor_sets_
|
|
[SpirvShaderTranslator::kDescriptorSetTexturesPixel] =
|
|
write_textures[0].dstSet;
|
|
}
|
|
// Write.
|
|
if (write_descriptor_set_count) {
|
|
dfn.vkUpdateDescriptorSets(device, write_descriptor_set_count,
|
|
write_descriptor_sets.data(), 0, nullptr);
|
|
}
|
|
// Only make valid if all descriptor sets have been allocated and written
|
|
// successfully.
|
|
current_graphics_descriptor_set_values_up_to_date_ |=
|
|
write_descriptor_set_bits;
|
|
|
|
// Bind the new descriptor sets.
|
|
uint32_t descriptor_sets_needed =
|
|
(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetCount) - 1;
|
|
if (!texture_count_vertex && !sampler_count_vertex) {
|
|
descriptor_sets_needed &=
|
|
~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex);
|
|
}
|
|
if (!texture_count_pixel && !sampler_count_pixel) {
|
|
descriptor_sets_needed &=
|
|
~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel);
|
|
}
|
|
uint32_t descriptor_sets_remaining =
|
|
descriptor_sets_needed &
|
|
~current_graphics_descriptor_sets_bound_up_to_date_;
|
|
uint32_t descriptor_set_index;
|
|
while (
|
|
xe::bit_scan_forward(descriptor_sets_remaining, &descriptor_set_index)) {
|
|
uint32_t descriptor_set_mask_tzcnt =
|
|
xe::tzcnt(~(descriptor_sets_remaining |
|
|
((UINT32_C(1) << descriptor_set_index) - 1)));
|
|
// TODO(Triang3l): Bind to compute for memexport emulation without vertex
|
|
// shader memory stores.
|
|
deferred_command_buffer_.CmdVkBindDescriptorSets(
|
|
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
current_guest_graphics_pipeline_layout_->GetPipelineLayout(),
|
|
descriptor_set_index, descriptor_set_mask_tzcnt - descriptor_set_index,
|
|
current_graphics_descriptor_sets_ + descriptor_set_index, 0, nullptr);
|
|
if (descriptor_set_mask_tzcnt >= 32) {
|
|
break;
|
|
}
|
|
descriptor_sets_remaining &=
|
|
~((UINT32_C(1) << descriptor_set_mask_tzcnt) - 1);
|
|
}
|
|
current_graphics_descriptor_sets_bound_up_to_date_ |= descriptor_sets_needed;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t VulkanCommandProcessor::WriteTransientTextureBindings(
|
|
bool is_vertex, uint32_t texture_count, uint32_t sampler_count,
|
|
VkDescriptorSetLayout descriptor_set_layout,
|
|
const VkDescriptorImageInfo* texture_image_info,
|
|
const VkDescriptorImageInfo* sampler_image_info,
|
|
VkWriteDescriptorSet* descriptor_set_writes_out) {
|
|
assert_true(frame_open_);
|
|
if (!texture_count && !sampler_count) {
|
|
return 0;
|
|
}
|
|
TextureDescriptorSetLayoutKey texture_descriptor_set_layout_key;
|
|
texture_descriptor_set_layout_key.texture_count = texture_count;
|
|
texture_descriptor_set_layout_key.sampler_count = sampler_count;
|
|
texture_descriptor_set_layout_key.is_vertex = uint32_t(is_vertex);
|
|
VkDescriptorSet texture_descriptor_set;
|
|
auto textures_free_it = texture_transient_descriptor_sets_free_.find(
|
|
texture_descriptor_set_layout_key);
|
|
if (textures_free_it != texture_transient_descriptor_sets_free_.end() &&
|
|
!textures_free_it->second.empty()) {
|
|
texture_descriptor_set = textures_free_it->second.back();
|
|
textures_free_it->second.pop_back();
|
|
} else {
|
|
std::array<VkDescriptorPoolSize, 2> texture_descriptor_counts;
|
|
uint32_t texture_descriptor_counts_count = 0;
|
|
if (texture_count) {
|
|
VkDescriptorPoolSize& texture_descriptor_count =
|
|
texture_descriptor_counts[texture_descriptor_counts_count++];
|
|
texture_descriptor_count.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
texture_descriptor_count.descriptorCount = texture_count;
|
|
}
|
|
if (sampler_count) {
|
|
VkDescriptorPoolSize& texture_descriptor_count =
|
|
texture_descriptor_counts[texture_descriptor_counts_count++];
|
|
texture_descriptor_count.type = VK_DESCRIPTOR_TYPE_SAMPLER;
|
|
texture_descriptor_count.descriptorCount = sampler_count;
|
|
}
|
|
assert_not_zero(texture_descriptor_counts_count);
|
|
texture_descriptor_set = transient_descriptor_allocator_textures_.Allocate(
|
|
descriptor_set_layout, texture_descriptor_counts.data(),
|
|
texture_descriptor_counts_count);
|
|
if (texture_descriptor_set == VK_NULL_HANDLE) {
|
|
return 0;
|
|
}
|
|
}
|
|
UsedTextureTransientDescriptorSet& used_texture_descriptor_set =
|
|
texture_transient_descriptor_sets_used_.emplace_back();
|
|
used_texture_descriptor_set.frame = frame_current_;
|
|
used_texture_descriptor_set.layout = texture_descriptor_set_layout_key;
|
|
used_texture_descriptor_set.set = texture_descriptor_set;
|
|
uint32_t descriptor_set_write_count = 0;
|
|
if (texture_count) {
|
|
VkWriteDescriptorSet& descriptor_set_write =
|
|
descriptor_set_writes_out[descriptor_set_write_count++];
|
|
descriptor_set_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
descriptor_set_write.pNext = nullptr;
|
|
descriptor_set_write.dstSet = texture_descriptor_set;
|
|
descriptor_set_write.dstBinding = 0;
|
|
descriptor_set_write.dstArrayElement = 0;
|
|
descriptor_set_write.descriptorCount = texture_count;
|
|
descriptor_set_write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
descriptor_set_write.pImageInfo = texture_image_info;
|
|
descriptor_set_write.pBufferInfo = nullptr;
|
|
descriptor_set_write.pTexelBufferView = nullptr;
|
|
}
|
|
if (sampler_count) {
|
|
VkWriteDescriptorSet& descriptor_set_write =
|
|
descriptor_set_writes_out[descriptor_set_write_count++];
|
|
descriptor_set_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
descriptor_set_write.pNext = nullptr;
|
|
descriptor_set_write.dstSet = texture_descriptor_set;
|
|
descriptor_set_write.dstBinding = texture_count;
|
|
descriptor_set_write.dstArrayElement = 0;
|
|
descriptor_set_write.descriptorCount = sampler_count;
|
|
descriptor_set_write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
|
|
descriptor_set_write.pImageInfo = sampler_image_info;
|
|
descriptor_set_write.pBufferInfo = nullptr;
|
|
descriptor_set_write.pTexelBufferView = nullptr;
|
|
}
|
|
assert_not_zero(descriptor_set_write_count);
|
|
return descriptor_set_write_count;
|
|
}
|
|
|
|
#define COMMAND_PROCESSOR VulkanCommandProcessor
|
|
#include "../pm4_command_processor_implement.h"
|
|
} // namespace vulkan
|
|
} // namespace gpu
|
|
} // namespace xe
|