diff --git a/src/xenia/gpu/vulkan/pipeline_cache.cc b/src/xenia/gpu/vulkan/pipeline_cache.cc index 63bad5164..af4c218eb 100644 --- a/src/xenia/gpu/vulkan/pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/pipeline_cache.cc @@ -78,6 +78,12 @@ PipelineCache::PipelineCache( } PipelineCache::~PipelineCache() { + // Destroy all pipelines. + for (auto it : cached_pipelines_) { + vkDestroyPipeline(device_, it.second, nullptr); + } + cached_pipelines_.clear(); + vkDestroyPipelineLayout(device_, pipeline_layout_, nullptr); vkDestroyPipelineCache(device_, pipeline_cache_, nullptr); @@ -164,7 +170,9 @@ bool PipelineCache::ConfigurePipeline(VkCommandBuffer command_buffer, return false; } if (!pipeline) { - pipeline = GetPipeline(render_state); + // Should have a hash key produced by the UpdateState pass. + uint64_t hash_key = XXH64_digest(&hash_state_); + pipeline = GetPipeline(render_state, hash_key); current_pipeline_ = pipeline; if (!pipeline) { // Unable to create pipeline. @@ -192,7 +200,15 @@ void PipelineCache::ClearCache() { // TODO(benvanik): caching. } -VkPipeline PipelineCache::GetPipeline(const RenderState* render_state) { +VkPipeline PipelineCache::GetPipeline(const RenderState* render_state, + uint64_t hash_key) { + // Lookup the pipeline in the cache. + auto it = cached_pipelines_.find(hash_key); + if (it != cached_pipelines_.end()) { + // Found existing pipeline. + return it->second; + } + VkPipelineDynamicStateCreateInfo dynamic_state_info; dynamic_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; @@ -233,18 +249,19 @@ VkPipeline PipelineCache::GetPipeline(const RenderState* render_state) { pipeline_info.subpass = 0; pipeline_info.basePipelineHandle = nullptr; pipeline_info.basePipelineIndex = 0; - VkPipeline pipeline = nullptr; auto err = vkCreateGraphicsPipelines(device_, nullptr, 1, &pipeline_info, nullptr, &pipeline); CheckResult(err, "vkCreateGraphicsPipelines"); - // TODO(benvanik): don't leak. + // Add to cache with the hash key for reuse. + cached_pipelines_.insert({hash_key, pipeline}); return pipeline; } -VkShaderModule PipelineCache::GetGeometryShader(PrimitiveType primitive_type) { +VkShaderModule PipelineCache::GetGeometryShader(PrimitiveType primitive_type, + bool is_line_mode) { switch (primitive_type) { case PrimitiveType::kLineList: case PrimitiveType::kLineStrip: @@ -267,6 +284,11 @@ VkShaderModule PipelineCache::GetGeometryShader(PrimitiveType primitive_type) { return nullptr; case PrimitiveType::kQuadList: // TODO(benvanik): quad list geometry shader. + if (is_line_mode) { + // + } else { + // + } return nullptr; case PrimitiveType::kQuadStrip: // TODO(benvanik): quad strip geometry shader. @@ -455,6 +477,27 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, // TODO(benvanik): push constants. + bool push_constants_dirty = full_update; + push_constants_dirty |= + SetShadowRegister(®s.sq_program_cntl, XE_GPU_REG_SQ_PROGRAM_CNTL); + push_constants_dirty |= + SetShadowRegister(®s.sq_context_misc, XE_GPU_REG_SQ_CONTEXT_MISC); + + xenos::xe_gpu_program_cntl_t program_cntl; + program_cntl.dword_0 = regs.sq_program_cntl; + + // Populate a register in the pixel shader with frag coord. + int ps_param_gen = (regs.sq_context_misc >> 8) & 0xFF; + // draw_batcher_.set_ps_param_gen(program_cntl.param_gen ? ps_param_gen : -1); + + // Normal vertex shaders only, for now. + // TODO(benvanik): transform feedback/memexport. + // https://github.com/freedreno/freedreno/blob/master/includes/a2xx.xml.h + // 0 = normal + // 2 = point size + assert_true(program_cntl.vs_export_mode == 0 || + program_cntl.vs_export_mode == 2); + return true; } @@ -476,35 +519,14 @@ bool PipelineCache::SetShadowRegister(float* dest, uint32_t register_name) { return true; } -PipelineCache::UpdateStatus PipelineCache::UpdateRenderTargets() { - auto& regs = update_render_targets_regs_; - - bool dirty = false; - dirty |= SetShadowRegister(®s.rb_modecontrol, XE_GPU_REG_RB_MODECONTROL); - dirty |= SetShadowRegister(®s.rb_surface_info, XE_GPU_REG_RB_SURFACE_INFO); - dirty |= SetShadowRegister(®s.rb_color_info, XE_GPU_REG_RB_COLOR_INFO); - dirty |= SetShadowRegister(®s.rb_color1_info, XE_GPU_REG_RB_COLOR1_INFO); - dirty |= SetShadowRegister(®s.rb_color2_info, XE_GPU_REG_RB_COLOR2_INFO); - dirty |= SetShadowRegister(®s.rb_color3_info, XE_GPU_REG_RB_COLOR3_INFO); - dirty |= SetShadowRegister(®s.rb_color_mask, XE_GPU_REG_RB_COLOR_MASK); - dirty |= SetShadowRegister(®s.rb_depthcontrol, XE_GPU_REG_RB_DEPTHCONTROL); - dirty |= - SetShadowRegister(®s.rb_stencilrefmask, XE_GPU_REG_RB_STENCILREFMASK); - dirty |= SetShadowRegister(®s.rb_depth_info, XE_GPU_REG_RB_DEPTH_INFO); - if (!dirty) { - return UpdateStatus::kCompatible; - } - - SCOPE_profile_cpu_f("gpu"); - - return UpdateStatus::kMismatch; -} - PipelineCache::UpdateStatus PipelineCache::UpdateState( VulkanShader* vertex_shader, VulkanShader* pixel_shader, PrimitiveType primitive_type) { bool mismatch = false; + // Reset hash so we can build it up. + XXH64_reset(&hash_state_, 0); + #define CHECK_UPDATE_STATUS(status, mismatch, error_message) \ { \ if (status == UpdateStatus::kError) { \ @@ -554,17 +576,16 @@ PipelineCache::UpdateStatus PipelineCache::UpdateShaderStages( bool dirty = false; dirty |= SetShadowRegister(®s.pa_su_sc_mode_cntl, XE_GPU_REG_PA_SU_SC_MODE_CNTL); - dirty |= SetShadowRegister(®s.sq_program_cntl, XE_GPU_REG_SQ_PROGRAM_CNTL); - dirty |= SetShadowRegister(®s.sq_context_misc, XE_GPU_REG_SQ_CONTEXT_MISC); - // dirty |= regs.vertex_shader != active_vertex_shader_; - // dirty |= regs.pixel_shader != active_pixel_shader_; - dirty |= regs.prim_type != primitive_type; + dirty |= regs.vertex_shader != vertex_shader; + dirty |= regs.pixel_shader != pixel_shader; + dirty |= regs.primitive_type != primitive_type; + XXH64_update(&hash_state_, ®s, sizeof(regs)); if (!dirty) { return UpdateStatus::kCompatible; } - // regs.vertex_shader = static_cast(active_vertex_shader_); - // regs.pixel_shader = static_cast(active_pixel_shader_); - regs.prim_type = primitive_type; + regs.vertex_shader = vertex_shader; + regs.pixel_shader = pixel_shader; + regs.primitive_type = primitive_type; update_shader_stages_stage_count_ = 0; @@ -579,7 +600,14 @@ PipelineCache::UpdateStatus PipelineCache::UpdateShaderStages( vertex_pipeline_stage.pName = "main"; vertex_pipeline_stage.pSpecializationInfo = nullptr; - auto geometry_shader = GetGeometryShader(primitive_type); + bool is_line_mode = false; + if (((regs.pa_su_sc_mode_cntl >> 3) & 0x3) != 0) { + uint32_t front_poly_mode = (regs.pa_su_sc_mode_cntl >> 5) & 0x7; + if (front_poly_mode == 1) { + is_line_mode = true; + } + } + auto geometry_shader = GetGeometryShader(primitive_type, is_line_mode); if (geometry_shader) { auto& geometry_pipeline_stage = update_shader_stages_info_[update_shader_stages_stage_count_++]; @@ -614,6 +642,7 @@ PipelineCache::UpdateStatus PipelineCache::UpdateVertexInputState( bool dirty = false; dirty |= vertex_shader != regs.vertex_shader; + XXH64_update(&hash_state_, ®s, sizeof(regs)); if (!dirty) { return UpdateStatus::kCompatible; } @@ -733,6 +762,7 @@ PipelineCache::UpdateStatus PipelineCache::UpdateInputAssemblyState( XE_GPU_REG_PA_SU_SC_MODE_CNTL); dirty |= SetShadowRegister(®s.multi_prim_ib_reset_index, XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX); + XXH64_update(&hash_state_, ®s, sizeof(regs)); if (!dirty) { return UpdateStatus::kCompatible; } @@ -831,6 +861,7 @@ PipelineCache::UpdateStatus PipelineCache::UpdateRasterizationState( XE_GPU_REG_PA_SC_SCREEN_SCISSOR_BR); dirty |= SetShadowRegister(®s.multi_prim_ib_reset_index, XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX); + XXH64_update(&hash_state_, ®s, sizeof(regs)); if (!dirty) { return UpdateStatus::kCompatible; } @@ -917,6 +948,7 @@ PipelineCache::UpdateStatus PipelineCache::UpdateDepthStencilState() { dirty |= SetShadowRegister(®s.rb_depthcontrol, XE_GPU_REG_RB_DEPTHCONTROL); dirty |= SetShadowRegister(®s.rb_stencilrefmask, XE_GPU_REG_RB_STENCILREFMASK); + XXH64_update(&hash_state_, ®s, sizeof(regs)); if (!dirty) { return UpdateStatus::kCompatible; } @@ -976,6 +1008,7 @@ PipelineCache::UpdateStatus PipelineCache::UpdateColorBlendState() { SetShadowRegister(®s.rb_blendcontrol[2], XE_GPU_REG_RB_BLENDCONTROL_2); dirty |= SetShadowRegister(®s.rb_blendcontrol[3], XE_GPU_REG_RB_BLENDCONTROL_3); + XXH64_update(&hash_state_, ®s, sizeof(regs)); if (!dirty) { return UpdateStatus::kCompatible; } diff --git a/src/xenia/gpu/vulkan/pipeline_cache.h b/src/xenia/gpu/vulkan/pipeline_cache.h index 1eb9d75ed..37a53c751 100644 --- a/src/xenia/gpu/vulkan/pipeline_cache.h +++ b/src/xenia/gpu/vulkan/pipeline_cache.h @@ -12,6 +12,8 @@ #include +#include "third_party/xxhash/xxhash.h" + #include "xenia/gpu/register_file.h" #include "xenia/gpu/spirv_shader_translator.h" #include "xenia/gpu/vulkan/render_cache.h" @@ -59,11 +61,12 @@ class PipelineCache { private: // Creates or retrieves an existing pipeline for the currently configured // state. - VkPipeline GetPipeline(const RenderState* render_state); + VkPipeline GetPipeline(const RenderState* render_state, uint64_t hash_key); // Gets a geometry shader used to emulate the given primitive type. // Returns nullptr if the primitive doesn't need to be emulated. - VkShaderModule GetGeometryShader(PrimitiveType primitive_type); + VkShaderModule GetGeometryShader(PrimitiveType primitive_type, + bool is_line_mode); // Sets required dynamic state on the command buffer. // Only state that has changed since the last call will be set unless @@ -89,6 +92,13 @@ class PipelineCache { // TODO(benvanik): geometry shader cache. + // Hash state used to incrementally produce pipeline hashes during update. + // By the time the full update pass has run the hash will represent the + // current state in a way that can uniquely identify the produced VkPipeline. + XXH64_state_t hash_state_; + // All previously generated pipelines mapped by hash. + std::unordered_map cached_pipelines_; + // Previously used pipeline. This matches our current state settings // and allows us to quickly(ish) reuse the pipeline if no registers have // changed. @@ -101,7 +111,6 @@ class PipelineCache { kError, }; - UpdateStatus UpdateRenderTargets(); UpdateStatus UpdateState(VulkanShader* vertex_shader, VulkanShader* pixel_shader, PrimitiveType primitive_type); @@ -137,18 +146,13 @@ class PipelineCache { } update_render_targets_regs_; struct UpdateShaderStagesRegisters { - PrimitiveType prim_type; + PrimitiveType primitive_type; uint32_t pa_su_sc_mode_cntl; - uint32_t sq_program_cntl; - uint32_t sq_context_misc; VulkanShader* vertex_shader; VulkanShader* pixel_shader; UpdateShaderStagesRegisters() { Reset(); } - void Reset() { - sq_program_cntl = 0; - vertex_shader = pixel_shader = nullptr; - } + void Reset() { std::memset(this, 0, sizeof(*this)); } } update_shader_stages_regs_; VkPipelineShaderStageCreateInfo update_shader_stages_info_[3]; uint32_t update_shader_stages_stage_count_ = 0; @@ -250,6 +254,9 @@ class PipelineCache { float rb_blend_rgba[4]; + uint32_t sq_program_cntl; + uint32_t sq_context_misc; + SetDynamicStateRegisters() { Reset(); } void Reset() { std::memset(this, 0, sizeof(*this)); } } set_dynamic_state_registers_;