[openal] Buffer mapping
Chris Robinson
chris.kcat at gmail.com
Sun Jan 21 18:21:30 EST 2018
Hello,
Recent updates for OpenAL Soft have added functions to directly map a
buffer's storage, which enables a way to fill a buffer without
unnecessary copying. However, given some issues with it, I'm looking for
feedback.
Firstly, this creates an issue related to formats. Obviously, to
directly write into or read from a buffer's storage, the format and size
should match what was originally given to alBufferData. This works fine
for 8-bit, 16-bit, 32-bit float, and now even alaw and mulaw. But
something like ADPCM gets converted on load, so mapping it won't work as
expected.
There are a few options I can think of to deal with this. The obvious
one is to double-buffer: keep a copy of the original data and use
separate storage for the converted samples, converting whenever the
original data is modified. This breaks the zero-copy behavior, though
given that it needs to convert, it can't be completely avoided. But it
also wastes memory when the original copy is no longer needed.
Alternatively, it could allocate some temporary memory. This helps save
memory when the mapping is no longer needed, but it would be slower
since it needs to allocate memory when mapping. For a readable mapping,
it also needs to fill/re-encode the mapped memory, which brings the
implication that the data given back isn't necessarily the same as what
was previously given (assuming either a lossy encoding or a higher
bit-depth than it's stored as).
The other option is to disallow mapping such formats. This helps ensure
mapping a buffer doesn't do anything weird behind the app's back, but
means the app can't be sure a given format can be mapped without testing
it. This is the cleanest from the implementation's perspective, though
slightly burdening on the app.
Second, currently the ability to map a buffer is declared by OR'ing some
flags with the format (if you want to be able to map a buffer for
writing, you first call alBufferData(bid, format | AL_MAP_WRITE_BIT,
...) then can later call alMapBuffer with AL_MAP_WRITE_BIT). Presuming
the format enums occupy 24 bits (all current ones do, AFAIK), the access
flags can fit in the upper 8 bits. This works fine and avoids having to
make another function, though it does put a limit on the number of
potential flags alBufferData can take (8), so I'm not sure if it would
be preferable to have a new function that takes an explicit flags parameter.
There's also the question of persistent mapping, or more generally,
allowing a buffer to be mapped that's in use. Reading has the benefit of
allowing an app to analyze the audio that's currently playing without
holding its own copy, or to read back a buffer's samples regardless if
it's currently in use or not. Writing has the benefit of more efficient
streaming as samples are allocated all in one chunk which is
periodically written to, rather than juggling and refilling multiple
buffers (though multiple buffers does offer slightly better underrun
behavior).
However, given that memory is asynchronously read in the mixer, allowing
the app to write to it creates coherency problems. Without some
guarantee of atomicity with reading and writing the same area, it's not
just a case of 'randomly' reading one value or the other, but it can be
literal junk that resembles neither what it was or what it will be
(particularly a problem for floats; invalid bit patterns could create
NaNs which have a habit of propagating uncontrollably if they get into a
feedback loop or IIR filter). Maybe this is fine with a stern warning of
"don't write where the mixer is reading" (and a flush function that acts
as a memory barrier to "commit" what was written), but I don't know.
Thoughts?
More information about the openal
mailing list