On my Steam Deck, this reduces the time it takes to initialize the wg_parser radically (by around 1 second). This helps in the game WILD HEARTS, which doesn't present while loading help videos during gameplay, causing large stutters.
Because GStreamer can randomly access the file with no known pattern on our side, I opted to implement this by buffering 4 chunks so that interleaved reads to different areas of the file don't cause us to discard and reload cached data more than we need to.
-- v5: winegstreamer: Cache wg_parser input data.
From: Derek Lesho dlesho@codeweavers.com
In order to reduce wg_parser initialization time by skipping the round-trip to the PE thread.
Signed-off-by: Derek Lesho dlesho@codeweavers.com --- dlls/winegstreamer/wg_parser.c | 174 +++++++++++++++++++++++++++++---- 1 file changed, 153 insertions(+), 21 deletions(-)
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index a8da149e7be..7eb26fe57fa 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -59,6 +59,12 @@ GST_DEBUG_CATEGORY(wine);
typedef BOOL (*init_gst_cb)(struct wg_parser *parser);
+struct input_cache_chunk +{ + guint64 position; + uint8_t *data; +}; + struct wg_parser { init_gst_cb init_gst; @@ -96,7 +102,10 @@ struct wg_parser bool unlimited_buffering;
gchar *sink_caps; + + struct input_cache_chunk input_cache_chunks[4]; }; +static const unsigned int input_cache_chunk_size = 512 << 10;
struct wg_parser_stream { @@ -946,30 +955,10 @@ static void pad_removed_cb(GstElement *element, GstPad *pad, gpointer user) g_free(name); }
-static GstFlowReturn src_getrange_cb(GstPad *pad, GstObject *parent, - guint64 offset, guint size, GstBuffer **buffer) +static GstFlowReturn issue_read_request(struct wg_parser *parser, guint64 offset, guint size, GstBuffer **buffer) { - struct wg_parser *parser = gst_pad_get_element_private(pad); GstFlowReturn ret;
- GST_LOG("pad %p, offset %" G_GINT64_MODIFIER "u, size %u, buffer %p.", pad, offset, size, *buffer); - - if (offset == GST_BUFFER_OFFSET_NONE) - offset = parser->next_pull_offset; - parser->next_pull_offset = offset + size; - - if (!size) - { - /* asfreader occasionally asks for zero bytes. gst_buffer_map() will - * return NULL in this case. Avoid confusing the read thread by asking - * it for zero bytes. */ - if (!*buffer) - *buffer = gst_buffer_new_and_alloc(0); - gst_buffer_set_size(*buffer, 0); - GST_LOG("Returning empty buffer."); - return GST_FLOW_OK; - } - pthread_mutex_lock(&parser->mutex);
assert(!parser->read_request.size); @@ -996,6 +985,140 @@ static GstFlowReturn src_getrange_cb(GstPad *pad, GstObject *parent, return ret; }
+static struct input_cache_chunk * get_cache_entry(struct wg_parser *parser, guint64 position) +{ + struct input_cache_chunk chunk; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(parser->input_cache_chunks); i++) + { + chunk = parser->input_cache_chunks[i]; + + if (chunk.data && position == chunk.position) + { + if (i != 0) + { + memmove(&parser->input_cache_chunks[1], &parser->input_cache_chunks[0], i * sizeof(chunk)); + parser->input_cache_chunks[0] = chunk; + } + + return &parser->input_cache_chunks[0]; + } + } + + return NULL; +} + +static GstFlowReturn read_cached_chunk(struct wg_parser *parser, guint64 chunk_position, unsigned int chunk_offset, GstBuffer *buffer, guint64 buffer_offset) +{ + struct input_cache_chunk *chunk; + GstBuffer *chunk_buffer; + void *chunk_data; + GstFlowReturn ret; + + if ((chunk = get_cache_entry(parser, chunk_position))) + { + if (!!gst_buffer_fill(buffer, buffer_offset, chunk->data + chunk_offset, input_cache_chunk_size - chunk_offset)) + return GST_FLOW_OK; + else + return GST_FLOW_ERROR; + } + + chunk = &parser->input_cache_chunks[ ARRAY_SIZE(parser->input_cache_chunks) - 1 ]; + + if (!(chunk_data = chunk->data)) + chunk_data = malloc(input_cache_chunk_size); + + chunk_buffer = gst_buffer_new_wrapped_full(0, chunk_data, input_cache_chunk_size, 0, input_cache_chunk_size, NULL, NULL); + ret = issue_read_request(parser, chunk_position, input_cache_chunk_size, &chunk_buffer); + gst_buffer_unref(chunk_buffer); + + if (ret != GST_FLOW_OK) + { + if (!chunk->data) + free(chunk_data); + return ret; + } + + memmove(&parser->input_cache_chunks[1], &parser->input_cache_chunks[0], (ARRAY_SIZE(parser->input_cache_chunks) - 1) * sizeof(*chunk)); + parser->input_cache_chunks[0].data = chunk_data; + parser->input_cache_chunks[0].position = chunk_position; + + chunk = &parser->input_cache_chunks[0]; + if (!!gst_buffer_fill(buffer, buffer_offset, chunk->data + chunk_offset, input_cache_chunk_size - chunk_offset)) + return GST_FLOW_OK; + else + return GST_FLOW_ERROR; +} + +static GstFlowReturn read_input_cache(struct wg_parser *parser, guint64 offset, guint size, GstBuffer **buffer) +{ + unsigned int i, chunk_count, chunk_offset, buffer_offset = 0; + GstBuffer *working_buffer; + guint64 chunk_position; + GstFlowReturn ret; + + working_buffer = *buffer; + if (!working_buffer) + working_buffer = gst_buffer_new_and_alloc(size); + + chunk_position = offset - (offset % input_cache_chunk_size); + chunk_count = (offset + size + input_cache_chunk_size - chunk_position - 1) / input_cache_chunk_size; + chunk_offset = offset - chunk_position; + + for (i = 0; i < chunk_count; i++) + { + if ((ret = read_cached_chunk(parser, chunk_position, chunk_offset, working_buffer, buffer_offset)) != GST_FLOW_OK) + { + if (!*buffer) + gst_buffer_unref(working_buffer); + return ret; + } + + chunk_position += input_cache_chunk_size; + buffer_offset += input_cache_chunk_size - chunk_offset; + chunk_offset = 0; + } + + *buffer = working_buffer; + return GST_FLOW_OK; +} + +static GstFlowReturn src_getrange_cb(GstPad *pad, GstObject *parent, + guint64 offset, guint size, GstBuffer **buffer) +{ + struct wg_parser *parser = gst_pad_get_element_private(pad); + + GST_LOG("pad %p, offset %" G_GINT64_MODIFIER "u, size %u, buffer %p.", pad, offset, size, *buffer); + + if (offset == GST_BUFFER_OFFSET_NONE) + offset = parser->next_pull_offset; + parser->next_pull_offset = offset + size; + + if (!size) + { + /* asfreader occasionally asks for zero bytes. gst_buffer_map() will + * return NULL in this case. Avoid confusing the read thread by asking + * it for zero bytes. */ + if (!*buffer) + *buffer = gst_buffer_new_and_alloc(0); + gst_buffer_set_size(*buffer, 0); + GST_LOG("Returning empty buffer."); + return GST_FLOW_OK; + } + + if (size >= input_cache_chunk_size || sizeof(void*) == 4) + return issue_read_request(parser, offset, size, buffer); + + if (offset >= parser->file_size) + return GST_FLOW_EOS; + + if ((offset + size) >= parser->file_size) + size = parser->file_size - offset; + + return read_input_cache(parser, offset, size, buffer); +} + static gboolean src_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) { struct wg_parser *parser = gst_pad_get_element_private(pad); @@ -1541,6 +1664,15 @@ static NTSTATUS wg_parser_disconnect(void *args) g_free(parser->sink_caps); parser->sink_caps = NULL;
+ for (i = 0; i < ARRAY_SIZE(parser->input_cache_chunks); i++) + { + if (parser->input_cache_chunks[i].data) + { + free(parser->input_cache_chunks[i].data); + parser->input_cache_chunks[i].data = NULL; + } + } + return S_OK; }
Cool 👍, just pushed w/ a rebase/addressed nits in case that doesn't come through.
This merge request was approved by Zebediah Figura.