[D3D12] Persistent shader and PSO storage

This commit is contained in:
Triang3l
2020-03-21 19:21:00 +03:00
parent b1d3fd2ad3
commit cde092ece1
20 changed files with 1112 additions and 203 deletions

View File

@@ -77,6 +77,12 @@ void D3D12CommandProcessor::ClearCaches() {
cache_clear_requested_ = true;
}
void D3D12CommandProcessor::InitializeShaderStorage(
const std::wstring& storage_root, uint32_t title_id, bool blocking) {
CommandProcessor::InitializeShaderStorage(storage_root, title_id, blocking);
pipeline_cache_->InitializeShaderStorage(storage_root, title_id, blocking);
}
void D3D12CommandProcessor::RequestFrameTrace(const std::wstring& root_path) {
// Capture with PIX if attached.
if (GetD3D12Context()->GetD3D12Provider()->GetGraphicsAnalysis() != nullptr) {
@@ -2123,7 +2129,7 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
}
bool D3D12CommandProcessor::CanEndSubmissionImmediately() const {
return !submission_open_ || !pipeline_cache_->IsCreatingPipelines();
return !submission_open_ || !pipeline_cache_->IsCreatingPipelineStates();
}
void D3D12CommandProcessor::AwaitAllSubmissionsCompletion() {

View File

@@ -43,6 +43,9 @@ class D3D12CommandProcessor : public CommandProcessor {
void ClearCaches() override;
void InitializeShaderStorage(const std::wstring& storage_root,
uint32_t title_id, bool blocking) override;
void RequestFrameTrace(const std::wstring& root_path) override;
void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) override;
@@ -125,10 +128,11 @@ class D3D12CommandProcessor : public CommandProcessor {
// render targets or copying to depth render targets.
void SetSamplePositions(MsaaSamples sample_positions);
// Returns a pipeline with deferred creation by its handle. May return nullptr
// if failed to create the pipeline.
inline ID3D12PipelineState* GetPipelineStateByHandle(void* handle) const {
return pipeline_cache_->GetPipelineStateByHandle(handle);
// Returns a pipeline state object with deferred creation by its handle. May
// return nullptr if failed to create the pipeline state object.
inline ID3D12PipelineState* GetD3D12PipelineStateByHandle(
void* handle) const {
return pipeline_cache_->GetD3D12PipelineStateByHandle(handle);
}
// Sets the current pipeline state to a compute pipeline. This is for cache

View File

@@ -200,8 +200,9 @@ void DeferredCommandList::Execute(ID3D12GraphicsCommandList* command_list,
}
} break;
case Command::kSetPipelineStateHandle: {
current_pipeline_state = command_processor_->GetPipelineStateByHandle(
*reinterpret_cast<void* const*>(stream));
current_pipeline_state =
command_processor_->GetD3D12PipelineStateByHandle(
*reinterpret_cast<void* const*>(stream));
if (current_pipeline_state) {
command_list->SetPipelineState(current_pipeline_state);
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,13 +11,17 @@
#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/platform.h"
#include "xenia/base/threading.h"
#include "xenia/gpu/d3d12/d3d12_shader.h"
#include "xenia/gpu/d3d12/render_target_cache.h"
@@ -40,10 +44,14 @@ class PipelineCache {
bool Initialize();
void Shutdown();
void ClearCache();
void ClearCache(bool shutting_down = false);
void InitializeShaderStorage(const std::wstring& storage_root,
uint32_t title_id, bool blocking);
void ShutdownShaderStorage();
void EndSubmission();
bool IsCreatingPipelines();
bool IsCreatingPipelineStates();
D3D12Shader* LoadShader(ShaderType shader_type, uint32_t guest_address,
const uint32_t* host_address, uint32_t dword_count);
@@ -57,15 +65,32 @@ class PipelineCache {
D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, bool tessellated,
PrimitiveType primitive_type, IndexFormat index_format, bool early_z,
const RenderTargetCache::PipelineRenderTarget render_targets[5],
void** pipeline_handle_out, ID3D12RootSignature** root_signature_out);
void** pipeline_state_handle_out,
ID3D12RootSignature** root_signature_out);
// Returns a pipeline with deferred creation by its handle. May return nullptr
// if failed to create the pipeline.
inline ID3D12PipelineState* GetPipelineStateByHandle(void* handle) const {
return reinterpret_cast<const Pipeline*>(handle)->state;
// Returns a pipeline state object with deferred creation by its handle. May
// return nullptr if failed to create the pipeline state object.
inline ID3D12PipelineState* GetD3D12PipelineStateByHandle(
void* handle) const {
return reinterpret_cast<const PipelineState*>(handle)->state;
}
private:
XEPACKEDSTRUCT(ShaderStoredHeader, {
uint64_t ucode_data_hash;
uint32_t ucode_dword_count : 16;
ShaderType type : 1;
PrimitiveType patch_primitive_type : 6;
reg::SQ_PROGRAM_CNTL sq_program_cntl;
static constexpr uint32_t kVersion = 0x20200301;
});
// Update PipelineDescription::kVersion if any of the Pipeline* enums are
// changed!
enum class PipelineStripCutIndex : uint32_t {
kNone,
kFFFF,
@@ -122,7 +147,8 @@ class PipelineCache {
kSrcAlphaSat,
};
struct PipelineRenderTarget {
// Update PipelineDescription::kVersion if anything is changed!
XEPACKEDSTRUCT(PipelineRenderTarget, {
uint32_t used : 1; // 1
ColorRenderTargetFormat format : 4; // 5
PipelineBlendFactor src_blend : 4; // 9
@@ -132,12 +158,12 @@ class PipelineCache {
PipelineBlendFactor dest_blend_alpha : 4; // 24
BlendOp blend_op_alpha : 3; // 27
uint32_t write_mask : 4; // 31
};
});
struct PipelineDescription {
ID3D12RootSignature* root_signature;
D3D12Shader* vertex_shader;
D3D12Shader* pixel_shader;
XEPACKEDSTRUCT(PipelineDescription, {
uint64_t vertex_shader_hash;
// 0 if drawing without a pixel shader.
uint64_t pixel_shader_hash;
int32_t depth_bias;
float depth_bias_slope_scaled;
@@ -170,19 +196,34 @@ class PipelineCache {
CompareFunction stencil_back_func : 3; // 32
PipelineRenderTarget render_targets[4];
static constexpr uint32_t kVersion = 0x20200309;
});
XEPACKEDSTRUCT(PipelineStoredDescription, {
uint64_t description_hash;
PipelineDescription description;
});
struct PipelineRuntimeDescription {
ID3D12RootSignature* root_signature;
D3D12Shader* vertex_shader;
D3D12Shader* pixel_shader;
PipelineDescription description;
};
bool TranslateShader(D3D12Shader* shader, reg::SQ_PROGRAM_CNTL cntl,
bool tessellated, PrimitiveType primitive_type);
bool TranslateShader(DxbcShaderTranslator& translator, D3D12Shader* shader,
reg::SQ_PROGRAM_CNTL cntl,
PrimitiveType patch_primitive_type);
bool GetCurrentStateDescription(
D3D12Shader* vertex_shader, D3D12Shader* pixel_shader, bool tessellated,
PrimitiveType primitive_type, IndexFormat index_format, bool early_z,
const RenderTargetCache::PipelineRenderTarget render_targets[5],
PipelineDescription& description_out);
PipelineRuntimeDescription& runtime_description_out);
ID3D12PipelineState* CreatePipelineState(
const PipelineDescription& description);
ID3D12PipelineState* CreateD3D12PipelineState(
const PipelineRuntimeDescription& runtime_description);
D3D12CommandProcessor* command_processor_;
RegisterFile* register_file_;
@@ -200,40 +241,71 @@ class PipelineCache {
// Xenos pixel shader provided.
std::vector<uint8_t> depth_only_pixel_shader_;
struct Pipeline {
struct PipelineState {
// nullptr if creation has failed.
ID3D12PipelineState* state;
PipelineDescription description;
PipelineRuntimeDescription description;
};
// All previously generated pipelines identified by hash and the description.
std::unordered_multimap<uint64_t, Pipeline*> pipelines_;
// All previously generated pipeline state objects identified by hash and the
// description.
std::unordered_multimap<uint64_t, PipelineState*> pipeline_states_;
// Previously used pipeline. This matches our current state settings
// and allows us to quickly(ish) reuse the pipeline if no registers have
// changed.
Pipeline* current_pipeline_ = nullptr;
// Previously used pipeline state object. This matches our current state
// settings and allows us to quickly(ish) reuse the pipeline state if no
// registers have changed.
PipelineState* current_pipeline_state_ = nullptr;
// Pipeline creation threads.
void CreationThread();
// Currently open shader storage path.
std::wstring shader_storage_root_;
uint32_t shader_storage_title_id_ = 0;
// Shader storage output stream, for preload in the next emulator runs.
FILE* shader_storage_file_ = nullptr;
bool shader_storage_file_flush_needed_ = false;
// Pipeline state storage output stream, for preload in the next emulator
// runs.
FILE* pipeline_state_storage_file_ = nullptr;
bool pipeline_state_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_state_queue_;
bool storage_write_flush_shaders_ = false;
bool storage_write_flush_pipeline_states_ = false;
bool storage_write_thread_shutdown_ = false;
std::unique_ptr<xe::threading::Thread> storage_write_thread_;
// Pipeline state object creation threads.
void CreationThread(size_t thread_index);
void CreateQueuedPipelineStatesOnProcessorThread();
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_.
uint32_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::deque<PipelineState*> creation_queue_;
// Number of threads that are currently creating a pipeline state object -
// incremented when a pipeline state object 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 state object is
// created and there are no more pipeline state objects to create. This is
// triggered by the thread creating the last pipeline state object.
std::unique_ptr<xe::threading::Event> creation_completion_event_ = nullptr;
// 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;
// Whether to shut down the creation threads as soon as possible. Protected
// with creation_request_lock_, notify_all creation_request_cond_ when set.
bool creation_threads_shutdown_ = 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_;
};