Files
Xenia-Canary/src/xenia/gpu/d3d12/pipeline_cache.h
2020-12-08 22:31:09 +03:00

374 lines
14 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2018 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_D3D12_PIPELINE_CACHE_H_
#define XENIA_GPU_D3D12_PIPELINE_CACHE_H_
#include <condition_variable>
#include <cstdio>
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
#include "xenia/base/hash.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/gpu/d3d12/d3d12_shader.h"
#include "xenia/gpu/d3d12/render_target_cache.h"
#include "xenia/gpu/dxbc_shader_translator.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/d3d12/d3d12_api.h"
namespace xe {
namespace gpu {
namespace d3d12 {
class D3D12CommandProcessor;
class PipelineCache {
public:
static constexpr size_t kLayoutUIDEmpty = 0;
PipelineCache(D3D12CommandProcessor& command_processor,
const RegisterFile& register_file, bool bindless_resources_used,
bool edram_rov_used,
flags::DepthFloat24Conversion depth_float24_conversion,
uint32_t resolution_scale);
~PipelineCache();
bool Initialize();
void Shutdown();
void ClearCache(bool shutting_down = false);
void InitializeShaderStorage(const std::filesystem::path& cache_root,
uint32_t title_id, bool blocking);
void ShutdownShaderStorage();
void EndSubmission();
bool IsCreatingPipelines();
D3D12Shader* LoadShader(xenos::ShaderType shader_type,
const uint32_t* host_address, uint32_t dword_count);
// Retrieves the shader modifications for the current state, and returns
// whether they are valid.
bool GetCurrentShaderModifications(
DxbcShaderTranslator::Modification& vertex_shader_modification_out,
DxbcShaderTranslator::Modification& pixel_shader_modification_out) const;
// Translates shaders if needed, also making shader info up to date.
bool EnsureShadersTranslated(D3D12Shader::D3D12Translation* vertex_shader,
D3D12Shader::D3D12Translation* pixel_shader);
bool ConfigurePipeline(
D3D12Shader::D3D12Translation* vertex_shader,
D3D12Shader::D3D12Translation* pixel_shader,
xenos::PrimitiveType primitive_type, xenos::IndexFormat index_format,
const RenderTargetCache::PipelineRenderTarget render_targets[5],
void** pipeline_handle_out, ID3D12RootSignature** root_signature_out);
// Returns a pipeline with deferred creation by its handle. May return nullptr
// if failed to create the pipeline.
ID3D12PipelineState* GetD3D12PipelineByHandle(void* handle) const {
return reinterpret_cast<const Pipeline*>(handle)->state;
}
private:
XEPACKEDSTRUCT(ShaderStoredHeader, {
uint64_t ucode_data_hash;
uint32_t ucode_dword_count : 31;
xenos::ShaderType type : 1;
reg::SQ_PROGRAM_CNTL sq_program_cntl;
static constexpr uint32_t kVersion = 0x20201207;
});
// Update PipelineDescription::kVersion if any of the Pipeline* enums are
// changed!
enum class PipelineStripCutIndex : uint32_t {
kNone,
kFFFF,
kFFFFFFFF,
};
enum class PipelineTessellationMode : uint32_t {
kNone,
kDiscrete,
kContinuous,
kAdaptive,
};
enum class PipelinePatchType : uint32_t {
kNone,
kLine,
kTriangle,
kQuad,
};
enum class PipelinePrimitiveTopologyType : uint32_t {
kPoint,
kLine,
kTriangle,
};
enum class PipelineGeometryShader : uint32_t {
kNone,
kPointList,
kRectangleList,
kQuadList,
};
enum class PipelineCullMode : uint32_t {
kNone,
kFront,
kBack,
};
enum class PipelineBlendFactor : uint32_t {
kZero,
kOne,
kSrcColor,
kInvSrcColor,
kSrcAlpha,
kInvSrcAlpha,
kDestColor,
kInvDestColor,
kDestAlpha,
kInvDestAlpha,
kBlendFactor,
kInvBlendFactor,
kSrcAlphaSat,
};
// Update PipelineDescription::kVersion if anything is changed!
XEPACKEDSTRUCT(PipelineRenderTarget, {
uint32_t used : 1; // 1
xenos::ColorRenderTargetFormat format : 4; // 5
PipelineBlendFactor src_blend : 4; // 9
PipelineBlendFactor dest_blend : 4; // 13
xenos::BlendOp blend_op : 3; // 16
PipelineBlendFactor src_blend_alpha : 4; // 20
PipelineBlendFactor dest_blend_alpha : 4; // 24
xenos::BlendOp blend_op_alpha : 3; // 27
uint32_t write_mask : 4; // 31
});
XEPACKEDSTRUCT(PipelineDescription, {
uint64_t vertex_shader_hash;
// 0 if drawing without a pixel shader.
uint64_t pixel_shader_hash;
uint32_t vertex_shader_modification;
uint32_t pixel_shader_modification;
int32_t depth_bias;
float depth_bias_slope_scaled;
PipelineStripCutIndex strip_cut_index : 2; // 2
// PipelinePrimitiveTopologyType for a vertex shader.
// xenos::TessellationMode for a domain shader.
uint32_t primitive_topology_type_or_tessellation_mode : 2; // 4
// Zero for non-kVertex host_vertex_shader_type.
PipelineGeometryShader geometry_shader : 2; // 6
uint32_t fill_mode_wireframe : 1; // 7
PipelineCullMode cull_mode : 2; // 9
uint32_t front_counter_clockwise : 1; // 10
uint32_t depth_clip : 1; // 11
uint32_t rov_msaa : 1; // 12
xenos::DepthRenderTargetFormat depth_format : 1; // 13
xenos::CompareFunction depth_func : 3; // 16
uint32_t depth_write : 1; // 17
uint32_t stencil_enable : 1; // 18
uint32_t stencil_read_mask : 8; // 26
uint32_t stencil_write_mask : 8; // 8
xenos::StencilOp stencil_front_fail_op : 3; // 11
xenos::StencilOp stencil_front_depth_fail_op : 3; // 14
xenos::StencilOp stencil_front_pass_op : 3; // 17
xenos::CompareFunction stencil_front_func : 3; // 20
xenos::StencilOp stencil_back_fail_op : 3; // 23
xenos::StencilOp stencil_back_depth_fail_op : 3; // 26
xenos::StencilOp stencil_back_pass_op : 3; // 29
xenos::CompareFunction stencil_back_func : 3; // 32
PipelineRenderTarget render_targets[4];
static constexpr uint32_t kVersion = 0x20201207;
});
XEPACKEDSTRUCT(PipelineStoredDescription, {
uint64_t description_hash;
PipelineDescription description;
});
struct PipelineRuntimeDescription {
ID3D12RootSignature* root_signature;
D3D12Shader::D3D12Translation* vertex_shader;
D3D12Shader::D3D12Translation* pixel_shader;
PipelineDescription description;
};
// Returns the host vertex shader type for the current draw if it's valid and
// supported, or Shader::HostVertexShaderType(-1) if not.
Shader::HostVertexShaderType GetCurrentHostVertexShaderTypeIfValid() const;
D3D12Shader* LoadShader(xenos::ShaderType shader_type,
const uint32_t* host_address, uint32_t dword_count,
uint64_t data_hash);
// Can be called from multiple threads.
bool TranslateShader(DxbcShaderTranslator& translator,
D3D12Shader::D3D12Translation& translation,
reg::SQ_PROGRAM_CNTL cntl,
IDxbcConverter* dxbc_converter = nullptr,
IDxcUtils* dxc_utils = nullptr,
IDxcCompiler* dxc_compiler = nullptr);
bool GetCurrentStateDescription(
D3D12Shader::D3D12Translation* vertex_shader,
D3D12Shader::D3D12Translation* pixel_shader,
xenos::PrimitiveType primitive_type, xenos::IndexFormat index_format,
const RenderTargetCache::PipelineRenderTarget render_targets[5],
PipelineRuntimeDescription& runtime_description_out);
ID3D12PipelineState* CreateD3D12Pipeline(
const PipelineRuntimeDescription& runtime_description);
D3D12CommandProcessor& command_processor_;
const RegisterFile& register_file_;
bool bindless_resources_used_;
bool edram_rov_used_;
// 20e4 depth conversion mode to use for non-ROV output.
flags::DepthFloat24Conversion depth_float24_conversion_;
uint32_t resolution_scale_;
// Reusable shader translator.
std::unique_ptr<DxbcShaderTranslator> shader_translator_;
// Command processor thread DXIL conversion/disassembly interfaces, if DXIL
// disassembly is enabled.
IDxbcConverter* dxbc_converter_ = nullptr;
IDxcUtils* dxc_utils_ = nullptr;
IDxcCompiler* dxc_compiler_ = nullptr;
// Ucode hash -> shader.
std::unordered_map<uint64_t, D3D12Shader*, xe::hash::IdentityHasher<uint64_t>>
shaders_;
struct LayoutUID {
size_t uid;
size_t vector_span_offset;
size_t vector_span_length;
};
std::mutex layouts_mutex_;
// Texture binding layouts of different shaders, for obtaining layout UIDs.
std::vector<D3D12Shader::TextureBinding> texture_binding_layouts_;
// Map of texture binding layouts used by shaders, for obtaining UIDs. Keys
// are XXH3 hashes of layouts, values need manual collision resolution using
// layout_vector_offset:layout_length of texture_binding_layouts_.
std::unordered_multimap<uint64_t, LayoutUID,
xe::hash::IdentityHasher<uint64_t>>
texture_binding_layout_map_;
// Bindless sampler indices of different shaders, for obtaining layout UIDs.
// For bindful, sampler count is used as the UID instead.
std::vector<uint32_t> bindless_sampler_layouts_;
// Keys are XXH3 hashes of used bindless sampler indices.
std::unordered_multimap<uint64_t, LayoutUID,
xe::hash::IdentityHasher<uint64_t>>
bindless_sampler_layout_map_;
// Empty depth-only pixel shader for writing to depth buffer via ROV when no
// Xenos pixel shader provided.
std::vector<uint8_t> depth_only_pixel_shader_;
struct Pipeline {
// nullptr if creation has failed.
ID3D12PipelineState* state;
PipelineRuntimeDescription description;
};
// All previously generated pipelines identified by hash and the description.
std::unordered_multimap<uint64_t, Pipeline*,
xe::hash::IdentityHasher<uint64_t>>
pipelines_;
// Previously used pipeline. This matches our current state settings and
// allows us to quickly(ish) reuse the pipeline if no registers have been
// changed.
Pipeline* current_pipeline_ = nullptr;
// Currently open shader storage path.
std::filesystem::path shader_storage_cache_root_;
uint32_t shader_storage_title_id_ = 0;
// Shader storage output stream, for preload in the next emulator runs.
FILE* shader_storage_file_ = nullptr;
// For only writing shaders to the currently open storage once, incremented
// when switching the storage.
uint32_t shader_storage_index_ = 0;
bool shader_storage_file_flush_needed_ = false;
// Pipeline storage output stream, for preload in the next emulator runs.
FILE* pipeline_storage_file_ = nullptr;
bool pipeline_storage_file_flush_needed_ = false;
// Thread for asynchronous writing to the storage streams.
void StorageWriteThread();
std::mutex storage_write_request_lock_;
std::condition_variable storage_write_request_cond_;
// Storage thread input is protected with storage_write_request_lock_, and the
// thread is notified about its change via storage_write_request_cond_.
std::deque<std::pair<const Shader*, reg::SQ_PROGRAM_CNTL>>
storage_write_shader_queue_;
std::deque<PipelineStoredDescription> storage_write_pipeline_queue_;
bool storage_write_flush_shaders_ = false;
bool storage_write_flush_pipelines_ = false;
bool storage_write_thread_shutdown_ = false;
std::unique_ptr<xe::threading::Thread> storage_write_thread_;
// Pipeline creation threads.
void CreationThread(size_t thread_index);
void CreateQueuedPipelinesOnProcessorThread();
std::mutex creation_request_lock_;
std::condition_variable creation_request_cond_;
// Protected with creation_request_lock_, notify_one creation_request_cond_
// when set.
std::deque<Pipeline*> creation_queue_;
// Number of threads that are currently creating a pipeline - incremented when
// a pipeline is dequeued (the completion event can't be triggered before this
// is zero). Protected with creation_request_lock_.
size_t creation_threads_busy_ = 0;
// Manual-reset event set when the last queued pipeline is created and there
// are no more pipelines to create. This is triggered by the thread creating
// the last pipeline.
std::unique_ptr<xe::threading::Event> creation_completion_event_;
// Whether setting the event on completion is queued. Protected with
// creation_request_lock_, notify_one creation_request_cond_ when set.
bool creation_completion_set_event_ = false;
// Creation threads with this index or above need to be shut down as soon as
// possible. Protected with creation_request_lock_, notify_all
// creation_request_cond_ when set.
size_t creation_threads_shutdown_from_ = SIZE_MAX;
std::vector<std::unique_ptr<xe::threading::Thread>> creation_threads_;
};
} // namespace d3d12
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_D3D12_PIPELINE_CACHE_H_