[XMA] Reserve headroom per decoded frame and account for it in gating
The 3-bit field at +24bit in DWORD 1 of the XMA context was named subframe_skip_count but never actually used for skipping subframes. Testing across multiple titles (PGR4, Halo Reach) shows the field might control extra output buffer blocks that must be reserved per decoded frame. Renaming the field to output_buffer_padding to reflect this observed behavior and adding padding to the minimum output space threshold Pdding blocks are reserved from the output budget after each frame is fully consumed, preventing the decoder from overrunning the space that the game expects to remain free. This is still likely not entirely correct but reduces some of the observed noise in current implementation.
This commit is contained in:
@@ -68,7 +68,11 @@ struct XMA_CONTEXT_DATA {
|
|||||||
uint32_t loop_subframe_skip : 3; // +17bit, XMASetLoopData might be
|
uint32_t loop_subframe_skip : 3; // +17bit, XMASetLoopData might be
|
||||||
// subframe_decode_count
|
// subframe_decode_count
|
||||||
uint32_t subframe_decode_count : 4; // +20bit
|
uint32_t subframe_decode_count : 4; // +20bit
|
||||||
uint32_t subframe_skip_count : 3; // +24bit
|
uint32_t output_buffer_padding : 3; // +24bit, extra output buffer blocks
|
||||||
|
// reserved per decoded frame
|
||||||
|
// NOTE(has207): this is pure guess
|
||||||
|
// but that's how we're using it
|
||||||
|
// currently
|
||||||
uint32_t sample_rate : 2; // +27bit enum of sample rates
|
uint32_t sample_rate : 2; // +27bit enum of sample rates
|
||||||
uint32_t is_stereo : 1; // +29bit
|
uint32_t is_stereo : 1; // +29bit
|
||||||
uint32_t unk_dword_1_c : 1; // +30bit
|
uint32_t unk_dword_1_c : 1; // +30bit
|
||||||
|
|||||||
@@ -131,14 +131,6 @@ bool XmaContextNew::Work() {
|
|||||||
|
|
||||||
if (data.IsConsumeOnlyContext()) {
|
if (data.IsConsumeOnlyContext()) {
|
||||||
Consume(&output_rb, &data);
|
Consume(&output_rb, &data);
|
||||||
// Clearing contexts that match TightBufferOutput heuristic can disrupt
|
|
||||||
// playback (e.g. audio noise during races in PGR4), so we only clear
|
|
||||||
// contexts with enough output buffer headroom where empty input reliably
|
|
||||||
// indicates the stream is finished (e.g. needed for dialog completion in
|
|
||||||
// Borderlands 2 startup).
|
|
||||||
// NOTE(has207): this feels wrong to have such a heuristic but it seems to
|
|
||||||
// work for the known cases. May need to be revisited if failing cases are
|
|
||||||
// found.
|
|
||||||
if (!data.HasTightOutputBuffer() &&
|
if (!data.HasTightOutputBuffer() &&
|
||||||
data.output_buffer_read_offset == data.output_buffer_write_offset) {
|
data.output_buffer_read_offset == data.output_buffer_write_offset) {
|
||||||
ClearLocked(&data);
|
ClearLocked(&data);
|
||||||
@@ -148,7 +140,8 @@ bool XmaContextNew::Work() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int32_t minimum_subframe_decode_count =
|
const int32_t minimum_subframe_decode_count =
|
||||||
(kBytesPerFrameChannel / kOutputBytesPerBlock) << data.is_stereo;
|
((kBytesPerFrameChannel / kOutputBytesPerBlock) << data.is_stereo) +
|
||||||
|
data.output_buffer_padding;
|
||||||
|
|
||||||
// We don't have enough space to even make one pass
|
// We don't have enough space to even make one pass
|
||||||
// Waiting for decoder to return more space.
|
// Waiting for decoder to return more space.
|
||||||
@@ -165,12 +158,12 @@ bool XmaContextNew::Work() {
|
|||||||
minimum_subframe_decode_count) {
|
minimum_subframe_decode_count) {
|
||||||
XELOGAPU(
|
XELOGAPU(
|
||||||
"XmaContext {}: Write Count: {}, Capacity: {} - {} {} Subframes: {} "
|
"XmaContext {}: Write Count: {}, Capacity: {} - {} {} Subframes: {} "
|
||||||
"Skip: {}",
|
"Padding: {}",
|
||||||
id(), (uint32_t)output_rb.write_count(),
|
id(), (uint32_t)output_rb.write_count(),
|
||||||
remaining_subframe_blocks_in_output_buffer_,
|
remaining_subframe_blocks_in_output_buffer_,
|
||||||
data.input_buffer_0_valid + (data.input_buffer_1_valid << 1),
|
data.input_buffer_0_valid + (data.input_buffer_1_valid << 1),
|
||||||
data.output_buffer_valid, data.subframe_decode_count,
|
data.output_buffer_valid, data.subframe_decode_count,
|
||||||
data.subframe_skip_count);
|
data.output_buffer_padding);
|
||||||
|
|
||||||
Decode(&data);
|
Decode(&data);
|
||||||
Consume(&output_rb, &data);
|
Consume(&output_rb, &data);
|
||||||
@@ -273,12 +266,19 @@ void XmaContextNew::Consume(RingBuffer* XE_RESTRICT output_rb,
|
|||||||
const int8_t raw_frame_read_offset =
|
const int8_t raw_frame_read_offset =
|
||||||
((kBytesPerFrameChannel / kOutputBytesPerBlock) << data->is_stereo) -
|
((kBytesPerFrameChannel / kOutputBytesPerBlock) << data->is_stereo) -
|
||||||
current_frame_remaining_subframes_;
|
current_frame_remaining_subframes_;
|
||||||
// + data->subframe_skip_count;
|
|
||||||
|
|
||||||
output_rb->Write(
|
output_rb->Write(
|
||||||
raw_frame_.data() + (kOutputBytesPerBlock * raw_frame_read_offset),
|
raw_frame_.data() + (kOutputBytesPerBlock * raw_frame_read_offset),
|
||||||
subframes_to_write * kOutputBytesPerBlock);
|
subframes_to_write * kOutputBytesPerBlock);
|
||||||
remaining_subframe_blocks_in_output_buffer_ -= subframes_to_write;
|
|
||||||
|
// Reserve extra blocks as headroom when unk_skip_decode is set.
|
||||||
|
// Only apply when the frame is fully consumed to avoid double-counting.
|
||||||
|
const int8_t headroom =
|
||||||
|
(current_frame_remaining_subframes_ - subframes_to_write == 0)
|
||||||
|
? data->output_buffer_padding
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
remaining_subframe_blocks_in_output_buffer_ -= subframes_to_write + headroom;
|
||||||
current_frame_remaining_subframes_ -= subframes_to_write;
|
current_frame_remaining_subframes_ -= subframes_to_write;
|
||||||
|
|
||||||
XELOGAPU("XmaContext {}: Consume: {} - {} - {} - {} - {}", id(),
|
XELOGAPU("XmaContext {}: Consume: {} - {} - {} - {} - {}", id(),
|
||||||
|
|||||||
Reference in New Issue
Block a user