LUMIERA.clone/src/steam/engine/buffer-provider.hpp
Ichthyostega c3ebade5d2 BufferProvider: investigate and fix broken allocation
Since the BufferStore implementation component is now supposed
to handle only the memory allocation, a complete end-to end data flow
must be ensured, so that allocation / deallocation functions get all
the relevant information. Notably, the `LocalTag` is now used as an
implementation-defined conduit for further (opaque) marker data.

Since all data passed over this API are simple values (even `LocalTag`
is effectively a 64bit integer), we can now avoid relying on output
parameters or similar side-effect tricks.
2026-02-13 03:17:11 +01:00

253 lines
12 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.

/*
BUFFER-PROVIDER.hpp - Abstraction for Buffer management during playback/render
Copyright (C)
2011, 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.hpp
** Abstraction to represent buffer management and lifecycle within the render engine.
** When looking at the render engine implementation at a high level, buffers are a scarce
** resource to be managed. Looking into the implementation in detail however reveals — maybe
** surprisingly — that we never need _direct access_ to the buffers holding media data. This is
** a consequence of the Engine's design, which treats _actual media processing_ as an external concern,
** handled by specialised libraries and Plug-ins. Buffers are thus treated as some entity to be _managed_,
** i.e. "allocated", "locked" and "released"; the actual meaning of these operations is considered a detail
** of the implementation.
**
** The code within the Render Engine is centred around _invocation_ and the transport of data; the latter is
** marked and represented as BufferHandle, that can be passed form invocation to invocation. For this to work,
** the _type_ of the buffers must be compatible with the data and the processing functions — and this condition
** is accommodated already in the preparation of the rendering, which is the purpose of the Builder. In this
** framework, BufferHandle objects act as a front-end, as being created by and linked to a BufferProvider
** implementation. There is no need to manage the lifecycle of buffers explicitly because the use of buffers
** is embedded into the fabric of the render calculation, which follows a rather strict protocol anyway.
** Relying on the capabilities of the scheduler, the sequence of individual jobs in the engine ensures...
** - that the availability of sufficient buffer memory was accommodated as part of the planning
** - that overall buffer capacity will be announced at start of the actual render invocation
** - that a buffer handle was obtained ("locked") prior to any operation requiring a buffer
** - that buffers are marked as free ("released") after doing the actual calculations.
**
** # Buffer Provider Protocol
**
** BufferProvider is a public interface, that also defines an interaction protocol.
** This implies that any usage of _buffer memory_ has to proceed through the stages of a process,
** which is flexible enough however to accommodate the various usage situations. At the bare minimum,
** a buffer can be _obtained and locked_, to get a fixed amount of memory, accessible through a BuffHandle.
** After usage, the buffer _must be released._ There is no tracking and no automatisation to ensure this
** duty is fulfilled — failure to release will block the buffer memory indeterminately. Usually this
** simple arrangement does not cause any serious problems, as the process of rendering media is
** bound to operate within very precisely defined boundaries anyway. Furthermore, the C++
** language offers various general-purpose capabilities to automate resource clean-up.
**
** For the general case, the usage cycle is defined to be more elaborate and flexible:
** - in a pre-planning step, a [Buffer Type Descriptor](\ref BuffDescr) can be obtained
** + this descriptor is copyable and embeds a back-link to the BufferProvider
** + obtaining such a descriptor implies a _type registration_
** + in this context a **Buffer Type** describes at least a fixed amount of storage,
** and optionally also an _access type,_ or even an attached _object creation_ and
** automatic _destruction_.
** - from a given BuffDescr, the usage of a number of buffers can optionally be _announced;_
** doing so is highly recommended, since it confirms the availability of an actual number
** of buffers (which can be lower than requested); these buffers are guaranteed to be
** available, and this availability will even be pre-arranged for the actual thread,
** as an asynchronous process
** - for the actual usage, individual buffers can be _locked_ on the BuffDescr, thereby
** providing a BuffHandle. The number of actually obtained buffers need not match the
** announced number (yet obviously it should, overall). Buffers exceeding the announced
** capacity _can_ be obtained, if possible — in that case the operation may block or
** even fail with an exception.
** - a BuffHandle, once obtained, represents the client's ownership and liability
** - optionally, the client may _emit_ on the BuffHandle, to indicate that data
** in the Buffer is complete and will not be touched anymore. The interpretation
** of this call is reserved to special services attached behind the BufferProvider
** interface, e.g. a Cache service or an DataSink.
** - as the last step, a BuffHandle _must_ be _released_. It must not be used further
** after that point, since the buffer memory might have been re-assigned to another
** client, or way even released altogether.
**
** # Providing an Implementation
**
** The class BufferProvider is abstract, yet contains some orchestration and clue code
** to provide the high-level operations. Functionality is broken down in terms of
** two _implementation interfaces:_
** - BufferProvider::BufferStage describes the type registration and state transitions
** - BufferProvider::BufferStore handles coordination of memory access
**
** For creating actual implementations, buffer-provider-setup.hpp defines a framework.
** A simplistic heap-based implementation for demonstration and unit test usage is
** available through NaiveBufferSetup. This can be extended with tracking functionality
** to verify actual operations from a test setup, with the help of DiagnosticBufferProvider.
** The [Render Engine setup](\ref engine-facilities.hpp) includes a suitable BufferProvider
** implementation; when used from the actual [Render Environment](\ref render-environment.hpp),
** this will be backed by a production-strenght and concurrency-safe implementation.
**
** @see buffer-provider-protocol-test.cpp
** @see output-slot.hpp
** @see engine-ctx.hpp
** @see proc-node.hpp
** @see media-weaving-pattern.hpp
** @see weaving-pattern-builder.hpp
*/
#ifndef STEAM_ENGINE_BUFFR_PROVIDER_H
#define STEAM_ENGINE_BUFFR_PROVIDER_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/hash-value.h"
#include "steam/engine/buffhandle.hpp"
//#include "steam/engine/engine-ctx.hpp"
#include "steam/engine/type-handler.hpp"
#include "steam/engine/buffer-local-tag.hpp"
#include "steam/engine/buffer-metadata.hpp" /////////////////////////OOO must be removed from here
#include "lib/nocopy.hpp"
#include <utility>
#include <memory>
#include <tuple>
namespace steam {
namespace engine {
using lib::Literal;
using std::unique_ptr;
using std::forward;
class BufferMetadata;
LUMIERA_ERROR_DECLARE (BUFFER_MANAGEMENT); ///< Problem providing working buffers
/**
* Interface: a facility providing and managing working buffers for media calculations.
* The pointer to actual buffer storage can be retrieved by
* - optionally announcing the required buffer(s) beforehand
* - "locking" a buffer to yield a buffer handle
* - then dereferencing the obtained smart-handle
*
* @warning all of BufferProvider is assumed to run within a threadsafe environment.
*
* @todo as of 6/2011 buffer management within the engine is still a bit vague
* @todo as of 11/11 thread safety within the engine remains to be clarified ////////////////////////////TICKET #854
*/
class BufferProvider
: util::NonCopyable
{
protected: /* === for Implementation by concrete providers === */
public:
virtual ~BufferProvider(); ///< this is an ABC
uint announce (uint count, BuffDescr const&);
BuffHandle lockBuffer (BuffDescr const&);
void emitBuffer (BuffHandle const&);
void releaseBuffer (BuffHandle const&);
template<typename BU, typename...ARGS>
BuffHandle lockBufferFor (ARGS ...args);
/** allow for attaching and owing an object within an already created buffer */
void attachTypeHandler (BuffHandle const& target, BuffDescr const& reference);
void emergencyCleanup (BuffHandle const& target, bool invokeDtor =false);
/** describe the kind of buffer managed by this provider */
BuffDescr getDescriptorFor(size_t storageSize=0);
BuffDescr getDescriptorFor(size_t storageSize, TypeHandler specialTreatment);
template<typename BU, typename...ARGS>
BuffDescr getDescriptor (ARGS ...args);
/* === API for BuffHandle internal access === */
bool verifyValidity (BuffDescr const&) const;
size_t getBufferSize (HashVal typeID) const;
protected:
/// placeholder marker type for an actual data buffer
using Buff = StreamType::ImplFacade::DataBuffer;
BuffHandle buildHandle (HashVal typeID, Buff* storage, LocalTag =LocalTag::UNKNOWN);
bool was_created_by_this_provider (BuffDescr const&) const;
class BufferStage
: public BufferMetadata /////////////////////////////////////////////////////////////////////TICKET #1410 : mix-in exiting old implementation -- for sake of refactoring only!
{
public:
virtual ~BufferStage() { } ///< this is an interface
using BufferMetadata::BufferMetadata; ///////////////////////////////////////////////////////////TICKET #1410 : actual implementation structures should move down into this classes implementation
};
class BufferStore
: util::NonCopyable
{
protected:
using Slot = std::tuple<Buff*,LocalTag>;
public:
virtual ~BufferStore() { } ///< this is an interface
virtual uint prepareBuffers (uint cnt, size_t,HashVal typeID) =0;
virtual Slot provideBuffer (size_t,HashVal typeID, LocalTag) =0;
virtual void mark_emitted (size_t,HashVal, LocalTag const&) =0;
virtual void detachBuffer (size_t,HashVal, Slot alloc) =0;
};
unique_ptr<BufferStage> bufferStage_; ///////////////////////////////////////////////////////////TICKET #1410 : must be turned into an internal interface
unique_ptr<BufferStore> bufferStore_;
};
/* === Implementation === */
/** convenience shortcut:
* prepare and claim ("lock") a buffer suitable
* to hold an object of the given type.
* @return a handle embedding a suitably configured
* buffer descriptor. The corresponding buffer
* has been allocated and marked for exclusive use
*/
template<typename BU, typename...ARGS>
BuffHandle
BufferProvider::lockBufferFor (ARGS ...args)
{
BuffDescr attach_object_automatically = getDescriptor<BU> (forward<ARGS> (args)...);
return lockBuffer (attach_object_automatically);
}
/** define a "buffer type" for automatically creating
* an instance of the template type embedded into the buffer
* and destroying that embedded object when releasing the buffer.
*/
template<typename BU, typename...ARGS>
BuffDescr
BufferProvider::getDescriptor (ARGS ...args)
{
return getDescriptorFor (sizeof(BU), TypeHandler::create<BU> (forward<ARGS> (args)...));
}
}} // namespace steam::engine
#endif /*STEAM_ENGINE_BUFFR_PROVIDER_H*/