This refacotring prepares for the planned changes in `OutputSlot` * introduce a new compound parameter (tuple) BufferAlloc * wire this through all calls involved with storage handling __Background__: * `BufferMetadata` deemed important for the implementation of buffer storage * ''new requirement detected'': must be able to handle an actual storage block that is ''larger'' than the requested buffer size (⟼ tiling) * need compact and uniform info, also for the `BufferProxyAdaptor` callbacks
344 lines
12 KiB
C++
344 lines
12 KiB
C++
/*
|
||
BufferProvider - Abstraction for Buffer management during playback/render
|
||
|
||
Copyright (C)
|
||
2008, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation; either version 2 of the License, or (at your
|
||
option) any later version. See the file COPYING for further details.
|
||
|
||
* *****************************************************************/
|
||
|
||
|
||
/** @file buffer-provider.cpp
|
||
** Implementation details related to buffer allocation and lifecycle management in the render engine.
|
||
** This translation unit comprises the "back side" of the public front-end, where calls from the
|
||
** client are received and relayed to the appropriate part of the implementation back-end.
|
||
** Furthermore, some of the API functions on the [Buffer Descriptor](\ref BuffDescr) and the
|
||
** [Buffer Handle](\ref BuffHandle) are also directly implemented here, by forwarding them
|
||
** through the related implementation APIs. The actual backend implementation however is
|
||
** split out and isolated by the internal interfaces BufferProvider::BufferStage (for the
|
||
** state and lifecycle management) and BufferProvider::BufferStore (for memory handling).
|
||
**
|
||
** The actual backend implementations to use are configured in subclasses.
|
||
*/
|
||
|
||
|
||
#include "lib/error.hpp"
|
||
#include "vault/mem/buffer-provider.hpp"
|
||
#include "vault/mem/buffer-metadata.hpp"
|
||
#include "lib/util.hpp"
|
||
|
||
#include <utility>
|
||
|
||
using std::move;
|
||
using util::isSameAdr;
|
||
|
||
namespace vault {
|
||
namespace mem {
|
||
|
||
// storage for the default-marker constants
|
||
const TypeHandler TypeHandler::RAW{};
|
||
const LocalTag LocalTag::UNKNOWN{};
|
||
const metadata::Key metadata::Key::INVALID{HashVal(0), size_t(0)}; //////OOO consider to relocate those marker constants to somewhere more obvious
|
||
|
||
|
||
LUMIERA_ERROR_DEFINE (BUFFER_MANAGEMENT, "Problem providing working buffers");
|
||
|
||
|
||
|
||
BufferProvider::~BufferProvider() { }
|
||
|
||
|
||
/** @internal verify the given descriptor.
|
||
* @return true if it corresponds to a buffer
|
||
* currently locked and usable by client code
|
||
*/
|
||
bool
|
||
BufferProvider::isValid (HashVal id) const
|
||
{
|
||
return bool (bufferStage_->lookup (id));
|
||
}
|
||
|
||
bool
|
||
BufferProvider::isAllotted (HashVal id) const
|
||
{
|
||
return bufferStage_->isAllotted (id);
|
||
}
|
||
|
||
bool
|
||
BufferProvider::isAccessible (HashVal id) const
|
||
{
|
||
return bufferStage_->isAccessible (id);
|
||
}
|
||
|
||
size_t
|
||
BufferProvider::getBufferSize (HashVal id) const
|
||
{
|
||
auto& key = bufferStage_->lookup (id);
|
||
return key.storageSize();
|
||
}
|
||
|
||
|
||
|
||
BuffDescr
|
||
BufferProvider::getDescriptorFor (size_t storageSize)
|
||
{
|
||
auto& typeKey = bufferStage_->defineBufferType (storageSize, TypeHandler::RAW);
|
||
return buildDescriptor (typeKey);
|
||
}
|
||
|
||
|
||
BuffDescr
|
||
BufferProvider::getDescriptorFor(size_t storageSize, TypeHandler specialTreatment)
|
||
{
|
||
auto& typeKey = bufferStage_->defineBufferType (storageSize, move (specialTreatment));
|
||
return buildDescriptor (typeKey);
|
||
}
|
||
|
||
|
||
/** BufferProvider API: declare in advance the need for working buffers.
|
||
* This optional call allows client code to ensure the availability of the
|
||
* necessary working space, prior to starting the actual operations. The
|
||
* client may reasonably assume to get the actual number of buffers, as
|
||
* indicated by the return value. A provider may be able to handle
|
||
* various kinds of buffers (e.g. of differing size), which are
|
||
* distinguished by _the type embodied into_ the BuffDescr.
|
||
* @return maximum number of simultaneously usable buffers of this type,
|
||
* to be retrieved later through calls to #lockBuffer.
|
||
* @throw error::State when no buffer of this kind can be provided
|
||
* @note the returned count may differ from the requested count.
|
||
*/
|
||
uint
|
||
BufferProvider::announce (uint cnt, BuffDescr const& type)
|
||
{
|
||
size_t buffSiz = getBufferSize (type);
|
||
uint actually_possible = bufferStore_->prepareBuffers (type, cnt, buffSiz);
|
||
if (!actually_possible)
|
||
throw err::State ("unable to fulfil request for buffers"
|
||
,LUMIERA_ERROR_BUFFER_MANAGEMENT);
|
||
return actually_possible;
|
||
}
|
||
|
||
|
||
/** BufferProvider API: retrieve a single buffer for exclusive use.
|
||
* This call actually claims a buffer of this type and marks it for
|
||
* use by client code. Performs the necessary metadata state transition
|
||
* leading from an abstract buffer type to a derived metadata::Entry
|
||
* that corresponds to an actual buffer and has a lifecycle state.
|
||
* This entry is marked as _locked_ for exclusive use by one client.
|
||
* The returned handle allows to query state and perform further
|
||
* state transitions (_emitted_, _released_), yet provides no
|
||
* automatic tracking and clean-up. The client is explicitly
|
||
* responsible to invoke #releaseBuffer (either on the
|
||
* BufferProvider, or directly on the BuffHandle).
|
||
* @return a copyable handle, representing this buffer and this usage transaction.
|
||
* @throw error::State when unable to provide this buffer
|
||
* @note this function may be used right away, without prior announcing, but then
|
||
* the client should be prepared for exceptions. The #announce operation allows
|
||
* to establish a reliably available baseline.
|
||
* @todo 2/2025 might need to accept a cache key as additional parameter //////////////////////////////////TICKET #1392 : get cache key from computation to the cache backend
|
||
* @todo 2/2026 my intention is to use metadata::Entry within a threadsafe allocator
|
||
* to pass buffers around. For this to be possible, buffer-metadata.hpp needs
|
||
* some refactoring, and then the BufferStore::provideBuffer would have to
|
||
* return a metadata::Entry
|
||
*/
|
||
BuffHandle
|
||
BufferProvider::lockBuffer (BuffDescr const& type)
|
||
{
|
||
REQUIRE (was_created_by_this_provider (type));
|
||
auto& typeKey = bufferStage_->lookup (type);
|
||
auto [storage, actualSize, localTag] = bufferStore_->provideBuffer (type,typeKey.storageSize(),typeKey.localTag());
|
||
auto& stateKey = bufferStage_->mark_locked (typeKey, storage, localTag);
|
||
|
||
return BuffHandle (buildDescriptor(stateKey), storage);
|
||
} // NOTE: not the underlying parent descriptor!
|
||
|
||
|
||
/** BufferProvider API: state transition to _emitted state_.
|
||
* Client code may signal a state transition through this optional operation.
|
||
* The actual meaning of an "emitted" buffer is implementation defined; similarly,
|
||
* some back-ends may actually do something when emitting a buffer (e.g. commit data
|
||
* to cache), while others just set a flag or do nothing at all. This state transition
|
||
* may be invoked at most once per locked buffer.
|
||
* @throw error::Fatal in case of invalid state transition sequence. Only a locked buffer
|
||
* may be emitted, and at most once.
|
||
* @warning by convention, emitting a buffer implies that the contained data is ready and
|
||
* might be used by other parts of the application.
|
||
* While an emitted buffer should not be modified anymore, it can still be accessed.
|
||
*/
|
||
void
|
||
BufferProvider::emitBuffer (BuffHandle const& handle)
|
||
{
|
||
auto& stateKey = bufferStage_->mark_emitted (handle);
|
||
bufferStore_->mark_emitted (stateKey.parentKey()
|
||
,BuffAlloc{handle.rawStorage()
|
||
,stateKey.storageSize()
|
||
,stateKey.localTag()});
|
||
}
|
||
|
||
|
||
/** BufferProvider API: declare buffer usage as completed and detach from storage.
|
||
* Client code is required to release _each previously locked buffer_ eventually.
|
||
* @warning invalidates the BuffHandle, clients mustn't access the buffer anymore.
|
||
* Right after releasing, an access through the handle will throw;
|
||
* yet the buffer might be re-used and the handle become valid
|
||
* later on accidentally.
|
||
* @note EX_FREE
|
||
*/
|
||
void
|
||
BufferProvider::releaseBuffer (BuffHandle& handle)
|
||
try { // might invoke embedded dtor function
|
||
auto& stateKey = bufferStage_->mark_released (handle);
|
||
bufferStore_->detachBuffer (stateKey.parentKey()
|
||
,BuffAlloc{handle.rawStorage()
|
||
,stateKey.storageSize()
|
||
,stateKey.localTag()});
|
||
bufferStage_->discard (handle);
|
||
}
|
||
ERROR_LOG_AND_IGNORE (engine, "releasing a buffer from BufferProvider")
|
||
|
||
|
||
/** @internal abort normal lifecycle, reset the underlying buffer and detach from it.
|
||
* This allows to break out of normal usage and reset the handle to _invalid state_
|
||
* @param invokeDtor if possibly the clean-up function of an TypeHandler registered with
|
||
* the buffer metadata should be invoked prior to resetting the metadata state.
|
||
* Default is \em not to invoke anything
|
||
* @note EX_FREE
|
||
*/
|
||
void
|
||
BufferProvider::emergencyCleanup (BuffHandle& handle, bool invokeDtor)
|
||
try {
|
||
auto& stateKey = bufferStage_->abandon (handle, invokeDtor);
|
||
bufferStore_->detachBuffer (stateKey.parentKey()
|
||
,BuffAlloc{handle.rawStorage()
|
||
,stateKey.storageSize()
|
||
,stateKey.localTag()});
|
||
bufferStage_->discard (handle);
|
||
}
|
||
ERROR_LOG_AND_IGNORE (engine, "cleanup of buffer metadata while handling an error")
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/* === BuffDescr and BuffHandle === */
|
||
|
||
BuffDescr
|
||
BufferProvider::buildDescriptor (HashVal key)
|
||
{
|
||
return {*this, key};
|
||
}
|
||
|
||
bool
|
||
BufferProvider::was_created_by_this_provider (BuffDescr const& descr) const
|
||
{
|
||
return isSameAdr (this, descr.provider_);
|
||
}
|
||
|
||
/**
|
||
* A Buffer Descriptor is considered _valid_
|
||
* iff it represents a registered buffer type.
|
||
* @note this implies that the buffer size is not zero.
|
||
*/
|
||
bool
|
||
BuffDescr::isValid() const
|
||
{
|
||
ENSURE (provider_);
|
||
return provider_->isValid (*this);
|
||
}
|
||
|
||
/**
|
||
* A concrete Buffer Handle is considered valid,
|
||
* iff it is currently registered in the buffer metadata table,
|
||
* and the corresponding metadata::Entry indicates LOCKED state,
|
||
* but not yet emitted (or released).
|
||
* @note this implies also that it is in some active state, and
|
||
* indirectly (due to the _Buffer Provider Protocol_) also
|
||
* that the _parent type_ is valid, which means that it was
|
||
* created from a valid BuffDescr. But note that we can not
|
||
* see this parent type without lookup by the BufferProvider;
|
||
* The embedded #descriptor_ is synthetic, and reflects the
|
||
* handle's _state-key_ (⟶ registry Entry) not the type-key.
|
||
*/
|
||
bool
|
||
BuffHandle::isValid() const
|
||
{
|
||
ENSURE (descriptor_.provider_);
|
||
return bool(pBuffer_)
|
||
and descriptor_.provider_->isAccessible (*this);
|
||
}
|
||
|
||
bool
|
||
BuffHandle::isAllotted() const
|
||
{
|
||
ENSURE (descriptor_.provider_);
|
||
return bool(pBuffer_)
|
||
and descriptor_.provider_->isAllotted (*this);
|
||
}
|
||
|
||
|
||
size_t
|
||
BuffDescr::buffSize() const
|
||
{
|
||
ENSURE (provider_);
|
||
return provider_->getBufferSize (*this);
|
||
}
|
||
|
||
size_t
|
||
BuffHandle::size() const
|
||
{
|
||
return descriptor_.buffSize();
|
||
}
|
||
|
||
uint
|
||
BuffDescr::announce (uint count)
|
||
{
|
||
ENSURE (provider_);
|
||
return provider_->announce(count, *this);
|
||
}
|
||
|
||
|
||
BuffHandle
|
||
BuffDescr::lockBuffer()
|
||
{
|
||
ENSURE (provider_);
|
||
return provider_->lockBuffer(*this);
|
||
}
|
||
|
||
|
||
void
|
||
BuffHandle::emit()
|
||
{
|
||
REQUIRE (isValid());
|
||
descriptor_.provider_->emitBuffer(*this);
|
||
}
|
||
|
||
|
||
void
|
||
BuffHandle::release()
|
||
{
|
||
if (pBuffer_)
|
||
{
|
||
REQUIRE (isAllotted());
|
||
descriptor_.provider_->releaseBuffer(*this);
|
||
pBuffer_ = 0;
|
||
}
|
||
ENSURE (!isValid());
|
||
}
|
||
|
||
|
||
void
|
||
BuffHandle::emergencyCleanup()
|
||
{
|
||
descriptor_.provider_->emergencyCleanup(*this); // EX_FREE
|
||
pBuffer_ = 0;
|
||
}
|
||
|
||
|
||
|
||
}} // namespace vault::mem
|