diff --git a/framework/decode/block_parser.cpp b/framework/decode/block_parser.cpp index 8cb49c27a1..d13c95a96d 100644 --- a/framework/decode/block_parser.cpp +++ b/framework/decode/block_parser.cpp @@ -203,6 +203,7 @@ template if (ShouldDeferDecompression(block_buffer.GetData().size())) { return ParsedBlock(ParsedBlock::DeferredDecompressBlockTag{}, + block_index_, block_buffer, block_reference_policy_, std::forward(args)); @@ -216,10 +217,11 @@ template DecompressSpan(read_result.buffer, read_result.uncompressed_size, UseParserLocalStorageTag{}); if (uncompressed_data == nullptr) { - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } args.data = uncompressed_data; - return ParsedBlock(ParsedBlock::DecompressedBlockTag{}, block_buffer, std::forward(args)); + return ParsedBlock( + ParsedBlock::DecompressedBlockTag{}, block_index_, block_buffer, std::forward(args)); } // Use owned uncompressed storage only as needed @@ -227,9 +229,10 @@ template args.data = uncompressed_store.template GetAs(); if (uncompressed_store.empty()) { - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } return ParsedBlock(ParsedBlock::DecompressedBlockTag{}, + block_index_, block_buffer, block_reference_policy_, std::move(uncompressed_store), @@ -238,8 +241,11 @@ template } else { - return ParsedBlock( - ParsedBlock::UncompressedBlockTag{}, block_buffer, block_reference_policy_, std::forward(args)); + return ParsedBlock(ParsedBlock::UncompressedBlockTag{}, + block_index_, + block_buffer, + block_reference_policy_, + std::forward(args)); } } // Create a block that is never compressed with correct handling of both compression state and decompression policy @@ -248,6 +254,7 @@ template BlockParser::MakeIncompressibleParsedBlock(BlockBuffer& block_buffer, ArgPayload&& args, bool references_block_buffer) { return ParsedBlock(ParsedBlock::IncompressibleBlockTag{ block_buffer }, + block_index_, block_buffer, block_reference_policy_, references_block_buffer, @@ -283,7 +290,7 @@ ParsedBlock BlockParser::ParseBlock(BlockBuffer& block_buffer) case format::kUnknownBlock: default: WarnUnknownBlock(block_buffer); - return ParsedBlock{ ParsedBlock::UnknownBlockTag(), block_buffer.ReleaseData() }; + return ParsedBlock{ ParsedBlock::UnknownBlockTag(), block_index_, block_buffer.ReleaseData() }; break; } } @@ -401,7 +408,7 @@ ParsedBlock BlockParser::ParseFunctionCall(BlockBuffer& block_buffer) HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read function call block header"); } - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } ParsedBlock BlockParser::ParseMethodCall(BlockBuffer& block_buffer) @@ -439,7 +446,7 @@ ParsedBlock BlockParser::ParseMethodCall(BlockBuffer& block_buffer) HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read method call block header"); } - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } ParsedBlock BlockParser::ParseMetaData(BlockBuffer& block_buffer) @@ -454,7 +461,7 @@ ParsedBlock BlockParser::ParseMetaData(BlockBuffer& block_buffer) if (!success) { HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read function call block header"); - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } // Optional backing store for the various uncompressed metadata contents @@ -1415,7 +1422,7 @@ ParsedBlock BlockParser::ParseMetaData(BlockBuffer& block_buffer) if (!success) { HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read environment variable block header"); - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } GFXRECON_CHECK_CONVERSION_DATA_LOSS(size_t, header.string_length); @@ -1426,7 +1433,7 @@ ParsedBlock BlockParser::ParseMetaData(BlockBuffer& block_buffer) if (!success) { HandleBlockReadError(kErrorReadingBlockData, "Failed to read environment variable block data"); - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } const char* env_string = reinterpret_cast(parameter_data.data()); @@ -1632,7 +1639,7 @@ ParsedBlock BlockParser::ParseMetaData(BlockBuffer& block_buffer) // be passed through, even as unknown. // // A warning has been generated above - return ParsedBlock(ParsedBlock::UnknownBlockTag(), block_buffer.ReleaseData()); + return ParsedBlock(ParsedBlock::UnknownBlockTag{}, block_index_, block_buffer.ReleaseData()); } else { @@ -1641,7 +1648,7 @@ ParsedBlock BlockParser::ParseMetaData(BlockBuffer& block_buffer) } } - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } ParsedBlock BlockParser::ParseFrameMarker(BlockBuffer& block_buffer) @@ -1655,7 +1662,7 @@ ParsedBlock BlockParser::ParseFrameMarker(BlockBuffer& block_buffer) if (!success) { HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read frame marker block header"); - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } // Read the rest of the frame marker data. Currently frame markers are not dispatched to decoders. @@ -1673,7 +1680,7 @@ ParsedBlock BlockParser::ParseFrameMarker(BlockBuffer& block_buffer) else { WarnUnknownBlock(block_buffer, "frame marker", static_cast(marker_type)); - return ParsedBlock(ParsedBlock::UnknownBlockTag(), block_buffer.ReleaseData()); + return ParsedBlock(ParsedBlock::UnknownBlockTag{}, block_index_, block_buffer.ReleaseData()); } } else @@ -1681,7 +1688,7 @@ ParsedBlock BlockParser::ParseFrameMarker(BlockBuffer& block_buffer) HandleBlockReadError(kErrorReadingBlockData, "Failed to read frame marker data"); } - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } ParsedBlock BlockParser::ParseStateMarker(BlockBuffer& block_buffer) @@ -1695,7 +1702,7 @@ ParsedBlock BlockParser::ParseStateMarker(BlockBuffer& block_buffer) if (!success) { HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read state marker block header"); - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } uint64_t frame_number = 0; @@ -1716,7 +1723,7 @@ ParsedBlock BlockParser::ParseStateMarker(BlockBuffer& block_buffer) else { WarnUnknownBlock(block_buffer, "state marker", static_cast(marker_type)); - return ParsedBlock(ParsedBlock::UnknownBlockTag(), block_buffer.ReleaseData()); + return ParsedBlock(ParsedBlock::UnknownBlockTag{}, block_index_, block_buffer.ReleaseData()); } } else @@ -1724,7 +1731,7 @@ ParsedBlock BlockParser::ParseStateMarker(BlockBuffer& block_buffer) HandleBlockReadError(kErrorReadingBlockData, "Failed to read state marker data"); } - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } ParsedBlock BlockParser::ParseAnnotation(BlockBuffer& block_buffer) @@ -1738,7 +1745,7 @@ ParsedBlock BlockParser::ParseAnnotation(BlockBuffer& block_buffer) if (!success) { HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read annotation block header"); - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } decltype(format::AnnotationHeader::label_length) label_length = 0; @@ -1790,7 +1797,7 @@ ParsedBlock BlockParser::ParseAnnotation(BlockBuffer& block_buffer) HandleBlockReadError(kErrorReadingBlockHeader, "Failed to read annotation block header"); } - return ParsedBlock(ParsedBlock::InvalidBlockTag()); + return ParsedBlock(ParsedBlock::InvalidBlockTag(), block_index_); } GFXRECON_END_NAMESPACE(decode) diff --git a/framework/decode/file_processor.cpp b/framework/decode/file_processor.cpp index d309d3c5d5..7b5cb5dc20 100644 --- a/framework/decode/file_processor.cpp +++ b/framework/decode/file_processor.cpp @@ -32,8 +32,6 @@ GFXRECON_BEGIN_NAMESPACE(gfxrecon) GFXRECON_BEGIN_NAMESPACE(decode) -const uint32_t kFirstFrame = 0; - FileProcessor::FileProcessor() : current_frame_number_(kFirstFrame), error_state_(kErrorInvalidFileDescriptor), bytes_read_(0), annotation_handler_(nullptr), compressor_(nullptr), block_index_(0), block_limit_(0), @@ -93,47 +91,27 @@ bool FileProcessor::Initialize(const std::string& filename) return success; } -std::string FileProcessor::ApplyAbsolutePath(const std::string& file) -{ - if (absolute_path_.empty()) - { - return file; - } - - return absolute_path_ + file; -} - bool FileProcessor::ProcessNextFrame() { - auto block_processor = [this]() { return this->ProcessBlocksOneFrame(); }; - return DoProcessNextFrame(block_processor); -} - -bool FileProcessor::ProcessBlocksOneFrame() -{ - for (ApiDecoder* decoder : decoders_) + if (!IsFileValid()) { - decoder->SetCurrentFrameNumber(current_frame_number_); + error_state_ = CheckFileStatus(); + return false; } - block_parser_->SetDecompressionPolicy(BlockParser::DecompressionPolicy::kAlways); - return ProcessBlocks(); -} -bool FileProcessor::DoProcessNextFrame(const std::function& block_processor) -{ - bool success = IsFileValid(); - - if (success) - { + DispatchVisitor dispatch_visitor(decoders_, annotation_handler_); + // No need to decompress within the dispatch function, given kAlways policy. + block_parser_->SetDecompressionPolicy(BlockParser::DecompressionPolicy::kAlways); + DispatchFunction dispatch = [this, &dispatch_visitor](uint64_t block_index, ParsedBlock& block) { + SetDecoderBlockIndex(block_index); + std::visit(dispatch_visitor, block.GetArgs()); + return ProcessBlockState::kRunning; + }; - success = block_processor(); - } - else - { - error_state_ = CheckFileStatus(); - } + SetDecoderFrameNumber(current_frame_number_); + ProcessBlockState process_result = ProcessBlocks(dispatch, true /* check decoder completion */); - return success; + return ContinueProcessing(process_result); } bool FileProcessor::ProcessAllFrames() @@ -153,25 +131,25 @@ bool FileProcessor::ProcessAllFrames() return (error_state_ == kErrorNone); } -bool FileProcessor::ContinueDecoding() +bool FileProcessor::ContinueDecoding(uint64_t block_index, bool check_decoders) { bool early_exit = false; // If a block limit was specified, obey it. // If not (block_limit_ = 0), then the consumer may determine early exit if (block_limit_ > 0) { - if (block_index_ > block_limit_) + if (block_index > block_limit_) { early_exit = true; } } - else + else if (check_decoders) { int completed_decoders = 0; for (auto& decoder : decoders_) { - if (decoder->IsComplete(block_index_) == true) + if (decoder->IsComplete(block_index) == true) { completed_decoders++; } @@ -272,31 +250,21 @@ void FileProcessor::DecrementRemainingCommands() } } -bool FileProcessor::ProcessBlocks() +FileProcessor::ProcessBlockState FileProcessor::ProcessBlocks(DispatchFunction& dispatch, bool check_decoder_completion) { - BlockBuffer block_buffer; - bool success = true; + BlockBuffer block_buffer; + ProcessBlockState process_state = ProcessBlockState::kRunning; + BlockParser& block_parser = *block_parser_.get(); + ProcessVisitor process_visitor(*this); - BlockParser& block_parser = GetBlockParser(); - // NOTE: To test deferred decompression operation uncomment next line - // block_parser.SetDecompressionPolicy(BlockParser::DecompressionPolicy::kQueueOptimized); - - ProcessVisitor process_visitor(*this); - DispatchVisitor dispatch_visitor(decoders_, annotation_handler_); - - while (success) + while (process_state == ProcessBlockState::kRunning) { PrintBlockInfo(); - success = ContinueDecoding(); + bool success = ContinueDecoding(block_index_, check_decoder_completion); if (success) { - success = GetBlockBuffer(block_parser, block_buffer); - - for (auto decoder : decoders_) - { - decoder->SetCurrentBlockIndex(block_index_); - } + success = ReadBlockBuffer(block_parser, block_buffer); if (success) { @@ -324,33 +292,61 @@ bool FileProcessor::ProcessBlocks() success = process_visitor.IsSuccess(); if (success) { - std::visit(dispatch_visitor, parsed_block.GetArgs()); + process_state = dispatch(block_index_, parsed_block); + if ((ProcessBlockState::kRunning == process_state) && + process_visitor.IsFrameDelimiter()) + { + process_state = ProcessBlockState::kFrameBoundary; + } } + else + { + process_state = ProcessBlockState::kError; + } + } + else + { + // Decompression failed. Decompress logs error. + process_state = ProcessBlockState::kError; } } - - // NOTE: Warnings for unknown/invalid blocks are handled in the BlockParser - - if (process_visitor.IsFrameDelimiter()) + else if (parsed_block.IsUnknown()) { - // The ProcessVisitor (pre-dispatch) is not the right place to update the frame state, so do it - // here - UpdateEndFrameState(); - break; + // Unrecognized block type. + GFXRECON_LOG_WARNING("Skipping unrecognized file block with type %u (frame %u block %" PRIu64 + ")", + block_buffer.Header().type, + current_frame_number_, + block_index_); + GFXRECON_CHECK_CONVERSION_DATA_LOSS(size_t, block_buffer.Header().size); + } + else if (!parsed_block.IsValid()) + { + // Invalid block. Error already logged in ParseBlock. + process_state = ProcessBlockState::kError; } } + ++block_index_; + DecrementRemainingCommands(); } - else + else // GetBlockBuffer failed { - success = HandleBlockEof("read", true); + process_state = HandleBlockEof("read", true); } } - ++block_index_; - DecrementRemainingCommands(); + else // ContinueDecoding returned false + { + process_state = ProcessBlockState::kEndProcessing; + } } - DecrementRemainingCommands(); - return success; + // Update the frame number etc. + if (process_state == ProcessBlockState::kFrameBoundary) + { + UpdateEndFrameState(); + } + + return process_state; } // While ReadBlockBuffer both reads the block header and the block body, checks for @@ -375,12 +371,6 @@ bool FileProcessor::ReadBlockBuffer(BlockParser& parser, BlockBuffer& block_buff return success; } -// Preloading overloads this to get preloaded blocks -bool FileProcessor::GetBlockBuffer(BlockParser& parser, BlockBuffer& block_buffer) -{ - return ReadBlockBuffer(parser, block_buffer); -} - bool FileProcessor::ReadBytes(void* buffer, size_t buffer_size) { // File entry is non-const to allow read bytes to be non-const (i.e. potentially reflect a stateful operation) @@ -413,6 +403,18 @@ util::DataSpan FileProcessor::ReadSpan(size_t bytes) return read_span; } +bool FileProcessor::IsFileValid() const +{ + if (!file_stack_.empty()) + { + return file_stack_.back().active_file->IsReady(); + } + else + { + return false; + } +} + bool FileProcessor::SeekActiveFile(const FileInputStreamPtr& active_file, int64_t offset, util::platform::FileSeekOrigin origin) @@ -525,7 +527,6 @@ void FileProcessor::UpdateEndFrameState() // Make sure to increment the frame number on the way out. ++current_frame_number_; - ++block_index_; } bool FileProcessor::ProcessFrameDelimiter(gfxrecon::format::ApiCallId call_id) @@ -635,10 +636,10 @@ void FileProcessor::PrintBlockInfo() const } } -bool FileProcessor::HandleBlockEof(const char* operation, bool report_frame_and_block) +FileProcessor::ProcessBlockState FileProcessor::HandleBlockEof(const char* operation, bool report_frame_and_block) { - bool success = false; + ProcessBlockState state = ProcessBlockState::kEndProcessing; if (!AtEof()) { // No data has been read for the current block, so we don't use 'HandleBlockReadError' here, as it @@ -659,6 +660,7 @@ bool FileProcessor::HandleBlockEof(const char* operation, bool report_frame_and_ } error_state_ = kErrorReadingBlockHeader; + state = ProcessBlockState::kError; } else { @@ -668,10 +670,29 @@ bool FileProcessor::HandleBlockEof(const char* operation, bool report_frame_and_ if (current_file.execute_till_eof) { file_stack_.pop_back(); - success = !file_stack_.empty(); + if (!file_stack_.empty()) + { + state = ProcessBlockState::kRunning; + } } } - return success; + return state; +} + +void FileProcessor::SetDecoderFrameNumber(uint64_t frame_number) +{ + for (auto* decoder : decoders_) + { + decoder->SetCurrentFrameNumber(frame_number); + } +} + +void FileProcessor::SetDecoderBlockIndex(uint64_t block_index) +{ + for (auto decoder : decoders_) + { + decoder->SetCurrentBlockIndex(block_index); + } } GFXRECON_END_NAMESPACE(decode) diff --git a/framework/decode/file_processor.h b/framework/decode/file_processor.h index 4329ff10e7..c9917c556e 100644 --- a/framework/decode/file_processor.h +++ b/framework/decode/file_processor.h @@ -102,6 +102,8 @@ class FileProcessor }; public: + const static uint32_t kFirstFrame = 0; + FileProcessor(); FileProcessor(uint64_t block_limit); @@ -123,7 +125,7 @@ class FileProcessor // Returns true if there are more frames to process, false if all frames have been processed or an error has // occurred. Use GetErrorState() to determine error condition. - bool ProcessNextFrame(); + virtual bool ProcessNextFrame(); // Returns false if processing failed. Use GetErrorState() to determine error condition for failure case. bool ProcessAllFrames(); @@ -172,10 +174,9 @@ class FileProcessor void ProcessAnnotation(const AnnotationArgs& annotation); protected: - bool DoProcessNextFrame(const std::function& block_processor); - bool ProcessBlocksOneFrame(); + using BlockProcessor = std::function; - bool ContinueDecoding(); + bool ContinueDecoding(uint64_t block_index, bool check_decoders); util::DataSpan ReadSpan(size_t buffer_size); bool ReadBytes(void* buffer, size_t buffer_size); @@ -183,9 +184,6 @@ class FileProcessor // Reads block header, from input stream. bool ReadBlockBuffer(BlockParser& parser, BlockBuffer& buffer); - // Gets the block buffer from input stream or preloaded data if available - virtual bool GetBlockBuffer(BlockParser& parser, BlockBuffer& block_buffer); - void UpdateEndFrameState(); // Returns whether the call_id is a frame delimiter and handles frame delimiting logic @@ -194,7 +192,20 @@ class FileProcessor void PrintBlockInfo() const; - bool HandleBlockEof(const char* operation, bool report_frame_and_block); + enum class ProcessBlockState : int32_t + { + // Negative values indicate terminal states. Do not call ProcessBlocks again after receiving these. + // + // Returned when ProcessBlocks ... + kFrameBoundary = 1, // encountered a frame boundary + kRunning = 0, // never. Internal state: continue looping in ProcessBlocks + kEndProcessing = -1, // completed processing (!ContinueDecoding or clean EOF) + kError = -2, // encountered an error + }; + static bool ContinueProcessing(ProcessBlockState state) { return static_cast(state) >= 0; } + static bool IsFrameBoundary(ProcessBlockState state) { return state == ProcessBlockState::kFrameBoundary; } + + ProcessBlockState HandleBlockEof(const char* operation, bool report_frame_and_block); protected: uint64_t current_frame_number_; @@ -207,6 +218,7 @@ class FileProcessor uint64_t block_index_; protected: + bool IsFileValid() const; BlockIOError CheckFileStatus() const { if (file_stack_.empty()) @@ -236,13 +248,20 @@ class FileProcessor return file_stack_.back().active_file->IsEof(); } + // Dispatch function is allowed to modify the ParsedBlock as needed before processing + // including decompression, or even stealing the contents for deferred processing. + using DispatchFunction = std::function; + ProcessBlockState ProcessBlocks(DispatchFunction& dispatch, bool check_decoder_completeness); + + void SetDecoderBlockIndex(uint64_t block_index); + void SetDecoderFrameNumber(uint64_t frame_number); + BlockParser& GetBlockParser() { GFXRECON_ASSERT(block_parser_.get() != nullptr); return *block_parser_; } - private: class DispatchVisitor { public: @@ -290,6 +309,7 @@ class FileProcessor AnnotationHandler* annotation_handler_; }; + private: class ProcessVisitor { public: @@ -352,9 +372,7 @@ class FileProcessor template void operator()(const Args&) { - // The default behavior for a Visit is a successful, non-frame-delimiter - is_frame_delimiter = false; - success = true; + Reset(); } // Avoid unpacking the Arg from it's store in the Arg specific overloads @@ -367,6 +385,11 @@ class FileProcessor bool IsSuccess() const { return success; } bool IsFrameDelimiter() const { return is_frame_delimiter; } ProcessVisitor(FileProcessor& file_processor) : file_processor_(file_processor) {} + void Reset() + { + is_frame_delimiter = false; + success = true; + } private: bool is_frame_delimiter = false; @@ -375,23 +398,10 @@ class FileProcessor }; bool ProcessFileHeader(); - bool ProcessBlocks(); // NOTE: These two can't be const as derived class updates state. virtual bool SkipBlockProcessing() { return false; } // No block skipping in base class - bool IsFileValid() const - { - if (!file_stack_.empty()) - { - return file_stack_.back().active_file->IsReady(); - } - else - { - return false; - } - } - bool SeekActiveFile(const FileInputStreamPtr& file, int64_t offset, util::platform::FileSeekOrigin origin); bool SeekActiveFile(int64_t offset, util::platform::FileSeekOrigin origin); @@ -405,8 +415,6 @@ class FileProcessor void DecrementRemainingCommands(); - std::string ApplyAbsolutePath(const std::string& file); - private: std::vector file_options_; format::EnabledOptions enabled_options_; diff --git a/framework/decode/parsed_block.h b/framework/decode/parsed_block.h index c6b4c6558f..bf8aea2531 100644 --- a/framework/decode/parsed_block.h +++ b/framework/decode/parsed_block.h @@ -104,15 +104,19 @@ class ParsedBlock using PoolEntry = util::HeapBufferPool::Entry; // Placeholder for buffer pool using UncompressedStore = PoolEntry; - bool IsValid() const { return state_ != BlockState::kInvalid; } - bool IsReady() const { return state_ == BlockState::kReady; } - bool IsVisitable() const { return (state_ == BlockState::kReady) || (state_ == BlockState::kDeferredDecompress); } - bool IsUnknown() const { return state_ == BlockState::kUnknown; } + bool IsValid() const noexcept { return state_ != BlockState::kInvalid; } + bool IsReady() const noexcept { return state_ == BlockState::kReady; } + bool IsVisitable() const noexcept + { + return (state_ == BlockState::kReady) || (state_ == BlockState::kDeferredDecompress); + } + bool IsUnknown() const noexcept { return state_ == BlockState::kUnknown; } bool NeedsDecompression() const { return state_ == BlockState::kDeferredDecompress; } - BlockState GetState() const { return state_; } - const util::DataSpan& GetBlockData() const { return block_data_; } - const DispatchArgs& GetArgs() const { return dispatch_args_; } - explicit operator bool() const { return IsValid(); } + BlockState GetState() const noexcept { return state_; } + uint64_t GetBlockIndex() const noexcept { return block_index_; } + const util::DataSpan& GetBlockData() const noexcept { return block_data_; } + const DispatchArgs& GetArgs() const noexcept { return dispatch_args_; } + explicit operator bool() const noexcept { return IsValid(); } template bool Holds() const @@ -155,14 +159,17 @@ class ParsedBlock // Create an empty block with no valid data struct InvalidBlockTag {}; - ParsedBlock(const InvalidBlockTag) : block_data_(), uncompressed_store_(), state_(BlockState::kInvalid) {} + ParsedBlock(const InvalidBlockTag, uint64_t block_index) : + block_index_(block_index), block_data_(), uncompressed_store_(), state_(BlockState::kInvalid) + {} // Create an unparsed block, either because the block type is unknown, or that is known, but has no // matching Args struct struct UnknownBlockTag {}; - ParsedBlock(const UnknownBlockTag, util::DataSpan&& block_data) : - block_data_(std::move(block_data)), uncompressed_store_(), state_(BlockState::kUnknown) + ParsedBlock(const UnknownBlockTag, uint64_t block_index, util::DataSpan&& block_data) : + block_index_(block_index), block_data_(std::move(block_data)), uncompressed_store_(), + state_(BlockState::kUnknown) {} // Create a block that must not be compressed with asserts on tag construction @@ -179,12 +186,14 @@ class ParsedBlock bool references_block_buffer) noexcept; template ParsedBlock(IncompressibleBlockTag, + uint64_t block_index, BlockBuffer& block_buffer, BlockReferencePolicy policy, bool references_block_buffer, ArgPayload&& args) : - block_data_(MakeIncompressibleBlockData(block_buffer, policy, references_block_buffer)), - uncompressed_store_(), dispatch_args_(MakeDispatchArgs(std::forward(args))), state_(kReady) + block_index_(block_index), + block_data_(MakeIncompressibleBlockData(block_buffer, policy, references_block_buffer)), uncompressed_store_(), + dispatch_args_(MakeDispatchArgs(std::forward(args))), state_(kReady) {} // Create a non-compressed block of a compressible block base type @@ -193,7 +202,12 @@ class ParsedBlock {}; static util::DataSpan MakeUncompressedBlockData(BlockBuffer& block_buffer, BlockReferencePolicy policy) noexcept; template - ParsedBlock(UncompressedBlockTag, BlockBuffer& block_buffer, BlockReferencePolicy policy, ArgPayload&& args) : + ParsedBlock(UncompressedBlockTag, + uint64_t block_index, + BlockBuffer& block_buffer, + BlockReferencePolicy policy, + ArgPayload&& args) : + block_index_(block_index), block_data_(MakeUncompressedBlockData(block_buffer, policy)), uncompressed_store_(), dispatch_args_(MakeDispatchArgs(std::forward(args))), state_(kReady) {} @@ -206,10 +220,12 @@ class ParsedBlock // For owned uncompressed store template ParsedBlock(DecompressedBlockTag, + uint64_t block_index, BlockBuffer& block_buffer, BlockReferencePolicy policy, UncompressedStore&& uncompressed_store, ArgPayload&& args) : + block_index_(block_index), block_data_(MakeDecompressedBlockData(block_buffer, policy)), uncompressed_store_(std::move(uncompressed_store)), dispatch_args_(MakeDispatchArgs(std::forward(args))), state_(kReady) @@ -217,8 +233,8 @@ class ParsedBlock // For unowned uncompressed store template - ParsedBlock(DecompressedBlockTag, const BlockBuffer& block_buffer, ArgPayload&& args) : - block_data_(block_buffer.MakeNonOwnedData()), uncompressed_store_(), + ParsedBlock(DecompressedBlockTag, uint64_t block_index, const BlockBuffer& block_buffer, ArgPayload&& args) : + block_index_(block_index), block_data_(block_buffer.MakeNonOwnedData()), uncompressed_store_(), dispatch_args_(MakeDispatchArgs(std::forward(args))), state_(kReady) {} @@ -228,7 +244,12 @@ class ParsedBlock static util::DataSpan MakeDeferredDecompressBlockData(BlockBuffer& block_buffer, BlockReferencePolicy policy) noexcept; template - ParsedBlock(DeferredDecompressBlockTag, BlockBuffer& block_buffer, BlockReferencePolicy policy, ArgPayload&& args) : + ParsedBlock(DeferredDecompressBlockTag, + uint64_t block_index, + BlockBuffer& block_buffer, + BlockReferencePolicy policy, + ArgPayload&& args) : + block_index_(block_index), block_data_(MakeDeferredDecompressBlockData(block_buffer, policy)), uncompressed_store_(), dispatch_args_(MakeDispatchArgs(std::forward(args))), state_(kDeferredDecompress) {} @@ -249,6 +270,9 @@ class ParsedBlock Get().data = uncompressed_store_.template GetAs(); } + // Needed for replay index based block skipping + uint64_t block_index_{ 0 }; + // The original contents of the read block (also backing store for uncompressed parameter views) util::DataSpan block_data_; diff --git a/framework/decode/preload_file_processor.cpp b/framework/decode/preload_file_processor.cpp index 94d6781be6..77f7468e81 100644 --- a/framework/decode/preload_file_processor.cpp +++ b/framework/decode/preload_file_processor.cpp @@ -24,6 +24,8 @@ #include "decode/preload_file_processor.h" #include "util/logging.h" +#include + GFXRECON_BEGIN_NAMESPACE(gfxrecon) GFXRECON_BEGIN_NAMESPACE(decode) @@ -31,90 +33,186 @@ PreloadFileProcessor::PreloadFileProcessor() {} void PreloadFileProcessor::PreloadNextFrames(size_t count) { - while (--count != 0U) + if (!IsFileValid()) { - DoProcessNextFrame([this]() { return this->PreloadBlocksOneFrame(); }); + error_state_ = CheckFileStatus(); + return; } - preload_block_data_ = std::move(pending_block_data_); -} -bool PreloadFileProcessor::PreloadBlocksOneFrame() -{ - BlockBuffer block_buffer; - bool success = true; + // Block processing will update current_frame_number_, so save and restore it, + // as callers rely on it remaining unchanged by preload. + const uint64_t save_current_frame = current_frame_number_; + bool success = true; + + // Escalate block reference policy to owned to retain backing store for preloaded blocks + auto save_reference_policy = block_parser_->GetBlockReferencePolicy(); + if (save_reference_policy == ParsedBlock::BlockReferencePolicy::kNonOwnedReference) + { + // Only need to change if was non-owned + block_parser_->SetBlockReferencePolicy(ParsedBlock::BlockReferencePolicy::kOwnedReferenceAsNeeded); + } - BlockParser block_parser([this](BlockIOError err, const char* message) { HandleBlockReadError(err, message); }, - pool_, - compressor_.get()); + // Use queue-optimized to set early decompression for "small" parsed blocks + auto save_decompression_policy = block_parser_->GetDecompressionPolicy(); + block_parser_->SetDecompressionPolicy(BlockParser::DecompressionPolicy::kQueueOptimized); - while (success) + preloaded_frames_.clear(); + preloaded_frames_.reserve(count); + + ProcessBlockState preload_result = ProcessBlockState::kFrameBoundary; + while ((count != 0U) && (preload_result == ProcessBlockState::kFrameBoundary)) { - PrintBlockInfo(); - success = ContinueDecoding(); + uint64_t current_preload_frame = current_frame_number_; + ParsedBlockQueue frame_blocks; + preload_result = PreloadBlocksOneFrame(frame_blocks); - if (success) + if (preload_result != ProcessBlockState::kError) { - success = ReadBlockBuffer(block_parser, block_buffer); - if (success) + // This is for the corner case where we are seeing an explicit frame boundary, for a file + // that assumes implicit frame boundaries on specific function call blocks. + // A WARNING is logged during block processing when this occurs. + + // We have two strategies to deal with this case: + const bool frame_stutter = (preload_result == ProcessBlockState::kFrameBoundary) && + (current_frame_number_ == current_preload_frame); + if (frame_stutter) { - // Valid checks for the presence and size of the data span matching the header - success = block_buffer.IsValid(); - - if (success) + // Deal with the frame marker after implied frame kFunctionCallBlock frame boundary case + GFXRECON_ASSERT(current_frame_number_ == (kFirstFrame + 1)); + if (preloaded_frames_.empty()) { - // Note: in order to support kExecuteBlocksFromFile in preload, we need to add special case logic - // to look for the meta data block here, and allow it to push itself on the stack, and then - // add command counting here as well. - const bool end_of_frame = block_buffer.IsFrameDelimiter(*this); - // Record the block data for replay, Moves DataSpan out of BlockBuffer, so don't use after this. - // NOTE: It is intentional to only store only the data span to make preload lightweight a possible - pending_block_data_.emplace_back(std::move(block_buffer.ReleaseData())); - if (end_of_frame) - { - // GFXRECON_LOG_INFO("Frame delimiter encountered during preload, ending frame preload."); - break; - } + // This is really part of the non-preloaded previous (first) frame, + // so immediately replay it to complete that frame + PreloadedFrame temp_frame(kFirstFrame); + temp_frame.AppendMovedBlocks(frame_blocks); + ProcessBlockState replay_result = ReplayOneFrame(temp_frame); + GFXRECON_ASSERT(replay_result == ProcessBlockState::kFrameBoundary); } else { - std::string msg = "Failed to preload block data of size " + - std::to_string(block_buffer.Header().size) + " for block type " + - format::ToString(block_buffer.Header().type); - HandleBlockReadError(kErrorReadingBlockData, msg.c_str()); + // Append the blocks leading up to the frame marker to the previous frame + // completing that frame with the stuttered frame marker + GFXRECON_ASSERT(!frame_blocks.empty()); + preloaded_frames_.back()->AppendMovedBlocks(frame_blocks); } } else { - // We can succeed at EOF, if there are more files on the stack. - success = HandleBlockEof("preload", false /* no frame or block info */); + // Normal case, just add the preloaded frame + preloaded_frames_.emplace_back(std::make_unique(current_preload_frame)); + preloaded_frames_.back()->AppendMovedBlocks(frame_blocks); + count--; } } } - return success; + // Need to remember how preloading ended to know what to do after replay completes + final_process_state_ = preload_result; + + if (count) + { + const uint64_t found = preloaded_frames_.size(); + const uint64_t total = count + found; + GFXRECON_LOG_INFO("Preload did not load all measurement frames. %" PRIu64 " frames found, %" PRIu64 " expected", + found, + total); + } + + current_preloaded_frame_ = preloaded_frames_.begin(); + + // Restore the original parser policies + block_parser_->SetBlockReferencePolicy(save_reference_policy); + block_parser_->SetDecompressionPolicy(save_decompression_policy); + + // Restore saved frame number callers expect it to be unchanged by preload + current_frame_number_ = save_current_frame; +} + +FileProcessor::ProcessBlockState PreloadFileProcessor::PreloadBlocksOneFrame(ParsedBlockQueue& frame_queue) +{ + DispatchFunction dispatch = [&frame_queue](uint64_t block_index, ParsedBlock& block) { + frame_queue.emplace_back(std::move(block)); + return ProcessBlockState::kRunning; + }; + + return ProcessBlocks(dispatch, false /* check decoder completion */); } -// Grab the block data off the front of the -bool PreloadFileProcessor::GetBlockBuffer(BlockParser& block_parser, BlockBuffer& block_buffer) +bool PreloadFileProcessor::ProcessNextFrame() { - // Quick escape - if (preload_block_data_.empty()) + // Clean up preloaded frames if we're at the end of the preloaded frames. It's done here + // so that the clean up time is not measured in the measurement-frame-range timing. + if (!preloaded_frames_.empty() && current_preloaded_frame_ == preloaded_frames_.end()) + { + preloaded_frames_.clear(); + current_preloaded_frame_ = preloaded_frames_.end(); + } + + // Passthrough if no preloaded frames. + if (preloaded_frames_.empty()) { - return Base::GetBlockBuffer(block_parser, block_buffer); + return FileProcessor::ProcessNextFrame(); } - block_buffer = BlockBuffer(std::move(preload_block_data_.front())); - preload_block_data_.pop_front(); + PreloadedFrame& frame = *(current_preloaded_frame_->get()); + ProcessBlockState process_result = ReplayOneFrame(frame); + ++current_preloaded_frame_; - // Caller expects read position just past header - // Again the pattern is to validate the header presense, not span consistency - bool success = block_buffer.SeekTo(sizeof(format::BlockHeader)); + const bool at_end = (current_preloaded_frame_ == preloaded_frames_.end()); + if (at_end) + { + if (IsFrameBoundary(process_result) && IsFrameBoundary(final_process_state_)) + { + // If we reached the end of preloaded frames on a frame boundary, increment the frame number + current_frame_number_++; + } + // Return true only if both the replay and preload are in a continue state + return ContinueProcessing(process_result) && ContinueProcessing(final_process_state_); + } - // However, preload should never add data the doesn't result in a valid block_buffer. - // Should have failed in preload. - GFXRECON_ASSERT(block_buffer.IsValid()); + if (IsFrameBoundary(process_result)) + { + current_frame_number_++; + } + return ContinueProcessing(process_result); +} + +FileProcessor::ProcessBlockState PreloadFileProcessor::ReplayOneFrame(PreloadedFrame& frame) +{ + BlockParser& block_parser = GetBlockParser(); + DispatchVisitor dispatch_visitor(decoders_, annotation_handler_); + SetDecoderFrameNumber(frame.frame_number); + + ProcessBlockState process_state = ProcessBlockState::kFrameBoundary; + for (auto& queued_block : frame.blocks) + { + uint64_t block_index = queued_block.GetBlockIndex(); + if (!ContinueDecoding(block_index, true /* check decoder completion */)) + { + process_state = ProcessBlockState::kEndProcessing; + break; + } + + // We assume that only known, vistable blocks were preloaded + GFXRECON_ASSERT(queued_block.IsVisitable()); + + bool decompressed = false; + if (queued_block.NeedsDecompression()) + { + if (!queued_block.Decompress(block_parser)) + { + process_state = ProcessBlockState::kError; + break; + } + decompressed = true; + } + + SetDecoderBlockIndex(block_index); + std::visit(dispatch_visitor, queued_block.GetArgs()); + block_index++; + } - return success; + return process_state; } GFXRECON_END_NAMESPACE(decode) diff --git a/framework/decode/preload_file_processor.h b/framework/decode/preload_file_processor.h index eb441e383a..079507ff3f 100644 --- a/framework/decode/preload_file_processor.h +++ b/framework/decode/preload_file_processor.h @@ -38,19 +38,49 @@ class PreloadFileProcessor : public FileProcessor using Base = FileProcessor; PreloadFileProcessor(); + // Returns true if there are more frames to process, false if all frames have been processed or an error has occured + bool ProcessNextFrame() override; + // Preloads *count* frames to continuous, expandable memory buffer void PreloadNextFrames(size_t count); - // Replaces ProcessBlocksOneFrame() to just read blocks into memory buffer - bool PreloadBlocksOneFrame(); private: - bool GetBlockBuffer(BlockParser& block_parser, BlockBuffer& block_buffer) override; + // Read and parse all blocks for one frame + using ParsedBlockQueue = std::deque; + using ParsedBlockReplay = std::vector; + + // This structure is optimized for most efficient replay, even at the expense + // of some preload overhead + struct PreloadedFrame + { + uint64_t frame_number; + ParsedBlockReplay blocks; + + PreloadedFrame() = default; + PreloadedFrame(PreloadedFrame&&) noexcept = default; + PreloadedFrame(const PreloadedFrame&) = delete; + PreloadedFrame& operator=(const PreloadedFrame&) = delete; + PreloadedFrame(uint64_t frame_number_) : frame_number(frame_number_), blocks() {} + + // Blocks must be iterable container of ParsedBlock, with a size() method + template + void AppendMovedBlocks(Blocks& from_blocks) + { + blocks.reserve(blocks.size() + from_blocks.size()); + blocks.insert( + blocks.end(), std::make_move_iterator(from_blocks.begin()), std::make_move_iterator(from_blocks.end())); + } + }; + using PreloadedFramePtr = std::unique_ptr; + using PreloadedFrames = std::vector; + using PreloadedFramesIt = PreloadedFrames::iterator; + + ProcessBlockState PreloadBlocksOneFrame(ParsedBlockQueue& frame_queue); + ProcessBlockState ReplayOneFrame(PreloadedFrame& frame); - // NOTE: We only need to store the block image, we can reconstitute the block header on replay. - // Given the number (sometimes 1,000's) of blocks/frame, not storing BlockBuffer's here is - // the compact choice - std::deque pending_block_data_; - std::deque preload_block_data_; + PreloadedFrames preloaded_frames_; + PreloadedFramesIt current_preloaded_frame_; // Only valid when preloaded_frames_ is not empty + ProcessBlockState final_process_state_{ ProcessBlockState::kError }; // How the last frame preload ended }; GFXRECON_END_NAMESPACE(decode) diff --git a/framework/graphics/fps_info.cpp b/framework/graphics/fps_info.cpp index 65205d6f56..34440315f8 100644 --- a/framework/graphics/fps_info.cpp +++ b/framework/graphics/fps_info.cpp @@ -80,7 +80,7 @@ bool FpsInfo::ShouldWaitIdleBeforeFrame(uint64_t frame) bool FpsInfo::ShouldQuit(uint64_t frame) { - return (quit_after_range_ && (frame > measurement_end_frame_)) || (quit_after_frame_ && frame > quit_frame_); + return (quit_after_range_ && (frame >= measurement_end_frame_)) || (quit_after_frame_ && frame > quit_frame_); } void FpsInfo::BeginFrame(uint64_t frame)