[Vulkan] Add tesselation support

Pretty much just following the D3D12 implementation
This commit is contained in:
Herman S.
2025-12-10 14:19:47 +09:00
parent 3e3aee1ef2
commit 83d776fa39
33 changed files with 3269 additions and 75 deletions

View File

@@ -225,6 +225,21 @@ bool VulkanCommandProcessor::SetupContext() {
descriptor_set_layout_bindings_constants
[SpirvShaderTranslator::kConstantBufferFetch]
.stageFlags = guest_shader_stages;
// Clip plane constants - used by vertex shader (and TES for tessellation).
descriptor_set_layout_bindings_constants
[SpirvShaderTranslator::kConstantBufferClipPlanes]
.stageFlags = guest_shader_vertex_stages_;
// Tessellation constants - used by tessellation control shader, the
// tessellation vertex shader (for index/factor processing), and the
// tessellation evaluation shader (domain shader, which is the translated
// Xenos vertex shader).
descriptor_set_layout_bindings_constants
[SpirvShaderTranslator::kConstantBufferTessellation]
.stageFlags = device_properties.tessellationShader
? (VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT |
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT |
VK_SHADER_STAGE_VERTEX_BIT)
: 0;
descriptor_set_layout_create_info.bindingCount =
uint32_t(xe::countof(descriptor_set_layout_bindings_constants));
descriptor_set_layout_create_info.pBindings =
@@ -2302,12 +2317,17 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
// Nothing to draw.
return true;
}
// TODO(Triang3l): Tessellation, geometry-type-specific vertex shader,
// vertex shader as compute.
// TODO(Triang3l): Geometry-type-specific vertex shader, vertex shader as
// compute.
// Skip unsupported host vertex shader types (but allow tessellation types
// through - they will be handled in pipeline creation or rejected there if
// not fully supported yet).
if (primitive_processing_result.host_vertex_shader_type !=
Shader::HostVertexShaderType::kVertex &&
primitive_processing_result.host_vertex_shader_type !=
Shader::HostVertexShaderType::kPointListAsTriangleStrip) {
Shader::HostVertexShaderType::kPointListAsTriangleStrip &&
!Shader::IsHostVertexShaderTypeDomain(
primitive_processing_result.host_vertex_shader_type)) {
return false;
}
@@ -4514,6 +4534,49 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader,
current_constant_buffers_up_to_date_ |=
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferClipPlanes;
}
// Tessellation constants.
// Always initialize the buffer info, even if tessellation is not active,
// because the descriptor set write always includes all constant buffers.
if (!(current_constant_buffers_up_to_date_ &
(UINT32_C(1)
<< SpirvShaderTranslator::kConstantBufferTessellation))) {
VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_
[SpirvShaderTranslator::kConstantBufferTessellation];
uint8_t* mapping = uniform_buffer_pool_->Request(
frame_current_, sizeof(SpirvShaderTranslator::TessellationConstants),
uniform_buffer_alignment, buffer_info.buffer, buffer_info.offset);
if (!mapping) {
return false;
}
buffer_info.range = sizeof(SpirvShaderTranslator::TessellationConstants);
// Populate tessellation constants from registers.
SpirvShaderTranslator::TessellationConstants tessellation_constants;
// Tessellation factor range, plus 1.0 according to Xbox 360 docs.
// For fractional_even partitioning (continuous mode), minimum must be
// >= 2.0.
float tess_factor_min =
regs.Get<float>(XE_GPU_REG_VGT_HOS_MIN_TESS_LEVEL) + 1.0f;
float tess_factor_max =
regs.Get<float>(XE_GPU_REG_VGT_HOS_MAX_TESS_LEVEL) + 1.0f;
tessellation_constants.tessellation_factor_range[0] = tess_factor_min;
tessellation_constants.tessellation_factor_range[1] = tess_factor_max;
tessellation_constants.padding0[0] = 0.0f;
tessellation_constants.padding0[1] = 0.0f;
// Vertex index processing parameters for tessellation shaders.
auto vgt_dma_size = regs.Get<reg::VGT_DMA_SIZE>();
tessellation_constants.vertex_index_endian =
static_cast<uint32_t>(vgt_dma_size.swap_mode);
tessellation_constants.vertex_index_offset =
regs[XE_GPU_REG_VGT_INDX_OFFSET];
tessellation_constants.vertex_index_min_max[0] =
regs[XE_GPU_REG_VGT_MIN_VTX_INDX];
tessellation_constants.vertex_index_min_max[1] =
regs[XE_GPU_REG_VGT_MAX_VTX_INDX];
std::memcpy(mapping, &tessellation_constants,
sizeof(SpirvShaderTranslator::TessellationConstants));
current_constant_buffers_up_to_date_ |=
UINT32_C(1) << SpirvShaderTranslator::kConstantBufferTessellation;
}
// Vertex shader float constants.
if (!(current_constant_buffers_up_to_date_ &
(UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatVertex))) {

View File

@@ -30,6 +30,21 @@
#include "xenia/gpu/xenos.h"
#include "xenia/ui/vulkan/vulkan_util.h"
// Tessellation shader bytecode.
namespace shaders {
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/adaptive_quad_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/adaptive_triangle_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/continuous_quad_1cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/continuous_quad_4cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/continuous_triangle_1cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/continuous_triangle_3cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/discrete_quad_1cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/discrete_quad_4cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/discrete_triangle_1cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/discrete_triangle_3cp_hs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/tessellation_adaptive_vs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/tessellation_indexed_vs.h"
} // namespace shaders
namespace xe {
namespace gpu {
namespace vulkan {
@@ -80,6 +95,68 @@ bool VulkanPipelineCache::Initialize() {
}
}
// Create tessellation shaders if tessellation is supported.
if (vulkan_device->properties().tessellationShader) {
// Vertex shaders for tessellation.
tessellation_indexed_vs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::tessellation_indexed_vs,
sizeof(shaders::tessellation_indexed_vs));
tessellation_adaptive_vs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::tessellation_adaptive_vs,
sizeof(shaders::tessellation_adaptive_vs));
// Discrete mode hull shaders.
discrete_triangle_1cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::discrete_triangle_1cp_hs,
sizeof(shaders::discrete_triangle_1cp_hs));
discrete_triangle_3cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::discrete_triangle_3cp_hs,
sizeof(shaders::discrete_triangle_3cp_hs));
discrete_quad_1cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::discrete_quad_1cp_hs,
sizeof(shaders::discrete_quad_1cp_hs));
discrete_quad_4cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::discrete_quad_4cp_hs,
sizeof(shaders::discrete_quad_4cp_hs));
// Continuous mode hull shaders.
continuous_triangle_1cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::continuous_triangle_1cp_hs,
sizeof(shaders::continuous_triangle_1cp_hs));
continuous_triangle_3cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::continuous_triangle_3cp_hs,
sizeof(shaders::continuous_triangle_3cp_hs));
continuous_quad_1cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::continuous_quad_1cp_hs,
sizeof(shaders::continuous_quad_1cp_hs));
continuous_quad_4cp_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::continuous_quad_4cp_hs,
sizeof(shaders::continuous_quad_4cp_hs));
// Adaptive mode hull shaders.
adaptive_triangle_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::adaptive_triangle_hs,
sizeof(shaders::adaptive_triangle_hs));
adaptive_quad_hs_ = ui::vulkan::util::CreateShaderModule(
vulkan_device, shaders::adaptive_quad_hs,
sizeof(shaders::adaptive_quad_hs));
// Verify all tessellation shaders were created successfully.
if (tessellation_indexed_vs_ == VK_NULL_HANDLE ||
tessellation_adaptive_vs_ == VK_NULL_HANDLE ||
discrete_triangle_1cp_hs_ == VK_NULL_HANDLE ||
discrete_triangle_3cp_hs_ == VK_NULL_HANDLE ||
discrete_quad_1cp_hs_ == VK_NULL_HANDLE ||
discrete_quad_4cp_hs_ == VK_NULL_HANDLE ||
continuous_triangle_1cp_hs_ == VK_NULL_HANDLE ||
continuous_triangle_3cp_hs_ == VK_NULL_HANDLE ||
continuous_quad_1cp_hs_ == VK_NULL_HANDLE ||
continuous_quad_4cp_hs_ == VK_NULL_HANDLE ||
adaptive_triangle_hs_ == VK_NULL_HANDLE ||
adaptive_quad_hs_ == VK_NULL_HANDLE) {
XELOGW(
"VulkanPipelineCache: Failed to create one or more tessellation "
"shaders - tessellation will not be available");
}
}
// Create Vulkan pipeline cache for faster pipeline creation.
VkPipelineCacheCreateInfo pipeline_cache_create_info = {};
pipeline_cache_create_info.sType =
@@ -118,6 +195,31 @@ void VulkanPipelineCache::Shutdown() {
// Destroy all internal shaders.
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
depth_only_fragment_shader_);
// Destroy tessellation shaders.
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
tessellation_indexed_vs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
tessellation_adaptive_vs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
discrete_triangle_1cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
discrete_triangle_3cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
discrete_quad_1cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
discrete_quad_4cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
continuous_triangle_1cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
continuous_triangle_3cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
continuous_quad_1cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
continuous_quad_4cp_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
adaptive_triangle_hs_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
adaptive_quad_hs_);
for (const auto& geometry_shader_pair : geometry_shaders_) {
if (geometry_shader_pair.second != VK_NULL_HANDLE) {
dfn.vkDestroyShaderModule(device, geometry_shader_pair.second, nullptr);
@@ -368,10 +470,34 @@ bool VulkanPipelineCache::ConfigurePipeline(
PipelineCreationArguments creation_arguments;
auto& pipeline =
*pipelines_.emplace(description, Pipeline(pipeline_layout)).first;
// Get tessellation shaders if needed.
VkShaderModule tessellation_vertex_shader = VK_NULL_HANDLE;
VkShaderModule tessellation_control_shader = VK_NULL_HANDLE;
if (description.tessellation_mode != PipelineTessellationMode::kNone) {
tessellation_vertex_shader =
GetTessellationVertexShader(description.tessellation_mode);
bool use_control_point_count =
(description.tessellation_mode == PipelineTessellationMode::kAdaptive);
tessellation_control_shader = GetTessellationControlShader(
description.tessellation_mode, description.tessellation_patch,
use_control_point_count);
if (tessellation_vertex_shader == VK_NULL_HANDLE ||
tessellation_control_shader == VK_NULL_HANDLE) {
XELOGE(
"VulkanPipelineCache: Failed to get tessellation shaders for mode {} "
"patch {}",
static_cast<uint32_t>(description.tessellation_mode),
static_cast<uint32_t>(description.tessellation_patch));
return false;
}
}
creation_arguments.pipeline = &pipeline;
creation_arguments.vertex_shader = vertex_shader;
creation_arguments.pixel_shader = pixel_shader;
creation_arguments.geometry_shader = geometry_shader;
creation_arguments.tessellation_vertex_shader = tessellation_vertex_shader;
creation_arguments.tessellation_control_shader = tessellation_control_shader;
creation_arguments.render_pass = render_pass;
if (!EnsurePipelineCreated(creation_arguments)) {
return false;
@@ -556,45 +682,90 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
// without them.
PipelineGeometryShader geometry_shader = PipelineGeometryShader::kNone;
PipelinePrimitiveTopology primitive_topology;
switch (primitive_processing_result.host_primitive_type) {
case xenos::PrimitiveType::kPointList:
geometry_shader = PipelineGeometryShader::kPointList;
primitive_topology = PipelinePrimitiveTopology::kPointList;
break;
case xenos::PrimitiveType::kLineList:
primitive_topology = PipelinePrimitiveTopology::kLineList;
break;
case xenos::PrimitiveType::kLineStrip:
primitive_topology = PipelinePrimitiveTopology::kLineStrip;
break;
case xenos::PrimitiveType::kTriangleList:
primitive_topology = PipelinePrimitiveTopology::kTriangleList;
break;
case xenos::PrimitiveType::kTriangleFan:
// The check should be performed at primitive processing time.
assert_true(device_properties.triangleFans);
primitive_topology = PipelinePrimitiveTopology::kTriangleFan;
break;
case xenos::PrimitiveType::kTriangleStrip:
primitive_topology = PipelinePrimitiveTopology::kTriangleStrip;
break;
case xenos::PrimitiveType::kRectangleList:
// Only use geometry shader if not using the fallback AsTriangleStrip
// vertex shader type (which is used when geometry shaders aren't
// supported).
if (primitive_processing_result.host_vertex_shader_type !=
Shader::HostVertexShaderType::kRectangleListAsTriangleStrip) {
geometry_shader = PipelineGeometryShader::kRectangleList;
}
primitive_topology = PipelinePrimitiveTopology::kTriangleList;
break;
case xenos::PrimitiveType::kQuadList:
geometry_shader = PipelineGeometryShader::kQuadList;
primitive_topology = PipelinePrimitiveTopology::kLineListWithAdjacency;
break;
default:
// TODO(Triang3l): All primitive types and tessellation.
return false;
// Handle tessellated and non-tessellated draws separately, like D3D12.
if (primitive_processing_result.IsTessellated()) {
// Tessellation is enabled - use patch list topology.
primitive_topology = PipelinePrimitiveTopology::kPatchList;
// Get tessellation mode from registers.
auto vgt_hos_cntl = regs.Get<reg::VGT_HOS_CNTL>();
switch (vgt_hos_cntl.tess_mode) {
case xenos::TessellationMode::kDiscrete:
description_out.tessellation_mode = PipelineTessellationMode::kDiscrete;
break;
case xenos::TessellationMode::kContinuous:
description_out.tessellation_mode =
PipelineTessellationMode::kContinuous;
break;
case xenos::TessellationMode::kAdaptive:
description_out.tessellation_mode = PipelineTessellationMode::kAdaptive;
break;
default:
// Unknown tessellation mode, fall back to discrete.
description_out.tessellation_mode = PipelineTessellationMode::kDiscrete;
break;
}
// Determine patch type based on primitive type.
switch (primitive_processing_result.host_primitive_type) {
case xenos::PrimitiveType::kTriangleList:
case xenos::PrimitiveType::kTrianglePatch:
description_out.tessellation_patch =
PipelineTessellationPatchType::kTriangle;
break;
case xenos::PrimitiveType::kQuadList:
case xenos::PrimitiveType::kQuadPatch:
description_out.tessellation_patch =
PipelineTessellationPatchType::kQuad;
break;
default:
XELOGE("VulkanPipelineCache: Unsupported tessellated primitive type {}",
uint32_t(primitive_processing_result.host_primitive_type));
return false;
}
} else {
// Non-tessellated draw.
switch (primitive_processing_result.host_primitive_type) {
case xenos::PrimitiveType::kPointList:
geometry_shader = PipelineGeometryShader::kPointList;
primitive_topology = PipelinePrimitiveTopology::kPointList;
break;
case xenos::PrimitiveType::kLineList:
primitive_topology = PipelinePrimitiveTopology::kLineList;
break;
case xenos::PrimitiveType::kLineStrip:
primitive_topology = PipelinePrimitiveTopology::kLineStrip;
break;
case xenos::PrimitiveType::kTriangleList:
primitive_topology = PipelinePrimitiveTopology::kTriangleList;
break;
case xenos::PrimitiveType::kTriangleFan:
// The check should be performed at primitive processing time.
assert_true(device_properties.triangleFans);
primitive_topology = PipelinePrimitiveTopology::kTriangleFan;
break;
case xenos::PrimitiveType::kTriangleStrip:
primitive_topology = PipelinePrimitiveTopology::kTriangleStrip;
break;
case xenos::PrimitiveType::kRectangleList:
// Only use geometry shader if not using the fallback AsTriangleStrip
// vertex shader type (which is used when geometry shaders aren't
// supported).
if (primitive_processing_result.host_vertex_shader_type !=
Shader::HostVertexShaderType::kRectangleListAsTriangleStrip) {
geometry_shader = PipelineGeometryShader::kRectangleList;
}
primitive_topology = PipelinePrimitiveTopology::kTriangleList;
break;
case xenos::PrimitiveType::kQuadList:
geometry_shader = PipelineGeometryShader::kQuadList;
primitive_topology = PipelinePrimitiveTopology::kLineListWithAdjacency;
break;
default:
// TODO(Triang3l): Remaining primitive types.
return false;
}
}
description_out.geometry_shader = geometry_shader;
description_out.primitive_topology = primitive_topology;
@@ -1804,6 +1975,55 @@ VkShaderModule VulkanPipelineCache::GetGeometryShader(GeometryShaderKey key) {
return shader_module;
}
VkShaderModule VulkanPipelineCache::GetTessellationControlShader(
PipelineTessellationMode mode, PipelineTessellationPatchType patch_type,
bool use_control_point_count) const {
if (mode == PipelineTessellationMode::kNone ||
patch_type == PipelineTessellationPatchType::kNone) {
return VK_NULL_HANDLE;
}
switch (mode) {
case PipelineTessellationMode::kDiscrete:
if (patch_type == PipelineTessellationPatchType::kTriangle) {
return use_control_point_count ? discrete_triangle_3cp_hs_
: discrete_triangle_1cp_hs_;
} else {
return use_control_point_count ? discrete_quad_4cp_hs_
: discrete_quad_1cp_hs_;
}
case PipelineTessellationMode::kContinuous:
if (patch_type == PipelineTessellationPatchType::kTriangle) {
return use_control_point_count ? continuous_triangle_3cp_hs_
: continuous_triangle_1cp_hs_;
} else {
return use_control_point_count ? continuous_quad_4cp_hs_
: continuous_quad_1cp_hs_;
}
case PipelineTessellationMode::kAdaptive:
// Adaptive mode always uses per-corner control points.
if (patch_type == PipelineTessellationPatchType::kTriangle) {
return adaptive_triangle_hs_;
} else {
return adaptive_quad_hs_;
}
default:
return VK_NULL_HANDLE;
}
}
VkShaderModule VulkanPipelineCache::GetTessellationVertexShader(
PipelineTessellationMode mode) const {
if (mode == PipelineTessellationMode::kNone) {
return VK_NULL_HANDLE;
}
// Adaptive mode reads edge factors from index buffer; other modes pass
// vertex indices.
return (mode == PipelineTessellationMode::kAdaptive)
? tessellation_adaptive_vs_
: tessellation_indexed_vs_;
}
bool VulkanPipelineCache::EnsurePipelineCreated(
const PipelineCreationArguments& creation_arguments) {
if (creation_arguments.pipeline->second.pipeline != VK_NULL_HANDLE) {
@@ -1839,26 +2059,77 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
render_target_cache_.GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock;
std::array<VkPipelineShaderStageCreateInfo, 3> shader_stages;
bool is_tessellated =
description.tessellation_mode != PipelineTessellationMode::kNone;
// Up to 5 shader stages: VS, TCS, TES, GS, FS.
std::array<VkPipelineShaderStageCreateInfo, 5> shader_stages;
uint32_t shader_stage_count = 0;
// Vertex or tessellation evaluation shader.
// Vertex shader or tessellation evaluation shader.
assert_true(creation_arguments.vertex_shader->is_translated());
if (!creation_arguments.vertex_shader->is_valid()) {
return false;
}
VkPipelineShaderStageCreateInfo& shader_stage_vertex =
shader_stages[shader_stage_count++];
shader_stage_vertex.sType =
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shader_stage_vertex.pNext = nullptr;
shader_stage_vertex.flags = 0;
shader_stage_vertex.stage = VK_SHADER_STAGE_VERTEX_BIT;
shader_stage_vertex.module =
creation_arguments.vertex_shader->shader_module();
assert_true(shader_stage_vertex.module != VK_NULL_HANDLE);
shader_stage_vertex.pName = "main";
shader_stage_vertex.pSpecializationInfo = nullptr;
if (is_tessellated) {
// For tessellation: use our pre-compiled VS for passing data to TCS,
// then TCS (hull shader), then the translated Xenos vertex shader as TES
// (domain shader).
// Tessellation vertex shader (passes indices to TCS).
VkPipelineShaderStageCreateInfo& shader_stage_tess_vs =
shader_stages[shader_stage_count++];
shader_stage_tess_vs.sType =
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shader_stage_tess_vs.pNext = nullptr;
shader_stage_tess_vs.flags = 0;
shader_stage_tess_vs.stage = VK_SHADER_STAGE_VERTEX_BIT;
shader_stage_tess_vs.module = creation_arguments.tessellation_vertex_shader;
shader_stage_tess_vs.pName = "main";
shader_stage_tess_vs.pSpecializationInfo = nullptr;
// Tessellation control shader (hull shader).
VkPipelineShaderStageCreateInfo& shader_stage_tcs =
shader_stages[shader_stage_count++];
shader_stage_tcs.sType =
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shader_stage_tcs.pNext = nullptr;
shader_stage_tcs.flags = 0;
shader_stage_tcs.stage = VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
shader_stage_tcs.module = creation_arguments.tessellation_control_shader;
shader_stage_tcs.pName = "main";
shader_stage_tcs.pSpecializationInfo = nullptr;
// Tessellation evaluation shader (domain shader) - the translated Xenos
// vertex shader.
VkPipelineShaderStageCreateInfo& shader_stage_tes =
shader_stages[shader_stage_count++];
shader_stage_tes.sType =
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shader_stage_tes.pNext = nullptr;
shader_stage_tes.flags = 0;
shader_stage_tes.stage = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
shader_stage_tes.module = creation_arguments.vertex_shader->shader_module();
assert_true(shader_stage_tes.module != VK_NULL_HANDLE);
shader_stage_tes.pName = "main";
shader_stage_tes.pSpecializationInfo = nullptr;
} else {
// Non-tessellated: standard vertex shader.
VkPipelineShaderStageCreateInfo& shader_stage_vertex =
shader_stages[shader_stage_count++];
shader_stage_vertex.sType =
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shader_stage_vertex.pNext = nullptr;
shader_stage_vertex.flags = 0;
shader_stage_vertex.stage = VK_SHADER_STAGE_VERTEX_BIT;
shader_stage_vertex.module =
creation_arguments.vertex_shader->shader_module();
assert_true(shader_stage_vertex.module != VK_NULL_HANDLE);
shader_stage_vertex.pName = "main";
shader_stage_vertex.pSpecializationInfo = nullptr;
}
// Geometry shader.
if (creation_arguments.geometry_shader != VK_NULL_HANDLE) {
VkPipelineShaderStageCreateInfo& shader_stage_geometry =
@@ -2174,6 +2445,27 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
VK_DYNAMIC_STATE_STENCIL_REFERENCE;
}
// Tessellation state (only used when tessellation is active).
VkPipelineTessellationStateCreateInfo tessellation_state = {};
tessellation_state.sType =
VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
if (is_tessellated) {
// Determine patch control point count based on mode and patch type.
// For adaptive mode, we use the actual patch corner count (3 for triangles,
// 4 for quads) since each control point has its own edge factor.
// For discrete/continuous modes, we use 1 control point (the Xenos vertex
// shader receives the patch index and computes all corners internally).
if (description.tessellation_mode == PipelineTessellationMode::kAdaptive) {
tessellation_state.patchControlPoints =
(description.tessellation_patch ==
PipelineTessellationPatchType::kTriangle)
? 3
: 4;
} else {
tessellation_state.patchControlPoints = 1;
}
}
VkGraphicsPipelineCreateInfo pipeline_create_info;
pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_create_info.pNext = nullptr;
@@ -2182,7 +2474,8 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
pipeline_create_info.pStages = shader_stages.data();
pipeline_create_info.pVertexInputState = &vertex_input_state;
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
pipeline_create_info.pTessellationState = nullptr;
pipeline_create_info.pTessellationState =
is_tessellated ? &tessellation_state : nullptr;
pipeline_create_info.pViewportState = &viewport_state;
pipeline_create_info.pRasterizationState = &rasterization_state;
pipeline_create_info.pMultisampleState = &multisample_state;
@@ -2199,19 +2492,23 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
VkPipeline pipeline;
if (dfn.vkCreateGraphicsPipelines(device, vk_pipeline_cache_, 1,
&pipeline_create_info, nullptr,
&pipeline) != VK_SUCCESS) {
// TODO(Triang3l): Move these error messages outside.
/* if (creation_arguments.pixel_shader) {
VkResult result = dfn.vkCreateGraphicsPipelines(
device, vk_pipeline_cache_, 1, &pipeline_create_info, nullptr, &pipeline);
if (result != VK_SUCCESS) {
if (creation_arguments.pixel_shader) {
XELOGE(
"Failed to create graphics pipeline with VS {:016X}, PS {:016X}",
"Failed to create graphics pipeline with VS {:016X}, PS {:016X} "
"(tessellated={}, result={})",
creation_arguments.vertex_shader->shader().ucode_data_hash(),
creation_arguments.pixel_shader->shader().ucode_data_hash());
creation_arguments.pixel_shader->shader().ucode_data_hash(),
is_tessellated, static_cast<int>(result));
} else {
XELOGE("Failed to create graphics pipeline with VS {:016X}",
creation_arguments.vertex_shader->shader().ucode_data_hash());
} */
XELOGE(
"Failed to create graphics pipeline with VS {:016X} "
"(tessellated={}, result={})",
creation_arguments.vertex_shader->shader().ucode_data_hash(),
is_tessellated, static_cast<int>(result));
}
return false;
}
creation_arguments.pipeline->second.pipeline = pipeline;

View File

@@ -114,6 +114,22 @@ class VulkanPipelineCache {
kPoint,
};
// Tessellation mode for pipeline creation.
// Must match the TCS (hull shader) selection logic.
enum class PipelineTessellationMode : uint32_t {
kNone,
kDiscrete, // Integer tessellation factors.
kContinuous, // Fractional (fractional_even) tessellation factors.
kAdaptive, // Per-edge factors from index buffer.
};
// Tessellation primitive type.
enum class PipelineTessellationPatchType : uint32_t {
kNone,
kTriangle,
kQuad,
};
enum class PipelineBlendFactor : uint32_t {
kZero,
kOne,
@@ -152,10 +168,12 @@ class VulkanPipelineCache {
VulkanRenderTargetCache::RenderPassKey render_pass_key;
// Shader stages.
PipelineGeometryShader geometry_shader : 2; // 2
PipelineGeometryShader geometry_shader : 2; // 2
PipelineTessellationMode tessellation_mode : 2; // 4
PipelineTessellationPatchType tessellation_patch : 2; // 6
// Input assembly.
PipelinePrimitiveTopology primitive_topology : 3; // 5
uint32_t primitive_restart : 1; // 6
PipelinePrimitiveTopology primitive_topology : 3; // 9
uint32_t primitive_restart : 1; // 10
// Rasterization.
uint32_t depth_clamp_enable : 1; // 7
PipelinePolygonMode polygon_mode : 2; // 9
@@ -216,6 +234,9 @@ class VulkanPipelineCache {
const VulkanShader::VulkanTranslation* vertex_shader;
const VulkanShader::VulkanTranslation* pixel_shader;
VkShaderModule geometry_shader;
// Tessellation shaders (only used when tessellation is active).
VkShaderModule tessellation_vertex_shader; // VS for passing data to TCS.
VkShaderModule tessellation_control_shader; // TCS (hull shader).
VkRenderPass render_pass;
};
@@ -271,6 +292,15 @@ class VulkanPipelineCache {
GeometryShaderKey& key_out);
VkShaderModule GetGeometryShader(GeometryShaderKey key);
// Get the appropriate tessellation control shader (hull shader) module.
VkShaderModule GetTessellationControlShader(
PipelineTessellationMode mode, PipelineTessellationPatchType patch_type,
bool use_control_point_count) const;
// Get the appropriate tessellation vertex shader module.
VkShaderModule GetTessellationVertexShader(
PipelineTessellationMode mode) const;
// Can be called from creation threads - all needed data must be fully set up
// at the point of the call: shaders must be translated, pipeline layout and
// render pass objects must be available.
@@ -317,6 +347,26 @@ class VulkanPipelineCache {
// shader interlock when no Xenos pixel shader provided.
VkShaderModule depth_only_fragment_shader_ = VK_NULL_HANDLE;
// Tessellation shaders.
// Vertex shaders for tessellation - pass indices/factors to TCS.
VkShaderModule tessellation_indexed_vs_ = VK_NULL_HANDLE;
VkShaderModule tessellation_adaptive_vs_ = VK_NULL_HANDLE;
// Tessellation control shaders (hull shaders) for different modes and
// primitive types.
// Discrete mode (integer tessellation factors).
VkShaderModule discrete_triangle_1cp_hs_ = VK_NULL_HANDLE;
VkShaderModule discrete_triangle_3cp_hs_ = VK_NULL_HANDLE;
VkShaderModule discrete_quad_1cp_hs_ = VK_NULL_HANDLE;
VkShaderModule discrete_quad_4cp_hs_ = VK_NULL_HANDLE;
// Continuous mode (fractional_even tessellation factors).
VkShaderModule continuous_triangle_1cp_hs_ = VK_NULL_HANDLE;
VkShaderModule continuous_triangle_3cp_hs_ = VK_NULL_HANDLE;
VkShaderModule continuous_quad_1cp_hs_ = VK_NULL_HANDLE;
VkShaderModule continuous_quad_4cp_hs_ = VK_NULL_HANDLE;
// Adaptive mode (per-edge factors from index buffer).
VkShaderModule adaptive_triangle_hs_ = VK_NULL_HANDLE;
VkShaderModule adaptive_quad_hs_ = VK_NULL_HANDLE;
// Vulkan pipeline cache for faster pipeline creation.
VkPipelineCache vk_pipeline_cache_ = VK_NULL_HANDLE;