4818 lines
214 KiB
C++
4818 lines
214 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/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/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/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::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;
|
|
}
|
|
}
|
|
title << " - HEAVILY INCOMPLETE, early development";
|
|
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;
|
|
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 using the same method as D3D12
|
|
uint32_t draw_resolution_scale_x, draw_resolution_scale_y;
|
|
TextureCache::GetConfigDrawResolutionScale(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 and EDRAM descriptor set layout.
|
|
bool edram_fragment_shader_interlock =
|
|
render_target_cache_->GetPath() ==
|
|
RenderTargetCache::Path::kPixelShaderInterlock;
|
|
VkDescriptorSetLayoutBinding
|
|
shared_memory_and_edram_descriptor_set_layout_bindings[2];
|
|
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;
|
|
} 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;
|
|
}
|
|
|
|
// Shared memory and EDRAM 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 + 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[2];
|
|
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;
|
|
}
|
|
dfn.vkUpdateDescriptorSets(device,
|
|
1 + uint32_t(edram_fragment_shader_interlock),
|
|
write_descriptor_sets, 0, nullptr);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Just not to expose uninitialized memory.
|
|
std::memset(&system_constants_, 0, sizeof(system_constants_));
|
|
|
|
return true;
|
|
}
|
|
|
|
void VulkanCommandProcessor::ShutdownContext() {
|
|
AwaitAllQueueOperationsCompletion();
|
|
|
|
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_);
|
|
|
|
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);
|
|
|
|
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();
|
|
|
|
// 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);
|
|
}
|
|
|
|
void VulkanCommandProcessor::EndRenderPass() {
|
|
assert_true(submission_open_);
|
|
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): Tessellation, geometry-type-specific vertex shader,
|
|
// vertex shader as compute.
|
|
if (primitive_processing_result.host_vertex_shader_type !=
|
|
Shader::HostVertexShaderType::kVertex &&
|
|
primitive_processing_result.host_vertex_shader_type !=
|
|
Shader::HostVertexShaderType::kPointListAsTriangleStrip) {
|
|
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.
|
|
VkPipeline pipeline;
|
|
const VulkanPipelineCache::PipelineLayoutProvider* pipeline_layout_provider;
|
|
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,
|
|
pipeline_layout_provider)) {
|
|
return false;
|
|
}
|
|
|
|
// 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_.
|
|
if (current_guest_graphics_pipeline_ != pipeline) {
|
|
deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
pipeline);
|
|
current_guest_graphics_pipeline_ = pipeline;
|
|
current_external_graphics_pipeline_ = VK_NULL_HANDLE;
|
|
}
|
|
auto pipeline_layout =
|
|
static_cast<const PipelineLayout*>(pipeline_layout_provider);
|
|
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));
|
|
}
|
|
} 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.
|
|
// TODO(Triang3l): Cache residency for ranges in a way similar to how texture
|
|
// validity is tracked.
|
|
//
|
|
// 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();
|
|
uint64_t vertex_buffers_resident[2] = {};
|
|
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;
|
|
if (vertex_buffers_resident[vfetch_index >> 6] &
|
|
(uint64_t(1) << (vfetch_index & 63))) {
|
|
continue;
|
|
}
|
|
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;
|
|
}
|
|
if (!shared_memory_->RequestRange(vfetch_constant.address << 2,
|
|
vfetch_constant.size << 2)) {
|
|
XELOGE(
|
|
"Failed to request vertex buffer at 0x{:08X} (size {}) in the "
|
|
"shared "
|
|
"memory",
|
|
vfetch_constant.address << 2, vfetch_constant.size << 2);
|
|
return false;
|
|
}
|
|
vertex_buffers_resident[vfetch_index >> 6] |= uint64_t(1)
|
|
<< (vfetch_index & 63);
|
|
}
|
|
}
|
|
|
|
// 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, false);
|
|
}
|
|
|
|
// 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::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::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);
|
|
|
|
// 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();
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 sRGB check.
|
|
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];
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Alpha test.
|
|
dirty |= system_constants_.alpha_test_reference != rb_alpha_ref;
|
|
system_constants_.alpha_test_reference = rb_alpha_ref;
|
|
|
|
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;
|
|
}
|
|
// 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
|