LUMIERA.clone/src/steam/engine/buffer-provider.cpp
Ichthyostega 75913725a8 BufferProvider: sharpen criterion for bool(BuffHandle)
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«
2026-02-16 01:44:38 +01:00

307 lines
10 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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