On 6/1/21 2:48 AM, Henri Verbeet wrote:
On Tue, 1 Jun 2021 at 02:12, Matteo Bruni matteo.mystral@gmail.com wrote:
Does it even make sense to use a persistently mapped GPU buffer for a resource mapped/updated by a deferred context? Obligatory disclaimer that I don't know anything about GPU-side performance, but obviously you can't reuse an earlier mapping for UpdateSubResource() or for DISCARD maps, and I'm failing to understand why an application would ever use NOOVERWRITE on a deferred context.
I don't have all the details of deferred contexts down, but I'm pretty sure that you can. All that you need to know is that the memory block you return to the application is not in use (be it by wined3d or by the underlying API / GPU) by the time the application will access it. Whether the memory is backed by a buffer object, suballocated from a larger GPU-side resource or a random chunk of system memory is an implementation detail.
NOOVERWRITE also makes perfect sense with deferred contexts. The general idea behind DISCARD and NOOVERWRITE is to gradually "fill" a larger resource over a number of draws, without requiring any synchronization between the draws. This is probably largely or entirely obvious but I'll go through the whole story to make sure we're on the same page.
The natural use case goes like this: the application wants to draw some dynamic geometry (i.e. generated at runtime, with variable primitive count). At initialization time it creates a couple large buffers (e.g. 1 MB each), one for vertices and one for indices. Then at runtime it maps both with DISCARD, fills them with whatever data is necessary for the current draw (e.g. vertex and index data for 100 triangles), keeping track of how much memory is still available in either buffer, unmaps and draws. After a while it decides that it needs to draw some more dynamic stuff. Now it does something very similar to the previous time, except if there is still some free space available it will map the buffer with NOOVERWRITE and "append" to the buffer, writing the new data after the area used by the first draw. NOOVERWRITE is a promise to d3d to not touch the data in use by any previous draw, which in practice means that the implementation can return the same memory as the previous map, rather than blocking to make sure that any pending draw using the same buffer is completed. Eventually the application will run out of space from one buffer or the other. No problem, at that point the application will map the relevant buffer with DISCARD and restart filling the buffer from the top. Here DISCARD is a different promise to d3d: the application tells d3d that it won't try to make use of the old contents of the buffer. What it implies for the implementation is that an entirely new "physical" buffer can be made available to the application, which will take the place of the previous "physical" buffer once pending draws are done with. Afterwards the application will resume mapping the buffer with NOOVERWRITE for the following dynamic draws, until the buffer fills up again and so on and so forth. Worth mentioning is that the "new" buffer that the implementation can return on a DISCARD map might in fact be an old buffer, for example coming from a buffer discarded some time earlier, currently unused and sitting idle. Or it might be a portion of a larger physical buffer that the implementation knows it's not in use.
Of course applications can and often do find ways to use those primitives more creatively, but this is the basic idea. Also, this works just as well for immediate or for deferred contexts.
Although you could certainly argue that since deferred contexts/command lists are created in advance, there should be no need for NOOVERWRITE maps, and the application could simply fill the entire resource (or at least, the part it's going to use) with the initial DISCARD map. (The case for DISCARD maps vs UpdateSubResource() is perhaps a little more subtle; when using GPU buffers, DISCARD maps would allow you to avoid a copy.) I suspect it's mostly for convenience; NOOVERWRITE maps on deferred contexts allow applications to render in the same way to deferred contexts as they would to immediate contexts.
I think that's mostly what was confusing me about it, yeah, but on reflection it would make sense that the application doesn't actually know all of the data it's going to upload in advance :-/