/** ****************************************************************************** * 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_pipeline_cache.h" #include #include #include #include #include #include #include "third_party/glslang/SPIRV/SpvBuilder.h" #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/profiling.h" #include "xenia/base/xxhash.h" #include "xenia/gpu/draw_util.h" #include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/register_file.h" #include "xenia/gpu/registers.h" #include "xenia/gpu/spirv_shader_translator.h" #include "xenia/gpu/vulkan/vulkan_command_processor.h" #include "xenia/gpu/vulkan/vulkan_shader.h" #include "xenia/gpu/xenos.h" #include "xenia/ui/vulkan/vulkan_util.h" namespace xe { namespace gpu { namespace vulkan { VulkanPipelineCache::VulkanPipelineCache( VulkanCommandProcessor& command_processor, const RegisterFile& register_file, VulkanRenderTargetCache& render_target_cache, VkShaderStageFlags guest_shader_vertex_stages) : command_processor_(command_processor), register_file_(register_file), render_target_cache_(render_target_cache), guest_shader_vertex_stages_(guest_shader_vertex_stages) {} VulkanPipelineCache::~VulkanPipelineCache() { Shutdown(); } bool VulkanPipelineCache::Initialize() { const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); shader_translator_ = std::make_unique( SpirvShaderTranslator::Features(provider)); return true; } void VulkanPipelineCache::Shutdown() { const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); VkDevice device = provider.device(); // Destroy all pipelines. last_pipeline_ = nullptr; for (const auto& pipeline_pair : pipelines_) { if (pipeline_pair.second.pipeline != VK_NULL_HANDLE) { dfn.vkDestroyPipeline(device, pipeline_pair.second.pipeline, nullptr); } } pipelines_.clear(); // Destroy all internal shaders. for (const auto& geometry_shader_pair : geometry_shaders_) { if (geometry_shader_pair.second != VK_NULL_HANDLE) { dfn.vkDestroyShaderModule(device, geometry_shader_pair.second, nullptr); } } geometry_shaders_.clear(); // Destroy all translated shaders. for (auto it : shaders_) { delete it.second; } shaders_.clear(); texture_binding_layout_map_.clear(); texture_binding_layouts_.clear(); // Shut down shader translation. shader_translator_.reset(); } VulkanShader* VulkanPipelineCache::LoadShader(xenos::ShaderType shader_type, const uint32_t* host_address, uint32_t dword_count) { // Hash the input memory and lookup the shader. uint64_t data_hash = XXH3_64bits(host_address, dword_count * sizeof(uint32_t)); auto it = shaders_.find(data_hash); if (it != shaders_.end()) { // Shader has been previously loaded. return it->second; } // Always create the shader and stash it away. // We need to track it even if it fails translation so we know not to try // again. VulkanShader* shader = new VulkanShader(command_processor_.GetVulkanProvider(), shader_type, data_hash, host_address, dword_count); shaders_.emplace(data_hash, shader); return shader; } SpirvShaderTranslator::Modification VulkanPipelineCache::GetCurrentVertexShaderModification( const Shader& shader, Shader::HostVertexShaderType host_vertex_shader_type) const { assert_true(shader.type() == xenos::ShaderType::kVertex); assert_true(shader.is_ucode_analyzed()); const auto& regs = register_file_; auto sq_program_cntl = regs.Get(); return SpirvShaderTranslator::Modification( shader_translator_->GetDefaultVertexShaderModification( shader.GetDynamicAddressableRegisterCount(sq_program_cntl.vs_num_reg), host_vertex_shader_type)); } SpirvShaderTranslator::Modification VulkanPipelineCache::GetCurrentPixelShaderModification( const Shader& shader, uint32_t normalized_color_mask) const { assert_true(shader.type() == xenos::ShaderType::kPixel); assert_true(shader.is_ucode_analyzed()); const auto& regs = register_file_; auto sq_program_cntl = regs.Get(); SpirvShaderTranslator::Modification modification( shader_translator_->GetDefaultPixelShaderModification( shader.GetDynamicAddressableRegisterCount( sq_program_cntl.ps_num_reg))); const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const VkPhysicalDeviceFeatures& device_features = provider.device_features(); if (!device_features.independentBlend) { // Since without independent blending, the write mask is common for all // attachments, but the render pass may still include the attachments from // previous draws (to prevent excessive render pass changes potentially // doing stores and loads), disable writing to render targets with a // completely empty write mask by removing the output from the shader. // Only explicitly excluding render targets that the shader actually writes // to, for better pipeline storage compatibility between devices with and // without independent blending (so in the usual situation - the shader // doesn't write to any render targets disabled via the color mask - no // explicit disabling of shader outputs will be needed, and the disabled // output mask will be 0). uint32_t color_targets_remaining = shader.writes_color_targets(); uint32_t color_target_index; while (xe::bit_scan_forward(color_targets_remaining, &color_target_index)) { color_targets_remaining &= ~(uint32_t(1) << color_target_index); if (!(normalized_color_mask & (uint32_t(0b1111) << (4 * color_target_index)))) { modification.pixel.color_outputs_disabled |= uint32_t(1) << color_target_index; } } } return modification; } bool VulkanPipelineCache::ConfigurePipeline( VulkanShader::VulkanTranslation* vertex_shader, VulkanShader::VulkanTranslation* pixel_shader, const PrimitiveProcessor::ProcessingResult& primitive_processing_result, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask, VulkanRenderTargetCache::RenderPassKey render_pass_key, VkPipeline& pipeline_out, const PipelineLayoutProvider*& pipeline_layout_out) { #if XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES SCOPE_profile_cpu_f("gpu"); #endif // XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES // Ensure shaders are translated - needed now for GetCurrentStateDescription. // Edge flags are not supported yet (because polygon primitives are not). assert_true(register_file_.Get().vs_export_mode != xenos::VertexShaderExportMode::kPosition2VectorsEdge && register_file_.Get().vs_export_mode != xenos::VertexShaderExportMode::kPosition2VectorsEdgeKill); assert_false(register_file_.Get().gen_index_vtx); if (!vertex_shader->is_translated()) { vertex_shader->shader().AnalyzeUcode(ucode_disasm_buffer_); if (!TranslateAnalyzedShader(*shader_translator_, *vertex_shader)) { XELOGE("Failed to translate the vertex shader!"); return false; } } if (!vertex_shader->is_valid()) { // Translation attempted previously, but not valid. return false; } if (pixel_shader != nullptr) { if (!pixel_shader->is_translated()) { pixel_shader->shader().AnalyzeUcode(ucode_disasm_buffer_); if (!TranslateAnalyzedShader(*shader_translator_, *pixel_shader)) { XELOGE("Failed to translate the pixel shader!"); return false; } } if (!pixel_shader->is_valid()) { // Translation attempted previously, but not valid. return false; } } PipelineDescription description; if (!GetCurrentStateDescription( vertex_shader, pixel_shader, primitive_processing_result, normalized_depth_control, normalized_color_mask, render_pass_key, description)) { return false; } if (last_pipeline_ && last_pipeline_->first == description) { pipeline_out = last_pipeline_->second.pipeline; pipeline_layout_out = last_pipeline_->second.pipeline_layout; return true; } auto it = pipelines_.find(description); if (it != pipelines_.end()) { last_pipeline_ = &*it; pipeline_out = it->second.pipeline; pipeline_layout_out = it->second.pipeline_layout; return true; } // Create the pipeline if not the latest and not already existing. const PipelineLayoutProvider* pipeline_layout = command_processor_.GetPipelineLayout( pixel_shader ? static_cast(pixel_shader->shader()) .GetTextureBindingsAfterTranslation() .size() : 0, pixel_shader ? static_cast(pixel_shader->shader()) .GetSamplerBindingsAfterTranslation() .size() : 0, static_cast(vertex_shader->shader()) .GetTextureBindingsAfterTranslation() .size(), static_cast(vertex_shader->shader()) .GetSamplerBindingsAfterTranslation() .size()); if (!pipeline_layout) { return false; } VkShaderModule geometry_shader = VK_NULL_HANDLE; GeometryShaderKey geometry_shader_key; if (GetGeometryShaderKey(description.geometry_shader, geometry_shader_key)) { geometry_shader = GetGeometryShader(geometry_shader_key); if (geometry_shader == VK_NULL_HANDLE) { return false; } } VkRenderPass render_pass = render_target_cache_.GetRenderPass(render_pass_key); if (render_pass == VK_NULL_HANDLE) { return false; } PipelineCreationArguments creation_arguments; auto& pipeline = *pipelines_.emplace(description, Pipeline(pipeline_layout)).first; creation_arguments.pipeline = &pipeline; creation_arguments.vertex_shader = vertex_shader; creation_arguments.pixel_shader = pixel_shader; creation_arguments.geometry_shader = geometry_shader; creation_arguments.render_pass = render_pass; if (!EnsurePipelineCreated(creation_arguments)) { return false; } pipeline_out = pipeline.second.pipeline; pipeline_layout_out = pipeline_layout; return true; } bool VulkanPipelineCache::TranslateAnalyzedShader( SpirvShaderTranslator& translator, VulkanShader::VulkanTranslation& translation) { VulkanShader& shader = static_cast(translation.shader()); // Perform translation. // If this fails the shader will be marked as invalid and ignored later. if (!translator.TranslateAnalyzedShader(translation)) { XELOGE("Shader {:016X} translation failed; marking as ignored", shader.ucode_data_hash()); return false; } if (translation.GetOrCreateShaderModule() == VK_NULL_HANDLE) { return false; } // TODO(Triang3l): Log that the shader has been successfully translated in // common code. // Set up the texture binding layout. if (shader.EnterBindingLayoutUserUIDSetup()) { // Obtain the unique IDs of the binding layout if there are any texture // bindings, for invalidation in the command processor. size_t texture_binding_layout_uid = kLayoutUIDEmpty; const std::vector& texture_bindings = shader.GetTextureBindingsAfterTranslation(); size_t texture_binding_count = texture_bindings.size(); if (texture_binding_count) { size_t texture_binding_layout_bytes = texture_binding_count * sizeof(*texture_bindings.data()); uint64_t texture_binding_layout_hash = XXH3_64bits(texture_bindings.data(), texture_binding_layout_bytes); auto found_range = texture_binding_layout_map_.equal_range(texture_binding_layout_hash); for (auto it = found_range.first; it != found_range.second; ++it) { if (it->second.vector_span_length == texture_binding_count && !std::memcmp( texture_binding_layouts_.data() + it->second.vector_span_offset, texture_bindings.data(), texture_binding_layout_bytes)) { texture_binding_layout_uid = it->second.uid; break; } } if (texture_binding_layout_uid == kLayoutUIDEmpty) { static_assert( kLayoutUIDEmpty == 0, "Layout UID is size + 1 because it's assumed that 0 is the UID for " "an empty layout"); texture_binding_layout_uid = texture_binding_layout_map_.size() + 1; LayoutUID new_uid; new_uid.uid = texture_binding_layout_uid; new_uid.vector_span_offset = texture_binding_layouts_.size(); new_uid.vector_span_length = texture_binding_count; texture_binding_layouts_.resize(new_uid.vector_span_offset + texture_binding_count); std::memcpy( texture_binding_layouts_.data() + new_uid.vector_span_offset, texture_bindings.data(), texture_binding_layout_bytes); texture_binding_layout_map_.emplace(texture_binding_layout_hash, new_uid); } } shader.SetTextureBindingLayoutUserUID(texture_binding_layout_uid); // Use the sampler count for samplers because it's the only thing that must // be the same for layouts to be compatible in this case // (instruction-specified parameters are used as overrides for creating // actual samplers). static_assert( kLayoutUIDEmpty == 0, "Empty layout UID is assumed to be 0 because for bindful samplers, the " "UID is their count"); shader.SetSamplerBindingLayoutUserUID( shader.GetSamplerBindingsAfterTranslation().size()); } return true; } void VulkanPipelineCache::WritePipelineRenderTargetDescription( reg::RB_BLENDCONTROL blend_control, uint32_t write_mask, PipelineRenderTarget& render_target_out) const { if (write_mask) { assert_zero(write_mask & ~uint32_t(0b1111)); // 32 because of 0x1F mask, for safety (all unknown to zero). static const PipelineBlendFactor kBlendFactorMap[32] = { /* 0 */ PipelineBlendFactor::kZero, /* 1 */ PipelineBlendFactor::kOne, /* 2 */ PipelineBlendFactor::kZero, // ? /* 3 */ PipelineBlendFactor::kZero, // ? /* 4 */ PipelineBlendFactor::kSrcColor, /* 5 */ PipelineBlendFactor::kOneMinusSrcColor, /* 6 */ PipelineBlendFactor::kSrcAlpha, /* 7 */ PipelineBlendFactor::kOneMinusSrcAlpha, /* 8 */ PipelineBlendFactor::kDstColor, /* 9 */ PipelineBlendFactor::kOneMinusDstColor, /* 10 */ PipelineBlendFactor::kDstAlpha, /* 11 */ PipelineBlendFactor::kOneMinusDstAlpha, /* 12 */ PipelineBlendFactor::kConstantColor, /* 13 */ PipelineBlendFactor::kOneMinusConstantColor, /* 14 */ PipelineBlendFactor::kConstantAlpha, /* 15 */ PipelineBlendFactor::kOneMinusConstantAlpha, /* 16 */ PipelineBlendFactor::kSrcAlphaSaturate, }; render_target_out.src_color_blend_factor = kBlendFactorMap[uint32_t(blend_control.color_srcblend)]; render_target_out.dst_color_blend_factor = kBlendFactorMap[uint32_t(blend_control.color_destblend)]; render_target_out.color_blend_op = blend_control.color_comb_fcn; render_target_out.src_alpha_blend_factor = kBlendFactorMap[uint32_t(blend_control.alpha_srcblend)]; render_target_out.dst_alpha_blend_factor = kBlendFactorMap[uint32_t(blend_control.alpha_destblend)]; render_target_out.alpha_blend_op = blend_control.alpha_comb_fcn; const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const VkPhysicalDevicePortabilitySubsetFeaturesKHR* device_portability_subset_features = provider.device_portability_subset_features(); if (device_portability_subset_features && !device_portability_subset_features->constantAlphaColorBlendFactors) { if (blend_control.color_srcblend == xenos::BlendFactor::kConstantAlpha) { render_target_out.src_color_blend_factor = PipelineBlendFactor::kConstantColor; } else if (blend_control.color_srcblend == xenos::BlendFactor::kOneMinusConstantAlpha) { render_target_out.src_color_blend_factor = PipelineBlendFactor::kOneMinusConstantColor; } if (blend_control.color_destblend == xenos::BlendFactor::kConstantAlpha) { render_target_out.dst_color_blend_factor = PipelineBlendFactor::kConstantColor; } else if (blend_control.color_destblend == xenos::BlendFactor::kOneMinusConstantAlpha) { render_target_out.dst_color_blend_factor = PipelineBlendFactor::kOneMinusConstantColor; } } } else { render_target_out.src_color_blend_factor = PipelineBlendFactor::kOne; render_target_out.dst_color_blend_factor = PipelineBlendFactor::kZero; render_target_out.color_blend_op = xenos::BlendOp::kAdd; render_target_out.src_alpha_blend_factor = PipelineBlendFactor::kOne; render_target_out.dst_alpha_blend_factor = PipelineBlendFactor::kZero; render_target_out.alpha_blend_op = xenos::BlendOp::kAdd; } render_target_out.color_write_mask = write_mask; } bool VulkanPipelineCache::GetCurrentStateDescription( const VulkanShader::VulkanTranslation* vertex_shader, const VulkanShader::VulkanTranslation* pixel_shader, const PrimitiveProcessor::ProcessingResult& primitive_processing_result, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask, VulkanRenderTargetCache::RenderPassKey render_pass_key, PipelineDescription& description_out) const { description_out.Reset(); const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const VkPhysicalDeviceFeatures& device_features = provider.device_features(); const VkPhysicalDevicePortabilitySubsetFeaturesKHR* device_portability_subset_features = provider.device_portability_subset_features(); const RegisterFile& regs = register_file_; auto pa_su_sc_mode_cntl = regs.Get(); description_out.vertex_shader_hash = vertex_shader->shader().ucode_data_hash(); description_out.vertex_shader_modification = vertex_shader->modification(); if (pixel_shader) { description_out.pixel_shader_hash = pixel_shader->shader().ucode_data_hash(); description_out.pixel_shader_modification = pixel_shader->modification(); } description_out.render_pass_key = render_pass_key; // TODO(Triang3l): Implement primitive types currently using geometry shaders // without them. PipelineGeometryShader geometry_shader = PipelineGeometryShader::kNone; PipelinePrimitiveTopology primitive_topology; switch (primitive_processing_result.host_primitive_type) { case xenos::PrimitiveType::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_portability_subset_features || device_portability_subset_features->triangleFans); primitive_topology = PipelinePrimitiveTopology::kTriangleFan; break; case xenos::PrimitiveType::kTriangleStrip: primitive_topology = PipelinePrimitiveTopology::kTriangleStrip; break; case xenos::PrimitiveType::kRectangleList: 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; } description_out.geometry_shader = geometry_shader; description_out.primitive_topology = primitive_topology; description_out.primitive_restart = primitive_processing_result.host_primitive_reset_enabled; description_out.depth_clamp_enable = regs.Get().clip_disable; // TODO(Triang3l): Tessellation. bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs); if (primitive_polygonal) { // Vulkan only allows the polygon mode to be set for both faces - pick the // most special one (more likely to represent the developer's deliberate // intentions - fill is very generic, wireframe is common in debug, points // are for pretty unusual things, but closer to debug purposes too - on the // Xenos, points have the lowest register value and triangles have the // highest) based on which faces are not culled. bool cull_front = pa_su_sc_mode_cntl.cull_front; bool cull_back = pa_su_sc_mode_cntl.cull_back; description_out.cull_front = cull_front; description_out.cull_back = cull_back; if (device_features.fillModeNonSolid) { xenos::PolygonType polygon_type = xenos::PolygonType::kTriangles; if (!cull_front) { polygon_type = std::min(polygon_type, pa_su_sc_mode_cntl.polymode_front_ptype); } if (!cull_back) { polygon_type = std::min(polygon_type, pa_su_sc_mode_cntl.polymode_back_ptype); } if (pa_su_sc_mode_cntl.poly_mode != xenos::PolygonModeEnable::kDualMode) { polygon_type = xenos::PolygonType::kTriangles; } switch (polygon_type) { case xenos::PolygonType::kPoints: // When points are not supported, use lines instead, preserving // debug-like purpose. description_out.polygon_mode = (!device_portability_subset_features || device_portability_subset_features->pointPolygons) ? PipelinePolygonMode::kPoint : PipelinePolygonMode::kLine; break; case xenos::PolygonType::kLines: description_out.polygon_mode = PipelinePolygonMode::kLine; break; case xenos::PolygonType::kTriangles: description_out.polygon_mode = PipelinePolygonMode::kFill; break; default: assert_unhandled_case(polygon_type); return false; } } else { description_out.polygon_mode = PipelinePolygonMode::kFill; } description_out.front_face_clockwise = pa_su_sc_mode_cntl.face != 0; } else { description_out.polygon_mode = PipelinePolygonMode::kFill; } // TODO(Triang3l): Skip depth / stencil and color state for the fragment // shader interlock RB implementation. if (render_pass_key.depth_and_color_used & 1) { if (normalized_depth_control.z_enable) { description_out.depth_write_enable = normalized_depth_control.z_write_enable; description_out.depth_compare_op = normalized_depth_control.zfunc; } else { description_out.depth_compare_op = xenos::CompareFunction::kAlways; } if (normalized_depth_control.stencil_enable) { description_out.stencil_test_enable = 1; description_out.stencil_front_fail_op = normalized_depth_control.stencilfail; description_out.stencil_front_pass_op = normalized_depth_control.stencilzpass; description_out.stencil_front_depth_fail_op = normalized_depth_control.stencilzfail; description_out.stencil_front_compare_op = normalized_depth_control.stencilfunc; if (primitive_polygonal && normalized_depth_control.backface_enable) { description_out.stencil_back_fail_op = normalized_depth_control.stencilfail_bf; description_out.stencil_back_pass_op = normalized_depth_control.stencilzpass_bf; description_out.stencil_back_depth_fail_op = normalized_depth_control.stencilzfail_bf; description_out.stencil_back_compare_op = normalized_depth_control.stencilfunc_bf; } else { description_out.stencil_back_fail_op = description_out.stencil_front_fail_op; description_out.stencil_back_pass_op = description_out.stencil_front_pass_op; description_out.stencil_back_depth_fail_op = description_out.stencil_front_depth_fail_op; description_out.stencil_back_compare_op = description_out.stencil_front_compare_op; } } } // Color blending and write masks (filled only for the attachments present in // the render pass object). uint32_t render_pass_color_rts = render_pass_key.depth_and_color_used >> 1; if (device_features.independentBlend) { uint32_t render_pass_color_rts_remaining = render_pass_color_rts; uint32_t color_rt_index; while (xe::bit_scan_forward(render_pass_color_rts_remaining, &color_rt_index)) { render_pass_color_rts_remaining &= ~(uint32_t(1) << color_rt_index); WritePipelineRenderTargetDescription( regs.Get( reg::RB_BLENDCONTROL::rt_register_indices[color_rt_index]), (normalized_color_mask >> (color_rt_index * 4)) & 0b1111, description_out.render_targets[color_rt_index]); } } else { // Take the blend control for the first render target that the guest wants // to write to (consider it the most important) and use it for all render // targets, if any. // TODO(Triang3l): Implement an option for independent blending via multiple // draw calls with different pipelines maybe? Though independent blending // support is pretty wide, with a quite prominent exception of Adreno 4xx // apparently. uint32_t render_pass_color_rts_remaining = render_pass_color_rts; uint32_t render_pass_first_color_rt_index; if (xe::bit_scan_forward(render_pass_color_rts_remaining, &render_pass_first_color_rt_index)) { render_pass_color_rts_remaining &= ~(uint32_t(1) << render_pass_first_color_rt_index); PipelineRenderTarget& render_pass_first_color_rt = description_out.render_targets[render_pass_first_color_rt_index]; uint32_t common_blend_rt_index; if (xe::bit_scan_forward(normalized_color_mask, &common_blend_rt_index)) { common_blend_rt_index >>= 2; // If a common write mask will be used for multiple render targets, use // the original RB_COLOR_MASK instead of the normalized color mask as // the normalized color mask has non-existent components forced to // written (don't need reading to be preserved), while the number of // components may vary between render targets. The attachments in the // pass that must not be written to at all will be excluded via a shader // modification. WritePipelineRenderTargetDescription( regs.Get( reg::RB_BLENDCONTROL::rt_register_indices [common_blend_rt_index]), (((normalized_color_mask & ~(uint32_t(0b1111) << (4 * common_blend_rt_index))) ? regs[XE_GPU_REG_RB_COLOR_MASK].u32 : normalized_color_mask) >> (4 * common_blend_rt_index)) & 0b1111, render_pass_first_color_rt); } else { // No render targets are written to, though the render pass still may // contain color attachments - set them to not written and not blending. render_pass_first_color_rt.src_color_blend_factor = PipelineBlendFactor::kOne; render_pass_first_color_rt.dst_color_blend_factor = PipelineBlendFactor::kZero; render_pass_first_color_rt.color_blend_op = xenos::BlendOp::kAdd; render_pass_first_color_rt.src_alpha_blend_factor = PipelineBlendFactor::kOne; render_pass_first_color_rt.dst_alpha_blend_factor = PipelineBlendFactor::kZero; render_pass_first_color_rt.alpha_blend_op = xenos::BlendOp::kAdd; } // Reuse the same blending settings for all render targets in the pass, // for description consistency. uint32_t color_rt_index; while (xe::bit_scan_forward(render_pass_color_rts_remaining, &color_rt_index)) { render_pass_color_rts_remaining &= ~(uint32_t(1) << color_rt_index); description_out.render_targets[color_rt_index] = render_pass_first_color_rt; } } } return true; } bool VulkanPipelineCache::ArePipelineRequirementsMet( const PipelineDescription& description) const { VkShaderStageFlags vertex_shader_stage = Shader::IsHostVertexShaderTypeDomain( SpirvShaderTranslator::Modification( description.vertex_shader_modification) .vertex.host_vertex_shader_type) ? VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT : VK_SHADER_STAGE_VERTEX_BIT; if (!(guest_shader_vertex_stages_ & vertex_shader_stage)) { return false; } const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const VkPhysicalDevicePortabilitySubsetFeaturesKHR* device_portability_subset_features = provider.device_portability_subset_features(); if (device_portability_subset_features) { if (description.primitive_topology == PipelinePrimitiveTopology::kTriangleFan && !device_portability_subset_features->triangleFans) { return false; } if (description.polygon_mode == PipelinePolygonMode::kPoint && !device_portability_subset_features->pointPolygons) { return false; } if (!device_portability_subset_features->constantAlphaColorBlendFactors) { uint32_t color_rts_remaining = description.render_pass_key.depth_and_color_used >> 1; uint32_t color_rt_index; while (xe::bit_scan_forward(color_rts_remaining, &color_rt_index)) { color_rts_remaining &= ~(uint32_t(1) << color_rt_index); const PipelineRenderTarget& color_rt = description.render_targets[color_rt_index]; if (color_rt.src_color_blend_factor == PipelineBlendFactor::kConstantAlpha || color_rt.src_color_blend_factor == PipelineBlendFactor::kOneMinusConstantAlpha || color_rt.dst_color_blend_factor == PipelineBlendFactor::kConstantAlpha || color_rt.dst_color_blend_factor == PipelineBlendFactor::kOneMinusConstantAlpha) { return false; } } } } const VkPhysicalDeviceFeatures& device_features = provider.device_features(); if (!device_features.geometryShader && description.geometry_shader != PipelineGeometryShader::kNone) { return false; } if (!device_features.fillModeNonSolid && description.polygon_mode != PipelinePolygonMode::kFill) { return false; } if (!device_features.independentBlend) { uint32_t color_rts_remaining = description.render_pass_key.depth_and_color_used >> 1; uint32_t first_color_rt_index; if (xe::bit_scan_forward(color_rts_remaining, &first_color_rt_index)) { color_rts_remaining &= ~(uint32_t(1) << first_color_rt_index); const PipelineRenderTarget& first_color_rt = description.render_targets[first_color_rt_index]; uint32_t color_rt_index; while (xe::bit_scan_forward(color_rts_remaining, &color_rt_index)) { color_rts_remaining &= ~(uint32_t(1) << color_rt_index); const PipelineRenderTarget& color_rt = description.render_targets[color_rt_index]; if (color_rt.src_color_blend_factor != first_color_rt.src_color_blend_factor || color_rt.dst_color_blend_factor != first_color_rt.dst_color_blend_factor || color_rt.color_blend_op != first_color_rt.color_blend_op || color_rt.src_alpha_blend_factor != first_color_rt.src_alpha_blend_factor || color_rt.dst_alpha_blend_factor != first_color_rt.dst_alpha_blend_factor || color_rt.alpha_blend_op != first_color_rt.alpha_blend_op || color_rt.color_write_mask != first_color_rt.color_write_mask) { return false; } } } } return true; } bool VulkanPipelineCache::GetGeometryShaderKey( PipelineGeometryShader geometry_shader_type, GeometryShaderKey& key_out) { if (geometry_shader_type == PipelineGeometryShader::kNone) { return false; } GeometryShaderKey key; key.type = geometry_shader_type; // TODO(Triang3l): Make the linkage parameters depend on the real needs of the // vertex and the pixel shader. key.interpolator_count = xenos::kMaxInterpolators; key.user_clip_plane_count = /* 6 */ 0; key.user_clip_plane_cull = 0; key.has_vertex_kill_and = /* 1 */ 0; key.has_point_size = /* 1 */ 0; key.has_point_coordinates = /* 1 */ 0; key_out = key; return true; } VkShaderModule VulkanPipelineCache::GetGeometryShader(GeometryShaderKey key) { auto it = geometry_shaders_.find(key); if (it != geometry_shaders_.end()) { return it->second; } std::vector id_vector_temp; std::vector uint_vector_temp; spv::ExecutionMode input_primitive_execution_mode = spv::ExecutionMode(0); uint32_t input_primitive_vertex_count = 0; spv::ExecutionMode output_primitive_execution_mode = spv::ExecutionMode(0); uint32_t output_max_vertices = 0; switch (key.type) { case PipelineGeometryShader::kRectangleList: // Triangle to a strip of 2 triangles. input_primitive_execution_mode = spv::ExecutionModeTriangles; input_primitive_vertex_count = 3; output_primitive_execution_mode = spv::ExecutionModeOutputTriangleStrip; output_max_vertices = 4; break; case PipelineGeometryShader::kQuadList: // 4 vertices passed via a line list with adjacency to a strip of 2 // triangles. input_primitive_execution_mode = spv::ExecutionModeInputLinesAdjacency; input_primitive_vertex_count = 4; output_primitive_execution_mode = spv::ExecutionModeOutputTriangleStrip; output_max_vertices = 4; break; default: assert_unhandled_case(key.type); } uint32_t clip_distance_count = key.user_clip_plane_cull ? 0 : key.user_clip_plane_count; uint32_t cull_distance_count = (key.user_clip_plane_cull ? key.user_clip_plane_count : 0) + key.has_vertex_kill_and; spv::Builder builder(spv::Spv_1_0, (SpirvShaderTranslator::kSpirvMagicToolId << 16) | 1, nullptr); spv::Id ext_inst_glsl_std_450 = builder.import("GLSL.std.450"); builder.addCapability(spv::CapabilityGeometry); if (clip_distance_count) { builder.addCapability(spv::CapabilityClipDistance); } if (cull_distance_count) { builder.addCapability(spv::CapabilityCullDistance); } builder.setMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450); builder.setSource(spv::SourceLanguageUnknown, 0); // TODO(Triang3l): Shader float controls (NaN preservation most importantly). std::vector main_interface; spv::Id type_void = builder.makeVoidType(); spv::Id type_bool = builder.makeBoolType(); spv::Id type_bool4 = builder.makeVectorType(type_bool, 4); spv::Id type_int = builder.makeIntType(32); spv::Id type_float = builder.makeFloatType(32); spv::Id type_float4 = builder.makeVectorType(type_float, 4); spv::Id type_clip_distances = clip_distance_count ? builder.makeArrayType( type_float, builder.makeUintConstant(clip_distance_count), 0) : spv::NoType; spv::Id type_cull_distances = cull_distance_count ? builder.makeArrayType( type_float, builder.makeUintConstant(cull_distance_count), 0) : spv::NoType; spv::Id type_interpolators = key.interpolator_count ? builder.makeArrayType( type_float4, builder.makeUintConstant(key.interpolator_count), 0) : spv::NoType; spv::Id type_point_coordinates = key.has_point_coordinates ? builder.makeVectorType(type_float, 2) : spv::NoType; // Inputs and outputs - matching glslang order, in gl_PerVertex gl_in[], // user-defined outputs, user-defined inputs, out gl_PerVertex. // TODO(Triang3l): Point parameters from the system uniform buffer. spv::Id const_input_primitive_vertex_count = builder.makeUintConstant(input_primitive_vertex_count); // in gl_PerVertex gl_in[]. // gl_Position. id_vector_temp.clear(); uint32_t member_in_gl_per_vertex_position = uint32_t(id_vector_temp.size()); id_vector_temp.push_back(type_float4); spv::Id const_member_in_gl_per_vertex_position = builder.makeIntConstant(int32_t(member_in_gl_per_vertex_position)); // gl_ClipDistance. uint32_t member_in_gl_per_vertex_clip_distance = UINT32_MAX; spv::Id const_member_in_gl_per_vertex_clip_distance = spv::NoResult; if (clip_distance_count) { member_in_gl_per_vertex_clip_distance = uint32_t(id_vector_temp.size()); id_vector_temp.push_back(type_clip_distances); const_member_in_gl_per_vertex_clip_distance = builder.makeIntConstant(int32_t(member_in_gl_per_vertex_clip_distance)); } // gl_CullDistance. uint32_t member_in_gl_per_vertex_cull_distance = UINT32_MAX; if (cull_distance_count) { member_in_gl_per_vertex_cull_distance = uint32_t(id_vector_temp.size()); id_vector_temp.push_back(type_cull_distances); } // Structure and array. spv::Id type_struct_in_gl_per_vertex = builder.makeStructType(id_vector_temp, "gl_PerVertex"); builder.addMemberName(type_struct_in_gl_per_vertex, member_in_gl_per_vertex_position, "gl_Position"); builder.addMemberDecoration(type_struct_in_gl_per_vertex, member_in_gl_per_vertex_position, spv::DecorationBuiltIn, spv::BuiltInPosition); if (clip_distance_count) { builder.addMemberName(type_struct_in_gl_per_vertex, member_in_gl_per_vertex_clip_distance, "gl_ClipDistance"); builder.addMemberDecoration( type_struct_in_gl_per_vertex, member_in_gl_per_vertex_clip_distance, spv::DecorationBuiltIn, spv::BuiltInClipDistance); } if (cull_distance_count) { builder.addMemberName(type_struct_in_gl_per_vertex, member_in_gl_per_vertex_cull_distance, "gl_CullDistance"); builder.addMemberDecoration( type_struct_in_gl_per_vertex, member_in_gl_per_vertex_cull_distance, spv::DecorationBuiltIn, spv::BuiltInCullDistance); } builder.addDecoration(type_struct_in_gl_per_vertex, spv::DecorationBlock); spv::Id type_array_in_gl_per_vertex = builder.makeArrayType( type_struct_in_gl_per_vertex, const_input_primitive_vertex_count, 0); spv::Id in_gl_per_vertex = builder.createVariable(spv::NoPrecision, spv::StorageClassInput, type_array_in_gl_per_vertex, "gl_in"); main_interface.push_back(in_gl_per_vertex); // Interpolators output. spv::Id out_interpolators = spv::NoResult; if (key.interpolator_count) { out_interpolators = builder.createVariable(spv::NoPrecision, spv::StorageClassOutput, type_interpolators, "xe_out_interpolators"); builder.addDecoration(out_interpolators, spv::DecorationLocation, 0); builder.addDecoration(out_interpolators, spv::DecorationInvariant); main_interface.push_back(out_interpolators); } // Point coordinate output. spv::Id out_point_coordinates = spv::NoResult; if (key.has_point_coordinates) { out_point_coordinates = builder.createVariable( spv::NoPrecision, spv::StorageClassOutput, type_point_coordinates, "xe_out_point_coordinates"); builder.addDecoration(out_point_coordinates, spv::DecorationLocation, key.interpolator_count); builder.addDecoration(out_point_coordinates, spv::DecorationInvariant); main_interface.push_back(out_point_coordinates); } // Interpolator input. spv::Id in_interpolators = spv::NoResult; if (key.interpolator_count) { in_interpolators = builder.createVariable( spv::NoPrecision, spv::StorageClassInput, builder.makeArrayType(type_interpolators, const_input_primitive_vertex_count, 0), "xe_in_interpolators"); builder.addDecoration(in_interpolators, spv::DecorationLocation, 0); main_interface.push_back(in_interpolators); } // Point size input. spv::Id in_point_size = spv::NoResult; if (key.has_point_size) { in_point_size = builder.createVariable( spv::NoPrecision, spv::StorageClassInput, builder.makeArrayType(type_float, const_input_primitive_vertex_count, 0), "xe_in_point_size"); builder.addDecoration(in_point_size, spv::DecorationLocation, key.interpolator_count); main_interface.push_back(in_point_size); } // out gl_PerVertex. // gl_Position. id_vector_temp.clear(); uint32_t member_out_gl_per_vertex_position = uint32_t(id_vector_temp.size()); id_vector_temp.push_back(type_float4); spv::Id const_member_out_gl_per_vertex_position = builder.makeIntConstant(int32_t(member_out_gl_per_vertex_position)); // gl_ClipDistance. uint32_t member_out_gl_per_vertex_clip_distance = UINT32_MAX; spv::Id const_member_out_gl_per_vertex_clip_distance = spv::NoResult; if (clip_distance_count) { member_out_gl_per_vertex_clip_distance = uint32_t(id_vector_temp.size()); id_vector_temp.push_back(type_clip_distances); const_member_out_gl_per_vertex_clip_distance = builder.makeIntConstant( int32_t(member_out_gl_per_vertex_clip_distance)); } // Structure. spv::Id type_struct_out_gl_per_vertex = builder.makeStructType(id_vector_temp, "gl_PerVertex"); builder.addMemberName(type_struct_out_gl_per_vertex, member_out_gl_per_vertex_position, "gl_Position"); builder.addMemberDecoration(type_struct_out_gl_per_vertex, member_out_gl_per_vertex_position, spv::DecorationInvariant); builder.addMemberDecoration(type_struct_out_gl_per_vertex, member_out_gl_per_vertex_position, spv::DecorationBuiltIn, spv::BuiltInPosition); if (clip_distance_count) { builder.addMemberName(type_struct_out_gl_per_vertex, member_out_gl_per_vertex_clip_distance, "gl_ClipDistance"); builder.addMemberDecoration(type_struct_out_gl_per_vertex, member_out_gl_per_vertex_clip_distance, spv::DecorationInvariant); builder.addMemberDecoration( type_struct_out_gl_per_vertex, member_out_gl_per_vertex_clip_distance, spv::DecorationBuiltIn, spv::BuiltInClipDistance); } builder.addDecoration(type_struct_out_gl_per_vertex, spv::DecorationBlock); spv::Id out_gl_per_vertex = builder.createVariable(spv::NoPrecision, spv::StorageClassOutput, type_struct_out_gl_per_vertex, ""); main_interface.push_back(out_gl_per_vertex); // Begin the main function. std::vector main_param_types; std::vector> main_precisions; spv::Block* main_entry; spv::Function* main_function = builder.makeFunctionEntry(spv::NoPrecision, type_void, "main", main_param_types, main_precisions, &main_entry); spv::Instruction* entry_point = builder.addEntryPoint(spv::ExecutionModelGeometry, main_function, "main"); for (spv::Id interface_id : main_interface) { entry_point->addIdOperand(interface_id); } builder.addExecutionMode(main_function, input_primitive_execution_mode); builder.addExecutionMode(main_function, spv::ExecutionModeInvocations, 1); builder.addExecutionMode(main_function, output_primitive_execution_mode); builder.addExecutionMode(main_function, spv::ExecutionModeOutputVertices, int(output_max_vertices)); // Note that after every OpEmitVertex, all output variables are undefined. // Discard the whole primitive if any vertex has a NaN position (may also be // set to NaN for emulation of vertex killing with the OR operator). for (uint32_t i = 0; i < input_primitive_vertex_count; ++i) { id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(builder.makeIntConstant(int32_t(i))); id_vector_temp.push_back(const_member_in_gl_per_vertex_position); spv::Id position_is_nan = builder.createUnaryOp( spv::OpAny, type_bool, builder.createUnaryOp( spv::OpIsNan, type_bool4, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision))); spv::Block& discard_predecessor = *builder.getBuildPoint(); spv::Block& discard_then_block = builder.makeNewBlock(); spv::Block& discard_merge_block = builder.makeNewBlock(); { std::unique_ptr selection_merge_op( std::make_unique(spv::OpSelectionMerge)); selection_merge_op->addIdOperand(discard_merge_block.getId()); selection_merge_op->addImmediateOperand( spv::SelectionControlDontFlattenMask); discard_predecessor.addInstruction(std::move(selection_merge_op)); } { std::unique_ptr branch_conditional_op( std::make_unique(spv::OpBranchConditional)); branch_conditional_op->addIdOperand(position_is_nan); branch_conditional_op->addIdOperand(discard_then_block.getId()); branch_conditional_op->addIdOperand(discard_merge_block.getId()); branch_conditional_op->addImmediateOperand(1); branch_conditional_op->addImmediateOperand(2); discard_predecessor.addInstruction(std::move(branch_conditional_op)); } discard_then_block.addPredecessor(&discard_predecessor); discard_merge_block.addPredecessor(&discard_predecessor); builder.setBuildPoint(&discard_then_block); builder.createNoResultOp(spv::OpReturn); builder.setBuildPoint(&discard_merge_block); } // Cull the whole primitive if any cull distance for all vertices in the // primitive is < 0. // TODO(Triang3l): For points, handle ps_ucp_mode (transform the host clip // space to the guest one, calculate the distances to the user clip planes, // cull using the distance from the center for modes 0, 1 and 2, cull and clip // per-vertex for modes 2 and 3) - except for the vertex kill flag. if (cull_distance_count) { spv::Id const_member_in_gl_per_vertex_cull_distance = builder.makeIntConstant(int32_t(member_in_gl_per_vertex_cull_distance)); spv::Id const_float_0 = builder.makeFloatConstant(0.0f); spv::Id cull_condition = spv::NoResult; for (uint32_t i = 0; i < cull_distance_count; ++i) { for (uint32_t j = 0; j < input_primitive_vertex_count; ++j) { id_vector_temp.clear(); id_vector_temp.reserve(3); id_vector_temp.push_back(builder.makeIntConstant(int32_t(j))); id_vector_temp.push_back(const_member_in_gl_per_vertex_cull_distance); id_vector_temp.push_back(builder.makeIntConstant(int32_t(i))); spv::Id cull_distance_is_negative = builder.createBinOp( spv::OpFOrdLessThan, type_bool, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision), const_float_0); if (cull_condition != spv::NoResult) { cull_condition = builder.createBinOp(spv::OpLogicalAnd, type_bool, cull_condition, cull_distance_is_negative); } else { cull_condition = cull_distance_is_negative; } } } assert_true(cull_condition != spv::NoResult); spv::Block& discard_predecessor = *builder.getBuildPoint(); spv::Block& discard_then_block = builder.makeNewBlock(); spv::Block& discard_merge_block = builder.makeNewBlock(); { std::unique_ptr selection_merge_op( std::make_unique(spv::OpSelectionMerge)); selection_merge_op->addIdOperand(discard_merge_block.getId()); selection_merge_op->addImmediateOperand( spv::SelectionControlDontFlattenMask); discard_predecessor.addInstruction(std::move(selection_merge_op)); } { std::unique_ptr branch_conditional_op( std::make_unique(spv::OpBranchConditional)); branch_conditional_op->addIdOperand(cull_condition); branch_conditional_op->addIdOperand(discard_then_block.getId()); branch_conditional_op->addIdOperand(discard_merge_block.getId()); branch_conditional_op->addImmediateOperand(1); branch_conditional_op->addImmediateOperand(2); discard_predecessor.addInstruction(std::move(branch_conditional_op)); } discard_then_block.addPredecessor(&discard_predecessor); discard_merge_block.addPredecessor(&discard_predecessor); builder.setBuildPoint(&discard_then_block); builder.createNoResultOp(spv::OpReturn); builder.setBuildPoint(&discard_merge_block); } switch (key.type) { case PipelineGeometryShader::kRectangleList: { // Construct a strip with the fourth vertex generated by mirroring a // vertex across the longest edge (the diagonal). // // Possible options: // // 0---1 // | /| // | / | - 12 is the longest edge, strip 0123 (most commonly used) // |/ | v3 = v0 + (v1 - v0) + (v2 - v0), or v3 = -v0 + v1 + v2 // 2--[3] // // 1---2 // | /| // | / | - 20 is the longest edge, strip 1203 // |/ | // 0--[3] // // 2---0 // | /| // | / | - 01 is the longest edge, strip 2013 // |/ | // 1--[3] spv::Id const_int_0 = builder.makeIntConstant(0); spv::Id const_int_1 = builder.makeIntConstant(1); spv::Id const_int_2 = builder.makeIntConstant(2); spv::Id const_int_3 = builder.makeIntConstant(3); // Get squares of edge lengths to choose the longest edge. // [0] - 12, [1] - 20, [2] - 01. spv::Id edge_lengths[3]; id_vector_temp.resize(3); id_vector_temp[1] = const_member_in_gl_per_vertex_position; for (uint32_t i = 0; i < 3; ++i) { id_vector_temp[0] = builder.makeIntConstant(int32_t((1 + i) % 3)); id_vector_temp[2] = const_int_0; spv::Id edge_0_x = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp[2] = const_int_1; spv::Id edge_0_y = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp[0] = builder.makeIntConstant(int32_t((2 + i) % 3)); id_vector_temp[2] = const_int_0; spv::Id edge_1_x = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp[2] = const_int_1; spv::Id edge_1_y = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); spv::Id edge_x = builder.createBinOp(spv::OpFSub, type_float, edge_1_x, edge_0_x); spv::Id edge_y = builder.createBinOp(spv::OpFSub, type_float, edge_1_y, edge_0_y); edge_lengths[i] = builder.createBinOp( spv::OpFAdd, type_float, builder.createBinOp(spv::OpFMul, type_float, edge_x, edge_x), builder.createBinOp(spv::OpFMul, type_float, edge_y, edge_y)); } // Choose the index of the first vertex in the strip based on which edge // is the longest, and calculate the indices of the other vertices. spv::Id vertex_indices[3]; // If 12 > 20 && 12 > 01, then 12 is the longest edge, and the strip is // 0123. Otherwise, if 20 > 01, then 20 is the longest, and the strip is // 1203, but if not, 01 is the longest, and the strip is 2013. vertex_indices[0] = builder.createTriOp( spv::OpSelect, type_int, builder.createBinOp( spv::OpLogicalAnd, type_bool, builder.createBinOp(spv::OpFOrdGreaterThan, type_bool, edge_lengths[0], edge_lengths[1]), builder.createBinOp(spv::OpFOrdGreaterThan, type_bool, edge_lengths[0], edge_lengths[2])), const_int_0, builder.createTriOp( spv::OpSelect, type_int, builder.createBinOp(spv::OpFOrdGreaterThan, type_bool, edge_lengths[1], edge_lengths[2]), const_int_1, const_int_2)); for (uint32_t i = 1; i < 3; ++i) { // vertex_indices[i] = (vertex_indices[0] + i) % 3 spv::Id vertex_index_without_wrapping = builder.createBinOp(spv::OpIAdd, type_int, vertex_indices[0], builder.makeIntConstant(int32_t(i))); vertex_indices[i] = builder.createTriOp( spv::OpSelect, type_int, builder.createBinOp(spv::OpSLessThan, type_bool, vertex_index_without_wrapping, const_int_3), vertex_index_without_wrapping, builder.createBinOp(spv::OpISub, type_int, vertex_index_without_wrapping, const_int_3)); } // Initialize the point coordinates output for safety if this shader type // is used with has_point_coordinates for some reason. spv::Id const_point_coordinates_zero = spv::NoResult; if (key.has_point_coordinates) { spv::Id const_float_0 = builder.makeFloatConstant(0.0f); id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(const_float_0); id_vector_temp.push_back(const_float_0); const_point_coordinates_zero = builder.makeCompositeConstant( type_point_coordinates, id_vector_temp); } // Emit the triangle in the strip that consists of the original vertices. for (uint32_t i = 0; i < 3; ++i) { spv::Id vertex_index = vertex_indices[i]; // Interpolators. if (key.interpolator_count) { id_vector_temp.clear(); id_vector_temp.push_back(vertex_index); builder.createStore( builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_interpolators, id_vector_temp), spv::NoPrecision), out_interpolators); } // Point coordinates. if (key.has_point_coordinates) { builder.createStore(const_point_coordinates_zero, out_point_coordinates); } // Position. id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(vertex_index); id_vector_temp.push_back(const_member_in_gl_per_vertex_position); spv::Id vertex_position = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp.clear(); id_vector_temp.push_back(const_member_out_gl_per_vertex_position); builder.createStore( vertex_position, builder.createAccessChain(spv::StorageClassOutput, out_gl_per_vertex, id_vector_temp)); // Clip distances. if (clip_distance_count) { id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(vertex_index); id_vector_temp.push_back(const_member_in_gl_per_vertex_clip_distance); spv::Id vertex_clip_distances = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp.clear(); id_vector_temp.push_back( const_member_out_gl_per_vertex_clip_distance); builder.createStore( vertex_clip_distances, builder.createAccessChain(spv::StorageClassOutput, out_gl_per_vertex, id_vector_temp)); } // Emit the vertex. builder.createNoResultOp(spv::OpEmitVertex); } // Construct the fourth vertex. // Interpolators. for (uint32_t i = 0; i < key.interpolator_count; ++i) { spv::Id const_int_i = builder.makeIntConstant(int32_t(i)); id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(vertex_indices[0]); id_vector_temp.push_back(const_int_i); spv::Id vertex_interpolator_v0 = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_interpolators, id_vector_temp), spv::NoPrecision); id_vector_temp[0] = vertex_indices[1]; spv::Id vertex_interpolator_v01 = builder.createBinOp( spv::OpFSub, type_float4, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_interpolators, id_vector_temp), spv::NoPrecision), vertex_interpolator_v0); builder.addDecoration(vertex_interpolator_v01, spv::DecorationNoContraction); id_vector_temp[0] = vertex_indices[2]; spv::Id vertex_interpolator_v3 = builder.createBinOp( spv::OpFAdd, type_float4, vertex_interpolator_v01, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_interpolators, id_vector_temp), spv::NoPrecision)); builder.addDecoration(vertex_interpolator_v3, spv::DecorationNoContraction); id_vector_temp.clear(); id_vector_temp.push_back(const_int_i); builder.createStore( vertex_interpolator_v3, builder.createAccessChain(spv::StorageClassOutput, out_interpolators, id_vector_temp)); } // Point coordinates. if (key.has_point_coordinates) { builder.createStore(const_point_coordinates_zero, out_point_coordinates); } // Position. id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(vertex_indices[0]); id_vector_temp.push_back(const_member_in_gl_per_vertex_position); spv::Id vertex_position_v0 = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp[0] = vertex_indices[1]; spv::Id vertex_position_v01 = builder.createBinOp( spv::OpFSub, type_float4, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision), vertex_position_v0); builder.addDecoration(vertex_position_v01, spv::DecorationNoContraction); id_vector_temp[0] = vertex_indices[2]; spv::Id vertex_position_v3 = builder.createBinOp( spv::OpFAdd, type_float4, vertex_position_v01, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision)); builder.addDecoration(vertex_position_v3, spv::DecorationNoContraction); id_vector_temp.clear(); id_vector_temp.push_back(const_member_out_gl_per_vertex_position); builder.createStore( vertex_position_v3, builder.createAccessChain(spv::StorageClassOutput, out_gl_per_vertex, id_vector_temp)); // Clip distances. for (uint32_t i = 0; i < clip_distance_count; ++i) { spv::Id const_int_i = builder.makeIntConstant(int32_t(i)); id_vector_temp.clear(); id_vector_temp.reserve(3); id_vector_temp.push_back(vertex_indices[0]); id_vector_temp.push_back(const_member_in_gl_per_vertex_clip_distance); id_vector_temp.push_back(const_int_i); spv::Id vertex_clip_distance_v0 = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp[0] = vertex_indices[1]; spv::Id vertex_clip_distance_v01 = builder.createBinOp( spv::OpFSub, type_float, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision), vertex_clip_distance_v0); builder.addDecoration(vertex_clip_distance_v01, spv::DecorationNoContraction); id_vector_temp[0] = vertex_indices[2]; spv::Id vertex_clip_distance_v3 = builder.createBinOp( spv::OpFAdd, type_float, vertex_clip_distance_v01, builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision)); builder.addDecoration(vertex_clip_distance_v3, spv::DecorationNoContraction); id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(const_member_in_gl_per_vertex_clip_distance); id_vector_temp.push_back(const_int_i); builder.createStore( vertex_clip_distance_v3, builder.createAccessChain(spv::StorageClassOutput, out_gl_per_vertex, id_vector_temp)); } // Emit the vertex. builder.createNoResultOp(spv::OpEmitVertex); builder.createNoResultOp(spv::OpEndPrimitive); } break; case PipelineGeometryShader::kQuadList: { // Initialize the point coordinates output for safety if this shader type // is used with has_point_coordinates for some reason. spv::Id const_point_coordinates_zero = spv::NoResult; if (key.has_point_coordinates) { spv::Id const_float_0 = builder.makeFloatConstant(0.0f); id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(const_float_0); id_vector_temp.push_back(const_float_0); const_point_coordinates_zero = builder.makeCompositeConstant( type_point_coordinates, id_vector_temp); } // Build the triangle strip from the original quad vertices in the // 0, 1, 3, 2 order (like specified for GL_QUAD_STRIP). // TODO(Triang3l): Find the correct decomposition of quads into triangles // on the real hardware. for (uint32_t i = 0; i < 4; ++i) { spv::Id const_vertex_index = builder.makeIntConstant(int32_t(i ^ (i >> 1))); // Interpolators. if (key.interpolator_count) { id_vector_temp.clear(); id_vector_temp.push_back(const_vertex_index); builder.createStore( builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_interpolators, id_vector_temp), spv::NoPrecision), out_interpolators); } // Point coordinates. if (key.has_point_coordinates) { builder.createStore(const_point_coordinates_zero, out_point_coordinates); } // Position. id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(const_vertex_index); id_vector_temp.push_back(const_member_in_gl_per_vertex_position); spv::Id vertex_position = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp.clear(); id_vector_temp.push_back(const_member_out_gl_per_vertex_position); builder.createStore( vertex_position, builder.createAccessChain(spv::StorageClassOutput, out_gl_per_vertex, id_vector_temp)); // Clip distances. if (clip_distance_count) { id_vector_temp.clear(); id_vector_temp.reserve(2); id_vector_temp.push_back(const_vertex_index); id_vector_temp.push_back(const_member_in_gl_per_vertex_clip_distance); spv::Id vertex_clip_distances = builder.createLoad( builder.createAccessChain(spv::StorageClassInput, in_gl_per_vertex, id_vector_temp), spv::NoPrecision); id_vector_temp.clear(); id_vector_temp.push_back( const_member_out_gl_per_vertex_clip_distance); builder.createStore( vertex_clip_distances, builder.createAccessChain(spv::StorageClassOutput, out_gl_per_vertex, id_vector_temp)); } // Emit the vertex. builder.createNoResultOp(spv::OpEmitVertex); } builder.createNoResultOp(spv::OpEndPrimitive); } break; default: assert_unhandled_case(key.type); } // End the main function. builder.leaveFunction(); // Serialize the shader code. std::vector shader_code; builder.dump(shader_code); // Create the shader module, and store the handle even if creation fails not // to try to create it again later. const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); VkShaderModule shader_module = ui::vulkan::util::CreateShaderModule( provider, reinterpret_cast(shader_code.data()), sizeof(uint32_t) * shader_code.size()); if (shader_module == VK_NULL_HANDLE) { XELOGE( "VulkanPipelineCache: Failed to create the primitive type geometry " "shader 0x{:08X}", key.key); } geometry_shaders_.emplace(key, shader_module); return shader_module; } bool VulkanPipelineCache::EnsurePipelineCreated( const PipelineCreationArguments& creation_arguments) { if (creation_arguments.pipeline->second.pipeline != VK_NULL_HANDLE) { return true; } // This function preferably should validate the description to prevent // unsupported behavior that may be dangerous/crashing because pipelines can // be created from the disk storage. if (creation_arguments.pixel_shader) { XELOGGPU("Creating graphics pipeline state with VS {:016X}, PS {:016X}", creation_arguments.vertex_shader->shader().ucode_data_hash(), creation_arguments.pixel_shader->shader().ucode_data_hash()); } else { XELOGGPU("Creating graphics pipeline state with VS {:016X}", creation_arguments.vertex_shader->shader().ucode_data_hash()); } const PipelineDescription& description = creation_arguments.pipeline->first; if (!ArePipelineRequirementsMet(description)) { assert_always( "When creating a new pipeline, the description must not require " "unsupported features, and when loading the pipeline storage, " "pipelines with unsupported features must be filtered out"); return false; } const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const VkPhysicalDeviceFeatures& device_features = provider.device_features(); std::array shader_stages; uint32_t shader_stage_count = 0; // Vertex 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; // Geometry shader. if (creation_arguments.geometry_shader != VK_NULL_HANDLE) { VkPipelineShaderStageCreateInfo& shader_stage_geometry = shader_stages[shader_stage_count++]; shader_stage_geometry.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stage_geometry.pNext = nullptr; shader_stage_geometry.flags = 0; shader_stage_geometry.stage = VK_SHADER_STAGE_GEOMETRY_BIT; shader_stage_geometry.module = creation_arguments.geometry_shader; shader_stage_geometry.pName = "main"; shader_stage_geometry.pSpecializationInfo = nullptr; } // Pixel shader. if (creation_arguments.pixel_shader) { assert_true(creation_arguments.pixel_shader->is_translated()); if (!creation_arguments.pixel_shader->is_valid()) { return false; } VkPipelineShaderStageCreateInfo& shader_stage_fragment = shader_stages[shader_stage_count++]; shader_stage_fragment.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stage_fragment.pNext = nullptr; shader_stage_fragment.flags = 0; shader_stage_fragment.stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stage_fragment.module = creation_arguments.pixel_shader->shader_module(); assert_true(shader_stage_fragment.module != VK_NULL_HANDLE); shader_stage_fragment.pName = "main"; shader_stage_fragment.pSpecializationInfo = nullptr; } VkPipelineVertexInputStateCreateInfo vertex_input_state = {}; vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; VkPipelineInputAssemblyStateCreateInfo input_assembly_state; input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; input_assembly_state.pNext = nullptr; input_assembly_state.flags = 0; switch (description.primitive_topology) { case PipelinePrimitiveTopology::kPointList: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; assert_false(description.primitive_restart); if (description.primitive_restart) { return false; } break; case PipelinePrimitiveTopology::kLineList: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; assert_false(description.primitive_restart); if (description.primitive_restart) { return false; } break; case PipelinePrimitiveTopology::kLineStrip: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; break; case PipelinePrimitiveTopology::kTriangleList: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; assert_false(description.primitive_restart); if (description.primitive_restart) { return false; } break; case PipelinePrimitiveTopology::kTriangleStrip: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; break; case PipelinePrimitiveTopology::kTriangleFan: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; break; case PipelinePrimitiveTopology::kLineListWithAdjacency: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY; assert_false(description.primitive_restart); if (description.primitive_restart) { return false; } break; case PipelinePrimitiveTopology::kPatchList: input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; assert_false(description.primitive_restart); if (description.primitive_restart) { return false; } break; default: assert_unhandled_case(description.primitive_topology); return false; } input_assembly_state.primitiveRestartEnable = description.primitive_restart ? VK_TRUE : VK_FALSE; VkPipelineViewportStateCreateInfo viewport_state; viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewport_state.pNext = nullptr; viewport_state.flags = 0; viewport_state.viewportCount = 1; viewport_state.pViewports = nullptr; viewport_state.scissorCount = 1; viewport_state.pScissors = nullptr; VkPipelineRasterizationStateCreateInfo rasterization_state = {}; rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterization_state.depthClampEnable = description.depth_clamp_enable ? VK_TRUE : VK_FALSE; switch (description.polygon_mode) { case PipelinePolygonMode::kFill: rasterization_state.polygonMode = VK_POLYGON_MODE_FILL; break; case PipelinePolygonMode::kLine: rasterization_state.polygonMode = VK_POLYGON_MODE_LINE; break; case PipelinePolygonMode::kPoint: rasterization_state.polygonMode = VK_POLYGON_MODE_POINT; break; default: assert_unhandled_case(description.polygon_mode); return false; } rasterization_state.cullMode = VK_CULL_MODE_NONE; if (description.cull_front) { rasterization_state.cullMode |= VK_CULL_MODE_FRONT_BIT; } if (description.cull_back) { rasterization_state.cullMode |= VK_CULL_MODE_BACK_BIT; } rasterization_state.frontFace = description.front_face_clockwise ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; // Depth bias is dynamic (even toggling - pipeline creation is expensive). // "If no depth attachment is present, r is undefined" in the depth bias // formula, though Z has no effect on anything if a depth attachment is not // used (the guest shader can't access Z), enabling only when there's a // depth / stencil attachment for correctness. // TODO(Triang3l): Disable the depth bias for the fragment shader interlock RB // implementation. rasterization_state.depthBiasEnable = (description.render_pass_key.depth_and_color_used & 0b1) ? VK_TRUE : VK_FALSE; // TODO(Triang3l): Wide lines. rasterization_state.lineWidth = 1.0f; VkSampleMask sample_mask = UINT32_MAX; VkPipelineMultisampleStateCreateInfo multisample_state = {}; multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; if (description.render_pass_key.msaa_samples == xenos::MsaaSamples::k2X && !render_target_cache_.IsMsaa2xSupported( description.render_pass_key.depth_and_color_used != 0)) { // Using sample 0 as 0 and 3 as 1 for 2x instead (not exactly the same // sample locations, but still top-left and bottom-right - however, this can // be adjusted with custom sample locations). multisample_state.rasterizationSamples = VK_SAMPLE_COUNT_4_BIT; sample_mask = 0b1001; // TODO(Triang3l): Research sample mask behavior without attachments (in // Direct3D, it's completely ignored in this case). multisample_state.pSampleMask = &sample_mask; } else { multisample_state.rasterizationSamples = VkSampleCountFlagBits( uint32_t(1) << uint32_t(description.render_pass_key.msaa_samples)); } VkPipelineDepthStencilStateCreateInfo depth_stencil_state = {}; depth_stencil_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depth_stencil_state.pNext = nullptr; if (description.depth_write_enable || description.depth_compare_op != xenos::CompareFunction::kAlways) { depth_stencil_state.depthTestEnable = VK_TRUE; depth_stencil_state.depthWriteEnable = description.depth_write_enable ? VK_TRUE : VK_FALSE; depth_stencil_state.depthCompareOp = VkCompareOp( uint32_t(VK_COMPARE_OP_NEVER) + uint32_t(description.depth_compare_op)); } if (description.stencil_test_enable) { depth_stencil_state.stencilTestEnable = VK_TRUE; depth_stencil_state.front.failOp = VkStencilOp(uint32_t(VK_STENCIL_OP_KEEP) + uint32_t(description.stencil_front_fail_op)); depth_stencil_state.front.passOp = VkStencilOp(uint32_t(VK_STENCIL_OP_KEEP) + uint32_t(description.stencil_front_pass_op)); depth_stencil_state.front.depthFailOp = VkStencilOp(uint32_t(VK_STENCIL_OP_KEEP) + uint32_t(description.stencil_front_depth_fail_op)); depth_stencil_state.front.compareOp = VkCompareOp(uint32_t(VK_COMPARE_OP_NEVER) + uint32_t(description.stencil_front_compare_op)); depth_stencil_state.back.failOp = VkStencilOp(uint32_t(VK_STENCIL_OP_KEEP) + uint32_t(description.stencil_back_fail_op)); depth_stencil_state.back.passOp = VkStencilOp(uint32_t(VK_STENCIL_OP_KEEP) + uint32_t(description.stencil_back_pass_op)); depth_stencil_state.back.depthFailOp = VkStencilOp(uint32_t(VK_STENCIL_OP_KEEP) + uint32_t(description.stencil_back_depth_fail_op)); depth_stencil_state.back.compareOp = VkCompareOp(uint32_t(VK_COMPARE_OP_NEVER) + uint32_t(description.stencil_back_compare_op)); } VkPipelineColorBlendAttachmentState color_blend_attachments[xenos::kMaxColorRenderTargets] = {}; uint32_t color_rts_used = description.render_pass_key.depth_and_color_used >> 1; { static const VkBlendFactor kBlendFactorMap[] = { VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR, VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_DST_COLOR, VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, VK_BLEND_FACTOR_CONSTANT_COLOR, VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, VK_BLEND_FACTOR_CONSTANT_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA, VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, }; // 8 entries for safety since 3 bits from the guest are passed directly. static const VkBlendOp kBlendOpMap[] = {VK_BLEND_OP_ADD, VK_BLEND_OP_SUBTRACT, VK_BLEND_OP_MIN, VK_BLEND_OP_MAX, VK_BLEND_OP_REVERSE_SUBTRACT, VK_BLEND_OP_ADD, VK_BLEND_OP_ADD, VK_BLEND_OP_ADD}; uint32_t color_rts_remaining = color_rts_used; uint32_t color_rt_index; while (xe::bit_scan_forward(color_rts_remaining, &color_rt_index)) { color_rts_remaining &= ~(uint32_t(1) << color_rt_index); VkPipelineColorBlendAttachmentState& color_blend_attachment = color_blend_attachments[color_rt_index]; const PipelineRenderTarget& color_rt = description.render_targets[color_rt_index]; if (color_rt.src_color_blend_factor != PipelineBlendFactor::kOne || color_rt.dst_color_blend_factor != PipelineBlendFactor::kZero || color_rt.color_blend_op != xenos::BlendOp::kAdd || color_rt.src_alpha_blend_factor != PipelineBlendFactor::kOne || color_rt.dst_alpha_blend_factor != PipelineBlendFactor::kZero || color_rt.alpha_blend_op != xenos::BlendOp::kAdd) { color_blend_attachment.blendEnable = VK_TRUE; color_blend_attachment.srcColorBlendFactor = kBlendFactorMap[uint32_t(color_rt.src_color_blend_factor)]; color_blend_attachment.dstColorBlendFactor = kBlendFactorMap[uint32_t(color_rt.dst_color_blend_factor)]; color_blend_attachment.colorBlendOp = kBlendOpMap[uint32_t(color_rt.color_blend_op)]; color_blend_attachment.srcAlphaBlendFactor = kBlendFactorMap[uint32_t(color_rt.src_alpha_blend_factor)]; color_blend_attachment.dstAlphaBlendFactor = kBlendFactorMap[uint32_t(color_rt.dst_alpha_blend_factor)]; color_blend_attachment.alphaBlendOp = kBlendOpMap[uint32_t(color_rt.alpha_blend_op)]; } color_blend_attachment.colorWriteMask = VkColorComponentFlags(color_rt.color_write_mask); if (!device_features.independentBlend) { // For non-independent blend, the pAttachments element for the first // actually used color will be replicated into all. break; } } } VkPipelineColorBlendStateCreateInfo color_blend_state = {}; color_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; color_blend_state.attachmentCount = 32 - xe::lzcnt(color_rts_used); color_blend_state.pAttachments = color_blend_attachments; if (color_rts_used && !device_features.independentBlend) { // "If the independent blending feature is not enabled, all elements of // pAttachments must be identical." uint32_t first_color_rt_index; xe::bit_scan_forward(color_rts_used, &first_color_rt_index); for (uint32_t i = 0; i < color_blend_state.attachmentCount; ++i) { if (i == first_color_rt_index) { continue; } color_blend_attachments[i] = color_blend_attachments[first_color_rt_index]; } } std::array dynamic_states; VkPipelineDynamicStateCreateInfo dynamic_state; dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamic_state.pNext = nullptr; dynamic_state.flags = 0; dynamic_state.dynamicStateCount = 0; dynamic_state.pDynamicStates = dynamic_states.data(); // Regardless of whether some of this state actually has any effect on the // pipeline, marking all as dynamic because otherwise, binding any pipeline // with such state not marked as dynamic will cause the dynamic state to be // invalidated (again, even if it has no effect). dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT; dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BIAS; dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_BLEND_CONSTANTS; dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK; dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_WRITE_MASK; dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; VkGraphicsPipelineCreateInfo pipeline_create_info; pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipeline_create_info.pNext = nullptr; pipeline_create_info.flags = 0; pipeline_create_info.stageCount = shader_stage_count; 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.pViewportState = &viewport_state; pipeline_create_info.pRasterizationState = &rasterization_state; pipeline_create_info.pMultisampleState = &multisample_state; pipeline_create_info.pDepthStencilState = &depth_stencil_state; pipeline_create_info.pColorBlendState = &color_blend_state; pipeline_create_info.pDynamicState = &dynamic_state; pipeline_create_info.layout = creation_arguments.pipeline->second.pipeline_layout->GetPipelineLayout(); pipeline_create_info.renderPass = creation_arguments.render_pass; pipeline_create_info.subpass = 0; pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE; pipeline_create_info.basePipelineIndex = -1; const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); VkDevice device = provider.device(); VkPipeline pipeline; if (dfn.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_create_info, nullptr, &pipeline) != VK_SUCCESS) { // TODO(Triang3l): Move these error messages outside. /* if (creation_arguments.pixel_shader) { XELOGE( "Failed to create graphics pipeline with VS {:016X}, PS {:016X}", creation_arguments.vertex_shader->shader().ucode_data_hash(), creation_arguments.pixel_shader->shader().ucode_data_hash()); } else { XELOGE("Failed to create graphics pipeline with VS {:016X}", creation_arguments.vertex_shader->shader().ucode_data_hash()); } */ return false; } creation_arguments.pipeline->second.pipeline = pipeline; return true; } } // namespace vulkan } // namespace gpu } // namespace xe