diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 73e450d8f..9700d6314 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -177,14 +177,17 @@ void D3D12CommandProcessor::SubmitBarriers() { } ID3D12RootSignature* D3D12CommandProcessor::GetRootSignature( - const D3D12Shader* vertex_shader, const D3D12Shader* pixel_shader, - bool tessellated) { + const D3D12Shader* vertex_shader, const D3D12Shader* pixel_shader) { assert_true(vertex_shader->is_translated()); assert_true(pixel_shader == nullptr || pixel_shader->is_translated()); - D3D12_SHADER_VISIBILITY vertex_visibility = - tessellated ? D3D12_SHADER_VISIBILITY_DOMAIN - : D3D12_SHADER_VISIBILITY_VERTEX; + D3D12_SHADER_VISIBILITY vertex_visibility; + if (vertex_shader->host_vertex_shader_type() != + Shader::HostVertexShaderType::kVertex) { + vertex_visibility = D3D12_SHADER_VISIBILITY_DOMAIN; + } else { + vertex_visibility = D3D12_SHADER_VISIBILITY_VERTEX; + } uint32_t texture_count_vertex, sampler_count_vertex; vertex_shader->GetTextureSRVs(texture_count_vertex); @@ -207,7 +210,8 @@ ID3D12RootSignature* D3D12CommandProcessor::GetRootSignature( index_offset += D3D12Shader::kMaxTextureSRVIndexBits; index |= sampler_count_vertex << index_offset; index_offset += D3D12Shader::kMaxSamplerBindingIndexBits; - index |= (tessellated ? 1 : 0) << index_offset; + index |= uint32_t(vertex_visibility == D3D12_SHADER_VISIBILITY_DOMAIN) + << index_offset; ++index_offset; assert_true(index_offset <= 32); @@ -1284,15 +1288,6 @@ bool D3D12CommandProcessor::IssueDraw(PrimitiveType primitive_type, return true; } - // Check if using tessellation to get the correct primitive type. - bool tessellated; - if (major_mode_explicit) { - tessellated = regs.Get().path_select == - xenos::VGTOutputPath::kTessellationEnable; - } else { - tessellated = false; - } - // Shaders will have already been defined by previous loads. // We need them to do just about anything so validate here. auto vertex_shader = static_cast(active_vertex_shader()); @@ -1308,13 +1303,21 @@ bool D3D12CommandProcessor::IssueDraw(PrimitiveType primitive_type, // Need a pixel shader in normal color mode. return false; } + // Get tessellation info for the current draw for vertex shader translation. + Shader::HostVertexShaderType host_vertex_shader_type = + pipeline_cache_->GetHostVertexShaderTypeIfValid(); + if (host_vertex_shader_type == Shader::HostVertexShaderType(-1)) { + return false; + } // Translate the shaders now to get memexport configuration and color mask, // which is needed by the render target cache, to check the possibility of // doing early depth/stencil, and also to get used textures and samplers. if (!pipeline_cache_->EnsureShadersTranslated(vertex_shader, pixel_shader, - tessellated, primitive_type)) { + host_vertex_shader_type)) { return false; } + bool tessellated = + host_vertex_shader_type != Shader::HostVertexShaderType::kVertex; // Check if memexport is used. If it is, we can't skip draw calls that have no // visual effect. @@ -1348,39 +1351,24 @@ bool D3D12CommandProcessor::IssueDraw(PrimitiveType primitive_type, // Set up primitive topology. bool indexed = index_buffer_info != nullptr && index_buffer_info->guest_base; + bool adaptive_tessellation = + host_vertex_shader_type == + Shader::HostVertexShaderType::kLineDomainAdaptive || + host_vertex_shader_type == + Shader::HostVertexShaderType::kTriangleDomainAdaptive || + host_vertex_shader_type == + Shader::HostVertexShaderType::kQuadDomainAdaptive; // Adaptive tessellation requires an index buffer, but it contains per-edge // tessellation factors (as floats) instead of control point indices. - bool adaptive_tessellation; - if (tessellated) { - xenos::TessellationMode tessellation_mode = - regs.Get().tess_mode; - adaptive_tessellation = - tessellation_mode == xenos::TessellationMode::kAdaptive; - if (adaptive_tessellation && - (!indexed || index_buffer_info->format != IndexFormat::kInt32)) { - return false; - } - // TODO(Triang3l): Implement all tessellation modes if games using any other - // than adaptive are found. The biggest question about them is what is being - // passed to vertex shader registers, especially if patches are drawn with - // an index buffer. - // https://www.slideshare.net/blackdevilvikas/next-generation-graphics-programming-on-xbox-360 - if (tessellation_mode != xenos::TessellationMode::kAdaptive) { - XELOGE( - "Tessellation mode %u is not implemented yet, only adaptive is " - "partially available now - report the game to Xenia developers!", - uint32_t(tessellation_mode)); - return false; - } - } else { - adaptive_tessellation = false; - } + assert_true(!adaptive_tessellation || + (indexed && index_buffer_info->format == IndexFormat::kInt32)); PrimitiveType primitive_type_converted; D3D_PRIMITIVE_TOPOLOGY primitive_topology; if (tessellated) { primitive_type_converted = primitive_type; switch (primitive_type_converted) { - // TODO(Triang3l): Support line patches. + // TODO(Triang3l): Support all kinds of patches if found in games. + case PrimitiveType::kTriangleList: case PrimitiveType::kTrianglePatch: primitive_topology = D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST; break; @@ -1422,7 +1410,7 @@ bool D3D12CommandProcessor::IssueDraw(PrimitiveType primitive_type, deferred_command_list_->D3DIASetPrimitiveTopology(primitive_topology); } uint32_t line_loop_closing_index; - if (!tessellated && primitive_type == PrimitiveType::kLineLoop && !indexed && + if (primitive_type == PrimitiveType::kLineLoop && !indexed && index_count >= 3) { // Add a vertex to close the loop, and make the vertex shader replace its // index (before adding the offset) with 0 to fetch the first vertex again. @@ -1455,7 +1443,7 @@ bool D3D12CommandProcessor::IssueDraw(PrimitiveType primitive_type, void* pipeline_handle; ID3D12RootSignature* root_signature; if (!pipeline_cache_->ConfigurePipeline( - vertex_shader, pixel_shader, tessellated, primitive_type_converted, + vertex_shader, pixel_shader, primitive_type_converted, indexed ? index_buffer_info->format : IndexFormat::kInt16, early_z, pipeline_render_targets, &pipeline_handle, &root_signature)) { return false; diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.h b/src/xenia/gpu/d3d12/d3d12_command_processor.h index a0dc68abe..717dce6ef 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.h +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.h @@ -95,8 +95,7 @@ class D3D12CommandProcessor : public CommandProcessor { // Finds or creates root signature for a pipeline. ID3D12RootSignature* GetRootSignature(const D3D12Shader* vertex_shader, - const D3D12Shader* pixel_shader, - bool tessellated); + const D3D12Shader* pixel_shader); ui::d3d12::UploadBufferPool* GetConstantBufferPool() const { return constant_buffer_pool_.get(); diff --git a/src/xenia/gpu/d3d12/pipeline_cache.cc b/src/xenia/gpu/d3d12/pipeline_cache.cc index 13097e3df..37d9fac89 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.cc +++ b/src/xenia/gpu/d3d12/pipeline_cache.cc @@ -272,9 +272,10 @@ void PipelineCache::InitializeShaderStorage(const std::wstring& storage_root, break; } assert_not_null(shader_to_translate.second); - if (!TranslateShader(translator, shader_to_translate.second, - shader_to_translate.first.sq_program_cntl, - shader_to_translate.first.patch_primitive_type)) { + if (!TranslateShader( + translator, shader_to_translate.second, + shader_to_translate.first.sq_program_cntl, + shader_to_translate.first.host_vertex_shader_type)) { std::unique_lock lock(shaders_failed_to_translate_mutex); shaders_failed_to_translate.push_back(shader_to_translate.second); } @@ -516,8 +517,7 @@ void PipelineCache::InitializeShaderStorage(const std::wstring& storage_root, pipeline_runtime_description.root_signature = command_processor_->GetRootSignature( pipeline_runtime_description.vertex_shader, - pipeline_runtime_description.pixel_shader, - pipeline_description.patch_type != PipelinePatchType::kNone); + pipeline_runtime_description.pixel_shader); if (!pipeline_runtime_description.root_signature) { continue; } @@ -712,10 +712,59 @@ D3D12Shader* PipelineCache::LoadShader(ShaderType shader_type, return shader; } -bool PipelineCache::EnsureShadersTranslated(D3D12Shader* vertex_shader, - D3D12Shader* pixel_shader, - bool tessellated, - PrimitiveType primitive_type) { +Shader::HostVertexShaderType PipelineCache::GetHostVertexShaderTypeIfValid() + const { + auto& regs = *register_file_; + auto vgt_draw_initiator = regs.Get(); + if (!xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode, + vgt_draw_initiator.prim_type)) { + // VGT_OUTPUT_PATH_CNTL and HOS registers are ignored in implicit major + // mode. + return Shader::HostVertexShaderType::kVertex; + } + if (regs.Get().path_select != + xenos::VGTOutputPath::kTessellationEnable) { + return Shader::HostVertexShaderType::kVertex; + } + xenos::TessellationMode tessellation_mode = + regs.Get().tess_mode; + switch (vgt_draw_initiator.prim_type) { + // case PrimitiveType::kTriangleList: + // switch (tessellation_mode) { + // case xenos::TessellationMode::kDiscrete: + // // Call of Duty 3 - green terrain in the first mission. + // case xenos::TessellationMode::kContinuous: + // // Viva Pinata - something on the start screen. + // return Shader::HostVertexShaderType::kTriangleDomainConstant; + // } + // break; + // TODO(Triang3l): Support non-adaptive tessellation. + case PrimitiveType::kTrianglePatch: + if (tessellation_mode == xenos::TessellationMode::kAdaptive) { + // Banjo-Kazooie: Nuts & Bolts - water. + // Halo 3 - water. + return Shader::HostVertexShaderType::kTriangleDomainAdaptive; + } + break; + case PrimitiveType::kQuadPatch: + if (tessellation_mode == xenos::TessellationMode::kAdaptive) { + // Viva Pinata - something on the start screen. + return Shader::HostVertexShaderType::kQuadDomainAdaptive; + } + break; + // TODO(Triang3l): Support line patches and non-adaptive quad + // tessellation. + } + XELOGE( + "Unsupported tessellation mode %u for primitive type %u. Report the game " + "to Xenia developers!", + uint32_t(tessellation_mode), uint32_t(vgt_draw_initiator.prim_type)); + return Shader::HostVertexShaderType(-1); +} + +bool PipelineCache::EnsureShadersTranslated( + D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, + Shader::HostVertexShaderType host_vertex_shader_type) { auto& regs = *register_file_; // These are the constant base addresses/ranges for shaders. @@ -734,12 +783,9 @@ bool PipelineCache::EnsureShadersTranslated(D3D12Shader* vertex_shader, xenos::VertexShaderExportMode::kPosition2VectorsEdgeKill); assert_false(sq_program_cntl.gen_index_vtx); - PrimitiveType patch_primitive_type = - tessellated ? primitive_type : PrimitiveType::kNone; - if (!vertex_shader->is_translated()) { if (!TranslateShader(*shader_translator_, vertex_shader, sq_program_cntl, - patch_primitive_type)) { + host_vertex_shader_type)) { XELOGE("Failed to translate the vertex shader!"); return false; } @@ -756,8 +802,7 @@ bool PipelineCache::EnsureShadersTranslated(D3D12Shader* vertex_shader, } if (pixel_shader != nullptr && !pixel_shader->is_translated()) { - if (!TranslateShader(*shader_translator_, pixel_shader, sq_program_cntl, - patch_primitive_type)) { + if (!TranslateShader(*shader_translator_, pixel_shader, sq_program_cntl)) { XELOGE("Failed to translate the pixel shader!"); return false; } @@ -777,7 +822,7 @@ bool PipelineCache::EnsureShadersTranslated(D3D12Shader* vertex_shader, } bool PipelineCache::ConfigurePipeline( - D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, bool tessellated, + D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, PrimitiveType primitive_type, IndexFormat index_format, bool early_z, const RenderTargetCache::PipelineRenderTarget render_targets[5], void** pipeline_state_handle_out, @@ -790,9 +835,9 @@ bool PipelineCache::ConfigurePipeline( assert_not_null(root_signature_out); PipelineRuntimeDescription runtime_description; - if (!GetCurrentStateDescription(vertex_shader, pixel_shader, tessellated, - primitive_type, index_format, early_z, - render_targets, runtime_description)) { + if (!GetCurrentStateDescription(vertex_shader, pixel_shader, primitive_type, + index_format, early_z, render_targets, + runtime_description)) { return false; } PipelineDescription& description = runtime_description.description; @@ -819,8 +864,9 @@ bool PipelineCache::ConfigurePipeline( } } - if (!EnsureShadersTranslated(vertex_shader, pixel_shader, tessellated, - primitive_type)) { + if (!EnsureShadersTranslated( + vertex_shader, pixel_shader, + Shader::HostVertexShaderType(description.host_vertex_shader_type))) { return false; } @@ -864,13 +910,13 @@ bool PipelineCache::ConfigurePipeline( return true; } -bool PipelineCache::TranslateShader(DxbcShaderTranslator& translator, - D3D12Shader* shader, - reg::SQ_PROGRAM_CNTL cntl, - PrimitiveType patch_primitive_type) { +bool PipelineCache::TranslateShader( + DxbcShaderTranslator& translator, D3D12Shader* shader, + reg::SQ_PROGRAM_CNTL cntl, + Shader::HostVertexShaderType host_vertex_shader_type) { // Perform translation. // If this fails the shader will be marked as invalid and ignored later. - if (!translator.Translate(shader, patch_primitive_type, cntl)) { + if (!translator.Translate(shader, cntl, host_vertex_shader_type)) { XELOGE("Shader %.16" PRIX64 " translation failed; marking as ignored", shader->ucode_data_hash()); return false; @@ -923,7 +969,7 @@ bool PipelineCache::TranslateShader(DxbcShaderTranslator& translator, } bool PipelineCache::GetCurrentStateDescription( - D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, bool tessellated, + D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, PrimitiveType primitive_type, IndexFormat index_format, bool early_z, const RenderTargetCache::PipelineRenderTarget render_targets[5], PipelineRuntimeDescription& runtime_description_out) { @@ -931,14 +977,13 @@ bool PipelineCache::GetCurrentStateDescription( auto& regs = *register_file_; auto pa_su_sc_mode_cntl = regs.Get(); - bool primitive_two_faced = IsPrimitiveTwoFaced(tessellated, primitive_type); // Initialize all unused fields to zero for comparison/hashing. std::memset(&runtime_description_out, 0, sizeof(runtime_description_out)); // Root signature. - runtime_description_out.root_signature = command_processor_->GetRootSignature( - vertex_shader, pixel_shader, tessellated); + runtime_description_out.root_signature = + command_processor_->GetRootSignature(vertex_shader, pixel_shader); if (runtime_description_out.root_signature == nullptr) { return false; } @@ -962,48 +1007,18 @@ bool PipelineCache::GetCurrentStateDescription( description_out.strip_cut_index = PipelineStripCutIndex::kNone; } - // Primitive topology type, tessellation mode and geometry shader. - if (tessellated) { - xenos::TessellationMode tessellation_mode = - regs.Get().tess_mode; - switch (tessellation_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: - assert_unhandled_case(tessellation_mode); - return false; - } - description_out.primitive_topology_type = - PipelinePrimitiveTopologyType::kPatch; - switch (primitive_type) { - case PrimitiveType::kLinePatch: - description_out.patch_type = PipelinePatchType::kLine; - break; - case PrimitiveType::kTrianglePatch: - description_out.patch_type = PipelinePatchType::kTriangle; - break; - case PrimitiveType::kQuadPatch: - description_out.patch_type = PipelinePatchType::kQuad; - break; - default: - assert_unhandled_case(primitive_type); - return false; - } - description_out.geometry_shader = PipelineGeometryShader::kNone; - } else { - description_out.tessellation_mode = PipelineTessellationMode::kNone; + // Host vertex shader type and primitive topology. + Shader::HostVertexShaderType host_vertex_shader_type = + GetHostVertexShaderTypeIfValid(); + if (host_vertex_shader_type == Shader::HostVertexShaderType(-1)) { + return false; + } + description_out.host_vertex_shader_type = host_vertex_shader_type; + if (host_vertex_shader_type == Shader::HostVertexShaderType::kVertex) { switch (primitive_type) { case PrimitiveType::kPointList: - description_out.primitive_topology_type = - PipelinePrimitiveTopologyType::kPoint; + description_out.primitive_topology_type_or_tessellation_mode = + uint32_t(PipelinePrimitiveTopologyType::kPoint); break; case PrimitiveType::kLineList: case PrimitiveType::kLineStrip: @@ -1011,15 +1026,14 @@ bool PipelineCache::GetCurrentStateDescription( // Quads are emulated as line lists with adjacency. case PrimitiveType::kQuadList: case PrimitiveType::k2DLineStrip: - description_out.primitive_topology_type = - PipelinePrimitiveTopologyType::kLine; + description_out.primitive_topology_type_or_tessellation_mode = + uint32_t(PipelinePrimitiveTopologyType::kLine); break; default: - description_out.primitive_topology_type = - PipelinePrimitiveTopologyType::kTriangle; + description_out.primitive_topology_type_or_tessellation_mode = + uint32_t(PipelinePrimitiveTopologyType::kTriangle); break; } - description_out.patch_type = PipelinePatchType::kNone; switch (primitive_type) { case PrimitiveType::kPointList: description_out.geometry_shader = PipelineGeometryShader::kPointList; @@ -1035,8 +1049,15 @@ bool PipelineCache::GetCurrentStateDescription( description_out.geometry_shader = PipelineGeometryShader::kNone; break; } + } else { + description_out.primitive_topology_type_or_tessellation_mode = + uint32_t(regs.Get().tess_mode); } + bool primitive_two_faced = IsPrimitiveTwoFaced( + host_vertex_shader_type != Shader::HostVertexShaderType::kVertex, + primitive_type); + // Rasterizer state. // Because Direct3D 12 doesn't support per-side fill mode and depth bias, the // values to use depends on the current culling state. @@ -1139,9 +1160,8 @@ bool PipelineCache::GetCurrentStateDescription( description_out.depth_bias_slope_scaled = poly_offset_scale * (1.0f / 16.0f); } - if (cvars::d3d12_tessellation_wireframe && tessellated && - (primitive_type == PrimitiveType::kTrianglePatch || - primitive_type == PrimitiveType::kQuadPatch)) { + if (cvars::d3d12_tessellation_wireframe && + host_vertex_shader_type != Shader::HostVertexShaderType::kVertex) { description_out.fill_mode_wireframe = 1; } description_out.depth_clip = !regs.Get().clip_disable; @@ -1333,130 +1353,119 @@ ID3D12PipelineState* PipelineCache::CreateD3D12PipelineState( break; } - // Vertex or hull/domain shaders. + // Primitive topology, vertex, hull/domain and geometry shaders. if (!runtime_description.vertex_shader->is_translated()) { XELOGE("Vertex shader %.16" PRIX64 " not translated", runtime_description.vertex_shader->ucode_data_hash()); assert_always(); return nullptr; } - if (description.tessellation_mode != PipelineTessellationMode::kNone) { - state_desc.VS.pShaderBytecode = tessellation_vs; - state_desc.VS.BytecodeLength = sizeof(tessellation_vs); - switch (description.patch_type) { - case PipelinePatchType::kTriangle: - if (runtime_description.vertex_shader->patch_primitive_type() != - PrimitiveType::kTrianglePatch) { - XELOGE( - "Tried to use vertex shader %.16" PRIX64 - " for triangle patch tessellation, but it's not a tessellation " - "domain shader or has the wrong domain", - runtime_description.vertex_shader->ucode_data_hash()); - assert_always(); - return nullptr; - } - switch (description.tessellation_mode) { - case PipelineTessellationMode::kDiscrete: - state_desc.HS.pShaderBytecode = discrete_triangle_hs; - state_desc.HS.BytecodeLength = sizeof(discrete_triangle_hs); - break; - case PipelineTessellationMode::kContinuous: - state_desc.HS.pShaderBytecode = continuous_triangle_hs; - state_desc.HS.BytecodeLength = sizeof(continuous_triangle_hs); - break; - case PipelineTessellationMode::kAdaptive: - state_desc.HS.pShaderBytecode = adaptive_triangle_hs; - state_desc.HS.BytecodeLength = sizeof(adaptive_triangle_hs); - break; - default: - assert_unhandled_case(description.tessellation_mode); - return nullptr; - } - break; - case PipelinePatchType::kQuad: - if (runtime_description.vertex_shader->patch_primitive_type() != - PrimitiveType::kQuadPatch) { - XELOGE("Tried to use vertex shader %.16" PRIX64 - " for quad patch tessellation, but it's not a tessellation " - "domain shader or has the wrong domain", - runtime_description.vertex_shader->ucode_data_hash()); - assert_always(); - return nullptr; - } - switch (description.tessellation_mode) { - case PipelineTessellationMode::kDiscrete: - state_desc.HS.pShaderBytecode = discrete_quad_hs; - state_desc.HS.BytecodeLength = sizeof(discrete_quad_hs); - break; - case PipelineTessellationMode::kContinuous: - state_desc.HS.pShaderBytecode = continuous_quad_hs; - state_desc.HS.BytecodeLength = sizeof(continuous_quad_hs); - break; - // TODO(Triang3l): True adaptive tessellation when properly tested. - default: - assert_unhandled_case(description.tessellation_mode); - return nullptr; - } - break; - default: - assert_unhandled_case(description.patch_type); - return nullptr; - } - // The Xenos vertex shader works like a domain shader with tessellation. - state_desc.DS.pShaderBytecode = - runtime_description.vertex_shader->translated_binary().data(); - state_desc.DS.BytecodeLength = - runtime_description.vertex_shader->translated_binary().size(); - } else { - if (runtime_description.vertex_shader->patch_primitive_type() != - PrimitiveType::kNone) { - XELOGE("Tried to use vertex shader %.16" PRIX64 - " without tessellation, but it's a tessellation domain shader", - runtime_description.vertex_shader->ucode_data_hash()); - assert_always(); - return nullptr; - } + Shader::HostVertexShaderType host_vertex_shader_type = + description.host_vertex_shader_type; + if (runtime_description.vertex_shader->host_vertex_shader_type() != + host_vertex_shader_type) { + XELOGE("Vertex shader %.16" PRIX64 + " translated into the wrong host shader " + "type", + runtime_description.vertex_shader->ucode_data_hash()); + assert_always(); + return nullptr; + } + if (host_vertex_shader_type == Shader::HostVertexShaderType::kVertex) { state_desc.VS.pShaderBytecode = runtime_description.vertex_shader->translated_binary().data(); state_desc.VS.BytecodeLength = runtime_description.vertex_shader->translated_binary().size(); - } - - // Pre-GS primitive topology type. - switch (description.primitive_topology_type) { - case PipelinePrimitiveTopologyType::kPoint: - state_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; - break; - case PipelinePrimitiveTopologyType::kLine: - state_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; - break; - case PipelinePrimitiveTopologyType::kTriangle: - state_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - break; - case PipelinePrimitiveTopologyType::kPatch: - state_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH; - break; - default: - assert_unhandled_case(description.primitive_topology_type); - return nullptr; - } - - // Geometry shader. - switch (description.geometry_shader) { - case PipelineGeometryShader::kPointList: - state_desc.GS.pShaderBytecode = primitive_point_list_gs; - state_desc.GS.BytecodeLength = sizeof(primitive_point_list_gs); - break; - case PipelineGeometryShader::kRectangleList: - state_desc.GS.pShaderBytecode = primitive_rectangle_list_gs; - state_desc.GS.BytecodeLength = sizeof(primitive_rectangle_list_gs); - break; - case PipelineGeometryShader::kQuadList: - state_desc.GS.pShaderBytecode = primitive_quad_list_gs; - state_desc.GS.BytecodeLength = sizeof(primitive_quad_list_gs); - break; - default: - break; + PipelinePrimitiveTopologyType primitive_topology_type = + PipelinePrimitiveTopologyType( + description.primitive_topology_type_or_tessellation_mode); + switch (primitive_topology_type) { + case PipelinePrimitiveTopologyType::kPoint: + state_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; + break; + case PipelinePrimitiveTopologyType::kLine: + state_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; + break; + case PipelinePrimitiveTopologyType::kTriangle: + state_desc.PrimitiveTopologyType = + D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + break; + default: + assert_unhandled_case(primitive_topology_type); + return nullptr; + } + switch (description.geometry_shader) { + case PipelineGeometryShader::kPointList: + state_desc.GS.pShaderBytecode = primitive_point_list_gs; + state_desc.GS.BytecodeLength = sizeof(primitive_point_list_gs); + break; + case PipelineGeometryShader::kRectangleList: + state_desc.GS.pShaderBytecode = primitive_rectangle_list_gs; + state_desc.GS.BytecodeLength = sizeof(primitive_rectangle_list_gs); + break; + case PipelineGeometryShader::kQuadList: + state_desc.GS.pShaderBytecode = primitive_quad_list_gs; + state_desc.GS.BytecodeLength = sizeof(primitive_quad_list_gs); + break; + default: + break; + } + } else { + state_desc.VS.pShaderBytecode = tessellation_vs; + state_desc.VS.BytecodeLength = sizeof(tessellation_vs); + state_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH; + xenos::TessellationMode tessellation_mode = xenos::TessellationMode( + description.primitive_topology_type_or_tessellation_mode); + switch (tessellation_mode) { + case xenos::TessellationMode::kDiscrete: + switch (host_vertex_shader_type) { + case Shader::HostVertexShaderType::kTriangleDomainConstant: + state_desc.HS.pShaderBytecode = discrete_triangle_hs; + state_desc.HS.BytecodeLength = sizeof(discrete_triangle_hs); + break; + case Shader::HostVertexShaderType::kQuadDomainConstant: + state_desc.HS.pShaderBytecode = discrete_quad_hs; + state_desc.HS.BytecodeLength = sizeof(discrete_quad_hs); + break; + default: + assert_unhandled_case(host_vertex_shader_type); + return nullptr; + } + break; + case xenos::TessellationMode::kContinuous: + switch (host_vertex_shader_type) { + case Shader::HostVertexShaderType::kTriangleDomainConstant: + state_desc.HS.pShaderBytecode = continuous_triangle_hs; + state_desc.HS.BytecodeLength = sizeof(continuous_triangle_hs); + break; + case Shader::HostVertexShaderType::kQuadDomainConstant: + state_desc.HS.pShaderBytecode = continuous_quad_hs; + state_desc.HS.BytecodeLength = sizeof(continuous_quad_hs); + break; + default: + assert_unhandled_case(host_vertex_shader_type); + return nullptr; + } + break; + case xenos::TessellationMode::kAdaptive: + switch (host_vertex_shader_type) { + case Shader::HostVertexShaderType::kTriangleDomainAdaptive: + state_desc.HS.pShaderBytecode = adaptive_triangle_hs; + state_desc.HS.BytecodeLength = sizeof(adaptive_triangle_hs); + break; + default: + assert_unhandled_case(host_vertex_shader_type); + return nullptr; + } + break; + default: + assert_unhandled_case(tessellation_mode); + return nullptr; + } + state_desc.DS.pShaderBytecode = + runtime_description.vertex_shader->translated_binary().data(); + state_desc.DS.BytecodeLength = + runtime_description.vertex_shader->translated_binary().size(); } // Pixel shader. @@ -1715,7 +1724,7 @@ void PipelineCache::StorageWriteThread() { shader_header.ucode_data_hash = shader->ucode_data_hash(); shader_header.ucode_dword_count = shader->ucode_dword_count(); shader_header.type = shader->type(); - shader_header.patch_primitive_type = shader->patch_primitive_type(); + shader_header.host_vertex_shader_type = shader->host_vertex_shader_type(); shader_header.sq_program_cntl = shader_pair.second; assert_not_null(shader_storage_file_); fwrite(&shader_header, sizeof(shader_header), 1, shader_storage_file_); diff --git a/src/xenia/gpu/d3d12/pipeline_cache.h b/src/xenia/gpu/d3d12/pipeline_cache.h index 7166185f7..2beee80c3 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.h +++ b/src/xenia/gpu/d3d12/pipeline_cache.h @@ -56,13 +56,17 @@ class PipelineCache { D3D12Shader* LoadShader(ShaderType shader_type, uint32_t guest_address, const uint32_t* host_address, uint32_t dword_count); + // Returns the host vertex shader type for the current draw if it's valid and + // supported, or Shader::HostVertexShaderType(-1) if not. + Shader::HostVertexShaderType GetHostVertexShaderTypeIfValid() const; + // Translates shaders if needed, also making shader info up to date. - bool EnsureShadersTranslated(D3D12Shader* vertex_shader, - D3D12Shader* pixel_shader, bool tessellated, - PrimitiveType primitive_type); + bool EnsureShadersTranslated( + D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, + Shader::HostVertexShaderType host_vertex_shader_type); bool ConfigurePipeline( - D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, bool tessellated, + D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, PrimitiveType primitive_type, IndexFormat index_format, bool early_z, const RenderTargetCache::PipelineRenderTarget render_targets[5], void** pipeline_state_handle_out, @@ -81,11 +85,11 @@ class PipelineCache { uint32_t ucode_dword_count : 16; ShaderType type : 1; - PrimitiveType patch_primitive_type : 6; + Shader::HostVertexShaderType host_vertex_shader_type : 3; reg::SQ_PROGRAM_CNTL sq_program_cntl; - static constexpr uint32_t kVersion = 0x20200301; + static constexpr uint32_t kVersion = 0x20200405; }); // Update PipelineDescription::kVersion if any of the Pipeline* enums are @@ -115,7 +119,6 @@ class PipelineCache { kPoint, kLine, kTriangle, - kPatch, }; enum class PipelineGeometryShader : uint32_t { @@ -169,21 +172,23 @@ class PipelineCache { float depth_bias_slope_scaled; PipelineStripCutIndex strip_cut_index : 2; // 2 - PipelineTessellationMode tessellation_mode : 2; // 4 - PipelinePrimitiveTopologyType primitive_topology_type : 2; // 6 - PipelinePatchType patch_type : 2; // 8 - PipelineGeometryShader geometry_shader : 2; // 10 - uint32_t fill_mode_wireframe : 1; // 11 - PipelineCullMode cull_mode : 2; // 13 - uint32_t front_counter_clockwise : 1; // 14 - uint32_t depth_clip : 1; // 15 - uint32_t rov_msaa : 1; // 16 - DepthRenderTargetFormat depth_format : 1; // 17 - CompareFunction depth_func : 3; // 20 - uint32_t depth_write : 1; // 21 - uint32_t stencil_enable : 1; // 22 - uint32_t stencil_read_mask : 8; // 30 - uint32_t force_early_z : 1; // 31 + Shader::HostVertexShaderType host_vertex_shader_type : 3; // 5 + // PipelinePrimitiveTopologyType for a vertex shader. + // xenos::TessellationMode for a domain shader. + uint32_t primitive_topology_type_or_tessellation_mode : 2; // 7 + // Zero for non-kVertex host_vertex_shader_type. + PipelineGeometryShader geometry_shader : 2; // 9 + uint32_t fill_mode_wireframe : 1; // 10 + PipelineCullMode cull_mode : 2; // 12 + uint32_t front_counter_clockwise : 1; // 13 + uint32_t depth_clip : 1; // 14 + uint32_t rov_msaa : 1; // 15 + DepthRenderTargetFormat depth_format : 1; // 16 + CompareFunction depth_func : 3; // 19 + uint32_t depth_write : 1; // 20 + uint32_t stencil_enable : 1; // 21 + uint32_t stencil_read_mask : 8; // 29 + uint32_t force_early_z : 1; // 30 uint32_t stencil_write_mask : 8; // 8 StencilOp stencil_front_fail_op : 3; // 11 @@ -197,7 +202,7 @@ class PipelineCache { PipelineRenderTarget render_targets[4]; - static constexpr uint32_t kVersion = 0x20200309; + static constexpr uint32_t kVersion = 0x20200405; }); XEPACKEDSTRUCT(PipelineStoredDescription, { @@ -214,10 +219,11 @@ class PipelineCache { bool TranslateShader(DxbcShaderTranslator& translator, D3D12Shader* shader, reg::SQ_PROGRAM_CNTL cntl, - PrimitiveType patch_primitive_type); + Shader::HostVertexShaderType host_vertex_shader_type = + Shader::HostVertexShaderType::kVertex); bool GetCurrentStateDescription( - D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, bool tessellated, + D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, PrimitiveType primitive_type, IndexFormat index_format, bool early_z, const RenderTargetCache::PipelineRenderTarget render_targets[5], PipelineRuntimeDescription& runtime_description_out); diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index 458d33668..85eddfef1 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -452,108 +452,111 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() { DxbcSrc::LF(0.0f)); } - if (IsDxbcVertexShader()) { - // Write the vertex index to GPR 0. - StartVertexShader_LoadVertexIndex(); - } else if (IsDxbcDomainShader()) { - assert_true(register_count() >= 2); - if (register_count() != 0) { - uint32_t temp_register_operand_length = - uses_register_dynamic_addressing() ? 3 : 2; + // Remember that x# are only accessible via mov load or store - use a + // temporary variable if need to do any computations! + switch (host_vertex_shader_type()) { + case Shader::HostVertexShaderType::kVertex: + StartVertexShader_LoadVertexIndex(); + break; - // Copy the domain location to r0.yz (for quad patches) or r0.xyz (for - // triangle patches), and also set the domain in STAT. - uint32_t domain_location_mask, domain_location_swizzle; - if (patch_primitive_type() == PrimitiveType::kTrianglePatch) { - domain_location_mask = 0b0111; + case Shader::HostVertexShaderType::kTriangleDomainAdaptive: + assert_true(register_count() >= 2); + if (register_count() >= 1) { + // Copy the domain location to r0.xyz. // ZYX swizzle with r1.y == 0, according to the water shader in // Banjo-Kazooie: Nuts & Bolts. - domain_location_swizzle = 0b00000110; - stat_.tessellator_domain = DxbcTessellatorDomain::kTriangle; - } else { - // TODO(Triang3l): Support line patches. - assert_true(patch_primitive_type() == PrimitiveType::kQuadPatch); + DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 0, 0b0111) + : DxbcDest::R(0, 0b0111), + DxbcSrc::VDomain(0b000110)); + if (register_count() >= 2) { + // Copy the primitive index to r1.x as a float. + uint32_t primitive_id_temp = + uses_register_dynamic_addressing() ? PushSystemTemp() : 1; + DxbcOpUToF(DxbcDest::R(primitive_id_temp, 0b0001), DxbcSrc::VPrim()); + if (uses_register_dynamic_addressing()) { + DxbcOpMov(DxbcDest::X(0, 1, 0b0001), + DxbcSrc::R(primitive_id_temp, DxbcSrc::kXXXX)); + // Release primitive_id_temp. + PopSystemTemp(); + } + // Write the swizzle of the barycentric coordinates to r1.y. It + // appears that the tessellator offloads the reordering of coordinates + // for edges to game shaders. + // + // In Banjo-Kazooie: Nuts & Bolts, the water shader multiplies the + // first control point's position by r0.z, the second CP's by r0.y, + // and the third CP's by r0.x. But before doing that it swizzles + // r0.xyz the following way depending on the value in r1.y: + // - ZXY for 1.0. + // - YZX for 2.0. + // - XZY for 4.0. + // - YXZ for 5.0. + // - ZYX for 6.0. + // Possibly, the logic here is that the value itself is the amount of + // rotation of the swizzle to the right, and 1 << 2 is set when the + // swizzle needs to be flipped before rotating. + // + // Direct3D 12 appears to be passing the coordinates in a consistent + // order, so can just use ZYX for triangle patches. + DxbcOpMov(uses_register_dynamic_addressing() + ? DxbcDest::X(0, 1, 0b0010) + : DxbcDest::R(1, 0b0010), + DxbcSrc::LF(0.0f)); + } + } + break; + + case Shader::HostVertexShaderType::kQuadDomainAdaptive: + assert_true(register_count() >= 2); + if (register_count() >= 1) { + // Copy the domain location to r0.yz. // According to the ground shader in Viva Pinata, though it's untested // as of April 4th, 2020. - domain_location_mask = 0b0110; - domain_location_swizzle = 0b00000100; - stat_.tessellator_domain = DxbcTessellatorDomain::kQuad; - } - DxbcOpMov(uses_register_dynamic_addressing() - ? DxbcDest::X(0, 0, domain_location_mask) - : DxbcDest::R(0, domain_location_mask), - DxbcSrc::VDomain(domain_location_swizzle)); - - // Copy the primitive index to r0.x (for quad patches) or r1.x (for - // triangle patches) as a float. - // When using indexable temps, copy through a r# because x# are apparently - // only accessible via mov. - // TODO(Triang3l): Investigate what should be written for primitives (or - // even control points) for non-adaptive tessellation modes (they may - // possibly have an index buffer). - // TODO(Triang3l): Support line patches. - uint32_t primitive_id_gpr_index = - patch_primitive_type() == PrimitiveType::kTrianglePatch ? 1 : 0; - if (register_count() > primitive_id_gpr_index) { - uint32_t primitive_id_temp = uses_register_dynamic_addressing() - ? PushSystemTemp() - : primitive_id_gpr_index; + DxbcOpMov(uses_register_dynamic_addressing() ? DxbcDest::X(0, 0, 0b0110) + : DxbcDest::R(0, 0b0110), + DxbcSrc::VDomain(0b000100)); + // Copy the primitive index to r0.x as a float. + uint32_t primitive_id_temp = + uses_register_dynamic_addressing() ? PushSystemTemp() : 0; DxbcOpUToF(DxbcDest::R(primitive_id_temp, 0b0001), DxbcSrc::VPrim()); if (uses_register_dynamic_addressing()) { - DxbcOpMov(DxbcDest::X(0, primitive_id_gpr_index, 0b0001), + DxbcOpMov(DxbcDest::X(0, 0, 0b0001), DxbcSrc::R(primitive_id_temp, DxbcSrc::kXXXX)); // Release primitive_id_temp. PopSystemTemp(); } + if (register_count() >= 2) { + // Write the swizzle of the barycentric coordinates to r1.x. It + // appears that the tessellator offloads the reordering of coordinates + // for edges to game shaders. + // + // In Viva Pinata, if we assume that r0.y is V and r0.z is U, the + // factors each control point value is multiplied by are the + // following: + // - (1-v)*(1-u), v*(1-u), (1-v)*u, v*u for 0.0 (base swizzle). + // - v*(1-u), (1-v)*(1-u), v*u, (1-v)*u for 1.0 (YXWZ). + // - v*u, (1-v)*u, v*(1-u), (1-v)*(1-u) for 2.0 (WZYX). + // - (1-v)*u, v*u, (1-v)*(1-u), v*(1-u) for 3.0 (ZWXY). + // According to the control point order at + // https://www.khronos.org/registry/OpenGL/extensions/AMD/AMD_vertex_shader_tessellator.txt + // the first is located at (0,0), the second at (0,1), the third at + // (1,0) and the fourth at (1,1). So, swizzle index 0 appears to be + // the correct one. (This, however, hasn't been tested yet as of April + // 5th, 2020.) + DxbcOpMov(uses_register_dynamic_addressing() + ? DxbcDest::X(0, 1, 0b0001) + : DxbcDest::R(1, 0b0001), + DxbcSrc::LF(0.0f)); + } } + break; - if (register_count() >= 2) { - // Write the swizzle of the barycentric/UV coordinates to r1.x (for quad - // patches) or r1.y (for triangle patches). It appears that the - // tessellator offloads the reordering of coordinates for edges to game - // shaders. - // - // In Banjo-Kazooie: Nuts & Bolts (triangle patches with per-edge - // factors), the shader multiplies the first control point's position by - // r0.z, the second CP's by r0.y, and the third CP's by r0.x. But before - // doing that it swizzles r0.xyz the following way depending on the - // value in r1.y: - // - ZXY for 1.0. - // - YZX for 2.0. - // - XZY for 4.0. - // - YXZ for 5.0. - // - ZYX for 6.0. - // Possibly, the logic here is that the value itself is the amount of - // rotation of the swizzle to the right, and 1 << 2 is set when the - // swizzle needs to be flipped before rotating. - // - // In Viva Pinata (quad patches with per-edge factors - not possible to - // test however as of December 12th, 2018), if we assume that r0.y is V - // and r0.z is U, the factors each control point value is multiplied by - // are the following: - // - (1-v)*(1-u), v*(1-u), (1-v)*u, v*u for 0.0 (base swizzle). - // - v*(1-u), (1-v)*(1-u), v*u, (1-v)*u for 1.0 (YXWZ). - // - v*u, (1-v)*u, v*(1-u), (1-v)*(1-u) for 2.0 (WZYX). - // - (1-v)*u, v*u, (1-v)*(1-u), v*(1-u) for 3.0 (ZWXY). - // According to the control point order at - // https://www.khronos.org/registry/OpenGL/extensions/AMD/AMD_vertex_shader_tessellator.txt - // the first is located at (0,0), the second at (0,1), the third at - // (1,0) and the fourth at (1,1). So, swizzle index 0 appears to be the - // correct one. But, this hasn't been tested yet. - // - // Direct3D 12 appears to be passing the coordinates in a consistent - // order, so we can just use ZYX for triangle patches. - // - // TODO(Triang3l): Support line patches. - uint32_t domain_location_swizzle_mask = - patch_primitive_type() == PrimitiveType::kTrianglePatch ? 0b0010 - : 0b0001; - DxbcOpMov(uses_register_dynamic_addressing() - ? DxbcDest::X(0, 1, domain_location_swizzle_mask) - : DxbcDest::R(1, domain_location_swizzle_mask), - DxbcSrc::LF(0.0f)); - } - } + default: + // TODO(Triang3l): Support line and non-adaptive patches. + assert_unhandled_case(host_vertex_shader_type()); + EmitTranslationError( + "Unsupported host vertex shader type in StartVertexOrDomainShader"); + break; } } @@ -3707,15 +3710,32 @@ void DxbcShaderTranslator::WritePatchConstantSignature() { // FXC refuses to compile without SV_TessFactor and SV_InsideTessFactor input, // so this is required. - uint32_t tess_factor_count_edge, tess_factor_count_inside; - if (patch_primitive_type() == PrimitiveType::kTrianglePatch) { - tess_factor_count_edge = 3; - tess_factor_count_inside = 1; - } else { - // TODO(Triang3l): Support line patches. - assert_true(patch_primitive_type() == PrimitiveType::kQuadPatch); - tess_factor_count_edge = 4; - tess_factor_count_inside = 2; + uint32_t tess_factor_count_edge = 3, tess_factor_name_edge = 13; + uint32_t tess_factor_count_inside = 1, tess_factor_name_inside = 14; + switch (host_vertex_shader_type()) { + case Shader::HostVertexShaderType::kTriangleDomainConstant: + case Shader::HostVertexShaderType::kTriangleDomainAdaptive: + tess_factor_count_edge = 3; + // D3D_NAME_FINAL_TRI_EDGE_TESSFACTOR. + tess_factor_name_edge = 13; + tess_factor_count_inside = 1; + // D3D_NAME_FINAL_TRI_INSIDE_TESSFACTOR. + tess_factor_name_inside = 14; + break; + case Shader::HostVertexShaderType::kQuadDomainConstant: + case Shader::HostVertexShaderType::kQuadDomainAdaptive: + tess_factor_count_edge = 4; + // D3D_NAME_FINAL_QUAD_EDGE_TESSFACTOR. + tess_factor_name_edge = 11; + tess_factor_count_inside = 2; + // D3D_NAME_FINAL_QUAD_INSIDE_TESSFACTOR. + tess_factor_name_inside = 12; + break; + default: + // TODO(Triang3l): Support line patches. + assert_unhandled_case(host_vertex_shader_type()); + EmitTranslationError( + "Unsupported host vertex shader type in WritePatchConstantSignature"); } uint32_t tess_factor_count_total = tess_factor_count_edge + tess_factor_count_inside; @@ -3729,24 +3749,10 @@ void DxbcShaderTranslator::WritePatchConstantSignature() { shader_object_.push_back(0); shader_object_.push_back( i < tess_factor_count_edge ? i : (i - tess_factor_count_edge)); - if (patch_primitive_type() == PrimitiveType::kTrianglePatch) { - if (i < tess_factor_count_edge) { - // D3D_NAME_FINAL_TRI_EDGE_TESSFACTOR. - shader_object_.push_back(13); - } else { - // D3D_NAME_FINAL_TRI_INSIDE_TESSFACTOR. - shader_object_.push_back(14); - } + if (i < tess_factor_count_edge) { + shader_object_.push_back(tess_factor_name_edge); } else { - // TODO(Triang3l): Support line patches. - assert_true(patch_primitive_type() == PrimitiveType::kQuadPatch); - if (i < tess_factor_count_edge) { - // D3D_NAME_FINAL_QUAD_EDGE_TESSFACTOR. - shader_object_.push_back(11); - } else { - // D3D_NAME_FINAL_QUAD_INSIDE_TESSFACTOR. - shader_object_.push_back(12); - } + shader_object_.push_back(tess_factor_name_inside); } // D3D_REGISTER_COMPONENT_FLOAT32. shader_object_.push_back(3); @@ -3965,29 +3971,39 @@ void DxbcShaderTranslator::WriteShaderCode() { // Inputs/outputs have 1D-indexed operands with a component mask and a // register index. + uint32_t domain_location_mask = 0b0111; if (IsDxbcDomainShader()) { // Not using control point data since Xenos only has a vertex shader acting // as both vertex shader and domain shader. - uint32_t control_point_count; - D3D11_SB_TESSELLATOR_DOMAIN domain; - if (patch_primitive_type() == PrimitiveType::kTrianglePatch) { - control_point_count = 3; - domain = D3D11_SB_TESSELLATOR_DOMAIN_TRI; - } else { - // TODO(Triang3l): Support line patches. - assert_true(patch_primitive_type() == PrimitiveType::kQuadPatch); - control_point_count = 4; - domain = D3D11_SB_TESSELLATOR_DOMAIN_QUAD; + stat_.c_control_points = 3; + stat_.tessellator_domain = DxbcTessellatorDomain::kTriangle; + switch (host_vertex_shader_type()) { + case Shader::HostVertexShaderType::kTriangleDomainConstant: + case Shader::HostVertexShaderType::kTriangleDomainAdaptive: + stat_.c_control_points = 3; + stat_.tessellator_domain = DxbcTessellatorDomain::kTriangle; + domain_location_mask = 0b0111; + break; + case Shader::HostVertexShaderType::kQuadDomainConstant: + case Shader::HostVertexShaderType::kQuadDomainAdaptive: + stat_.c_control_points = 4; + stat_.tessellator_domain = DxbcTessellatorDomain::kQuad; + domain_location_mask = 0b0011; + break; + default: + // TODO(Triang3l): Support line patches. + assert_unhandled_case(host_vertex_shader_type()); + EmitTranslationError( + "Unsupported host vertex shader type in WriteShaderCode"); } shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE( D3D11_SB_OPCODE_DCL_INPUT_CONTROL_POINT_COUNT) | - ENCODE_D3D11_SB_INPUT_CONTROL_POINT_COUNT(control_point_count) | + ENCODE_D3D11_SB_INPUT_CONTROL_POINT_COUNT(stat_.c_control_points) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(1)); - stat_.c_control_points = control_point_count; shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D11_SB_OPCODE_DCL_TESS_DOMAIN) | - ENCODE_D3D11_SB_TESS_DOMAIN(domain) | + ENCODE_D3D11_SB_TESS_DOMAIN(uint32_t(stat_.tessellator_domain)) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(1)); } @@ -4164,14 +4180,6 @@ void DxbcShaderTranslator::WriteShaderCode() { if (IsDxbcVertexOrDomainShader()) { if (IsDxbcDomainShader()) { // Domain location input (barycentric for triangles, UV for quads). - uint32_t domain_location_mask; - if (patch_primitive_type() == PrimitiveType::kTrianglePatch) { - domain_location_mask = 0b0111; - } else { - // TODO(Triang3l): Support line patches. - assert_true(patch_primitive_type() == PrimitiveType::kQuadPatch); - domain_location_mask = 0b0011; - } shader_object_.push_back( ENCODE_D3D10_SB_OPCODE_TYPE(D3D10_SB_OPCODE_DCL_INPUT) | ENCODE_D3D10_SB_TOKENIZED_INSTRUCTION_LENGTH(2)); diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 14ce7fdcc..b5b0b2c04 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -1657,11 +1657,11 @@ class DxbcShaderTranslator : public ShaderTranslator { } inline bool IsDxbcVertexShader() const { return IsDxbcVertexOrDomainShader() && - patch_primitive_type() == PrimitiveType::kNone; + host_vertex_shader_type() == Shader::HostVertexShaderType::kVertex; } inline bool IsDxbcDomainShader() const { return IsDxbcVertexOrDomainShader() && - patch_primitive_type() != PrimitiveType::kNone; + host_vertex_shader_type() != Shader::HostVertexShaderType::kVertex; } inline bool IsDxbcPixelShader() const { return is_depth_only_pixel_shader_ || is_pixel_shader(); diff --git a/src/xenia/gpu/shader.h b/src/xenia/gpu/shader.h index 8d7509d8e..c9f315183 100644 --- a/src/xenia/gpu/shader.h +++ b/src/xenia/gpu/shader.h @@ -530,6 +530,19 @@ struct ParsedAluInstruction { class Shader { public: + // If values are changed, invalidate shader storages where this is stored! And + // check bit count where this is packed. This is : uint32_t for simplicity of + // packing in bit fields. + enum class HostVertexShaderType : uint32_t { + kVertex, + kLineDomainConstant, + kLineDomainAdaptive, + kTriangleDomainConstant, + kTriangleDomainAdaptive, + kQuadDomainConstant, + kQuadDomainAdaptive, + }; + struct Error { bool is_fatal = false; std::string message; @@ -592,9 +605,12 @@ class Shader { // Whether the shader is identified as a vertex or pixel shader. ShaderType type() const { return shader_type_; } - // Tessellation patch primitive type for a vertex shader translated into a - // domain shader, or PrimitiveType::kNone for a normal vertex shader. - PrimitiveType patch_primitive_type() const { return patch_primitive_type_; } + // If this is a vertex shader, and it has been translated, type of the shader + // in a D3D11-like rendering pipeline - shader interface depends on in, so it + // must be known at translation time. + HostVertexShaderType host_vertex_shader_type() const { + return host_vertex_shader_type_; + } // Microcode dwords in host endianness. const std::vector& ucode_data() const { return ucode_data_; } @@ -676,7 +692,7 @@ class Shader { friend class ShaderTranslator; ShaderType shader_type_; - PrimitiveType patch_primitive_type_ = PrimitiveType::kNone; + HostVertexShaderType host_vertex_shader_type_ = HostVertexShaderType::kVertex; std::vector ucode_data_; uint64_t ucode_data_hash_; diff --git a/src/xenia/gpu/shader_compiler_main.cc b/src/xenia/gpu/shader_compiler_main.cc index 43d6c71cd..f30818a98 100644 --- a/src/xenia/gpu/shader_compiler_main.cc +++ b/src/xenia/gpu/shader_compiler_main.cc @@ -35,11 +35,12 @@ DEFINE_string(shader_output, "", "Output shader file path.", "GPU"); DEFINE_string(shader_output_type, "ucode", "Translator to use: [ucode, spirv, spirvtext, dxbc, dxbctext].", "GPU"); -DEFINE_string(shader_output_patch, "", - "Tessellation patch type in the generated tessellation " - "evaluation (domain) shader, or unspecified to produce a vertex " - "shader: [line, triangle, quad].", - "GPU"); +DEFINE_string( + vertex_shader_output_type, "", + "Type of the host interface to produce the vertex or domain shader for: " + "[vertex or unspecified, linedomain, linedomainadaptive, triangledomain, " + "triangledomainadaptive, quaddomain, quaddomainadaptive].", + "GPU"); DEFINE_bool(shader_output_dxbc_rov, false, "Output ROV-based output-merger code in DXBC pixel shaders.", "GPU"); @@ -112,18 +113,31 @@ int shader_compiler_main(const std::vector& args) { translator = std::make_unique(); } - PrimitiveType patch_primitive_type = PrimitiveType::kNone; + Shader::HostVertexShaderType host_vertex_shader_type = + Shader::HostVertexShaderType::kVertex; if (shader_type == ShaderType::kVertex) { - if (cvars::shader_output_patch == "line") { - patch_primitive_type = PrimitiveType::kLinePatch; - } else if (cvars::shader_output_patch == "triangle") { - patch_primitive_type = PrimitiveType::kTrianglePatch; - } else if (cvars::shader_output_patch == "quad") { - patch_primitive_type = PrimitiveType::kQuadPatch; + if (cvars::vertex_shader_output_type == "linedomain") { + host_vertex_shader_type = + Shader::HostVertexShaderType::kLineDomainConstant; + } else if (cvars::vertex_shader_output_type == "linedomainadaptive") { + host_vertex_shader_type = + Shader::HostVertexShaderType::kLineDomainAdaptive; + } else if (cvars::vertex_shader_output_type == "triangledomain") { + host_vertex_shader_type = + Shader::HostVertexShaderType::kTriangleDomainConstant; + } else if (cvars::vertex_shader_output_type == "triangledomainadaptive") { + host_vertex_shader_type = + Shader::HostVertexShaderType::kTriangleDomainAdaptive; + } else if (cvars::vertex_shader_output_type == "quaddomain") { + host_vertex_shader_type = + Shader::HostVertexShaderType::kQuadDomainConstant; + } else if (cvars::vertex_shader_output_type == "quaddomainadaptive") { + host_vertex_shader_type = + Shader::HostVertexShaderType::kQuadDomainAdaptive; } } - translator->Translate(shader.get(), patch_primitive_type); + translator->Translate(shader.get(), host_vertex_shader_type); const void* source_data = shader->translated_binary().data(); size_t source_data_size = shader->translated_binary().size(); diff --git a/src/xenia/gpu/shader_translator.cc b/src/xenia/gpu/shader_translator.cc index b2a219d6e..e4adc6a5d 100644 --- a/src/xenia/gpu/shader_translator.cc +++ b/src/xenia/gpu/shader_translator.cc @@ -107,26 +107,27 @@ bool ShaderTranslator::GatherAllBindingInformation(Shader* shader) { return true; } -bool ShaderTranslator::Translate(Shader* shader, PrimitiveType patch_type, - reg::SQ_PROGRAM_CNTL cntl) { +bool ShaderTranslator::Translate( + Shader* shader, reg::SQ_PROGRAM_CNTL cntl, + Shader::HostVertexShaderType host_vertex_shader_type) { Reset(); uint32_t cntl_num_reg = shader->type() == ShaderType::kVertex ? cntl.vs_num_reg : cntl.ps_num_reg; register_count_ = (cntl_num_reg & 0x80) ? 0 : (cntl_num_reg + 1); - return TranslateInternal(shader, patch_type); + return TranslateInternal(shader, host_vertex_shader_type); } -bool ShaderTranslator::Translate(Shader* shader, PrimitiveType patch_type) { +bool ShaderTranslator::Translate( + Shader* shader, Shader::HostVertexShaderType host_vertex_shader_type) { Reset(); - return TranslateInternal(shader, patch_type); + return TranslateInternal(shader, host_vertex_shader_type); } -bool ShaderTranslator::TranslateInternal(Shader* shader, - PrimitiveType patch_type) { +bool ShaderTranslator::TranslateInternal( + Shader* shader, Shader::HostVertexShaderType host_vertex_shader_type) { shader_type_ = shader->type(); - patch_primitive_type_ = - shader_type_ == ShaderType::kVertex ? patch_type : PrimitiveType::kNone; + host_vertex_shader_type_ = host_vertex_shader_type; ucode_dwords_ = shader->ucode_dwords(); ucode_dword_count_ = shader->ucode_dword_count(); @@ -194,7 +195,7 @@ bool ShaderTranslator::TranslateInternal(Shader* shader, shader->errors_ = std::move(errors_); shader->translated_binary_ = CompleteTranslation(); shader->ucode_disassembly_ = ucode_disasm_buffer_.to_string(); - shader->patch_primitive_type_ = patch_primitive_type_; + shader->host_vertex_shader_type_ = host_vertex_shader_type_; shader->vertex_bindings_ = std::move(vertex_bindings_); shader->texture_bindings_ = std::move(texture_bindings_); shader->constant_register_map_ = std::move(constant_register_map_); diff --git a/src/xenia/gpu/shader_translator.h b/src/xenia/gpu/shader_translator.h index 537606eb3..a41253669 100644 --- a/src/xenia/gpu/shader_translator.h +++ b/src/xenia/gpu/shader_translator.h @@ -33,9 +33,12 @@ class ShaderTranslator { // DEPRECATED(benvanik): remove this when shader cache is removed. bool GatherAllBindingInformation(Shader* shader); - bool Translate(Shader* shader, PrimitiveType patch_type, - reg::SQ_PROGRAM_CNTL cntl); - bool Translate(Shader* shader, PrimitiveType patch_type); + bool Translate(Shader* shader, reg::SQ_PROGRAM_CNTL cntl, + Shader::HostVertexShaderType host_vertex_shader_type = + Shader::HostVertexShaderType::kVertex); + bool Translate(Shader* shader, + Shader::HostVertexShaderType host_vertex_shader_type = + Shader::HostVertexShaderType::kVertex); protected: ShaderTranslator(); @@ -47,9 +50,11 @@ class ShaderTranslator { uint32_t register_count() const { return register_count_; } // True if the current shader is a vertex shader. bool is_vertex_shader() const { return shader_type_ == ShaderType::kVertex; } - // Tessellation patch primitive type for a vertex shader translated into a - // domain shader, or PrimitiveType::kNone for a normal vertex shader. - PrimitiveType patch_primitive_type() const { return patch_primitive_type_; } + // If translating a vertex shader, type of the shader in a D3D11-like + // rendering pipeline. + Shader::HostVertexShaderType host_vertex_shader_type() const { + return host_vertex_shader_type_; + } // True if the current shader is a pixel shader. bool is_pixel_shader() const { return shader_type_ == ShaderType::kPixel; } const Shader::ConstantRegisterMap& constant_register_map() const { @@ -181,7 +186,8 @@ class ShaderTranslator { bool disable_implicit_early_z; }; - bool TranslateInternal(Shader* shader, PrimitiveType patch_type); + bool TranslateInternal(Shader* shader, + Shader::HostVertexShaderType host_vertex_shader_type); void MarkUcodeInstruction(uint32_t dword_offset); void AppendUcodeDisasm(char c); @@ -230,7 +236,7 @@ class ShaderTranslator { // Input shader metadata and microcode. ShaderType shader_type_; - PrimitiveType patch_primitive_type_; + Shader::HostVertexShaderType host_vertex_shader_type_; const uint32_t* ucode_dwords_; size_t ucode_dword_count_; reg::SQ_PROGRAM_CNTL program_cntl_; diff --git a/src/xenia/gpu/vulkan/pipeline_cache.cc b/src/xenia/gpu/vulkan/pipeline_cache.cc index 93575002f..a1b7ff1d5 100644 --- a/src/xenia/gpu/vulkan/pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/pipeline_cache.cc @@ -366,7 +366,7 @@ bool PipelineCache::TranslateShader(VulkanShader* shader, reg::SQ_PROGRAM_CNTL cntl) { // Perform translation. // If this fails the shader will be marked as invalid and ignored later. - if (!shader_translator_->Translate(shader, PrimitiveType::kNone, cntl)) { + if (!shader_translator_->Translate(shader, cntl)) { XELOGE("Shader translation failed; marking shader as ignored"); return false; } diff --git a/src/xenia/gpu/xenos.h b/src/xenia/gpu/xenos.h index aa8174ecc..91813b422 100644 --- a/src/xenia/gpu/xenos.h +++ b/src/xenia/gpu/xenos.h @@ -52,17 +52,19 @@ enum class PrimitiveType : uint32_t { k2DLineStrip = 0x15, k2DTriStrip = 0x16, - // Tessellation patches (D3DTPT) when VGT_OUTPUT_PATH_CNTL & 3 is - // VGT_OUTPATH_TESS_EN (1). + // Tessellation patches (D3DTPT) when VGT_OUTPUT_PATH_CNTL::path_select is + // xenos::VGTOutputPath::tess_mode. Seen being used with adaptive tessellation + // in Banjo-Kazooie: Nuts & Bolts, Halo 3 and Viva Pinata; discrete/continuous + // uses kTriangleList in Call of Duty 3 and Viva Pinata. kLinePatch = 0x10, kTrianglePatch = 0x11, kQuadPatch = 0x12, }; inline bool IsPrimitiveTwoFaced(bool tessellated, PrimitiveType type) { - if (tessellated) { - return type == PrimitiveType::kTrianglePatch || - type == PrimitiveType::kQuadPatch; + if (tessellated && (type == PrimitiveType::kTrianglePatch || + type == PrimitiveType::kQuadPatch)) { + return true; } switch (type) { case PrimitiveType::kTriangleList: