doing so requires to trace a new call path from front to back, into the `BufferMetadata` implementation: there is now an explicit check that the buffer is in `LOCKED` state, but not yet `EMITTED` and als that it was not yet `released()` I think the additional effort is justified * in the regular / currently intended usage in the Render Engine, this predicate is not required, since compliance to the ''Buffer Provider Protocol'' as such is sufficient to ensure that * but since all of this is getting more generic and polished now, I could imagine future usages beyond the current confines; and then, this semantics is what you'd expect from such a `bool()` check: »I am allowed to use it«
307 lines
10 KiB
C++
307 lines
10 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 lifecycle management in the render engine.
|
||
*/
|
||
|
||
|
||
#include "lib/error.hpp"
|
||
#include "steam/engine/buffer-provider.hpp"
|
||
#include "steam/engine/buffer-metadata.hpp" /////////////////////////OOO must be removed from here
|
||
#include "steam/engine/simple-buffer-state-registry.hpp" /////////////OOO better use constexpr for hash-chaining?
|
||
#include "lib/util.hpp"
|
||
|
||
using util::isSameAdr;
|
||
|
||
namespace steam {
|
||
namespace engine {
|
||
|
||
// 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
|
||
|
||
namespace { // impl. details and definitions
|
||
|
||
const uint DEFAULT_DESCRIPTOR = 0;
|
||
|
||
}
|
||
|
||
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::isAccessible (HashVal id) const
|
||
{
|
||
return bufferStage_->isUsageAllowed (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 BuffDescr (*this, typeKey);
|
||
}
|
||
|
||
|
||
BuffDescr
|
||
BufferProvider::getDescriptorFor(size_t storageSize, TypeHandler specialTreatment)
|
||
{
|
||
auto& typeKey = bufferStage_->defineBufferType (storageSize, move (specialTreatment));
|
||
return BuffDescr (*this, 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 (cnt, buffSiz, type);
|
||
if (!actually_possible)
|
||
throw error::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
|
||
*/
|
||
BuffHandle
|
||
BufferProvider::lockBuffer (BuffDescr const& type)
|
||
{
|
||
REQUIRE (was_created_by_this_provider (type));
|
||
auto& typeKey = bufferStage_->lookup (type);
|
||
auto [storage, localTag] = bufferStore_->provideBuffer (typeKey.storageSize(), type, typeKey.localTag());
|
||
auto& stateKey = bufferStage_->mark_locked (typeKey, storage, localTag);
|
||
|
||
return BuffHandle (BuffDescr(*this, HashVal(stateKey)), storage);
|
||
} // NOTE: not the underlying parent descriptor!
|
||
|
||
|
||
/** BufferProvider API: state transition to \em 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.storageSize(), stateKey.parentKey(), 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 const& handle)
|
||
try { // might invoke embedded dtor function
|
||
auto& stateKey = bufferStage_->mark_released (handle);
|
||
bufferStore_->detachBuffer (stateKey.storageSize(), stateKey.parentKey()
|
||
,std::make_tuple (handle.rawStorage(), 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 const& handle, bool invokeDtor)
|
||
try {
|
||
auto& stateKey = bufferStage_->abandon (handle, invokeDtor);
|
||
bufferStore_->detachBuffer (stateKey.storageSize(), stateKey.parentKey()
|
||
,std::make_tuple (handle.rawStorage(), stateKey.localTag()));
|
||
bufferStage_->discard (handle);
|
||
}
|
||
ERROR_LOG_AND_IGNORE (engine, "cleanup of buffer metadata while handling an error")
|
||
|
||
|
||
|
||
bool
|
||
BufferProvider::was_created_by_this_provider (BuffDescr const& descr) const
|
||
{
|
||
return isSameAdr (this, descr.provider_);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
/* === BuffDescr and BuffHandle === */
|
||
|
||
/**
|
||
* 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);
|
||
}
|
||
|
||
|
||
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 (isValid());
|
||
descriptor_.provider_->releaseBuffer(*this);
|
||
pBuffer_ = 0;
|
||
}
|
||
ENSURE (!isValid());
|
||
}
|
||
|
||
|
||
void
|
||
BuffHandle::emergencyCleanup()
|
||
{
|
||
descriptor_.provider_->emergencyCleanup(*this); // EX_FREE
|
||
pBuffer_ = 0;
|
||
}
|
||
|
||
|
||
|
||
}} // namespace engine
|