DYT/Tool/OpenSceneGraph-3.6.5/include/osgEarth/GLUtils

1023 lines
36 KiB
Plaintext
Raw Normal View History

2024-12-24 23:49:36 +00:00
/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2020 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#pragma once
#include <osgEarth/Common>
#include <osgEarth/optional>
#include <osgEarth/Math>
#include <osg/StateSet>
#include <osg/OperationThread>
#include <osg/GraphicsContext>
#include <osg/GLObjects>
#include <osg/Drawable>
#include <osg/Texture2D>
#include <osg/ContextData>
#include <osg/State>
#include <set>
#ifndef GLintptr
#define GLintptr std::intptr_t
#endif
#ifndef GLsizeiptr
#define GLsizeiptr std::uintptr_t
#endif
#ifndef GL_DYNAMIC_STORAGE_BIT
#define GL_DYNAMIC_STORAGE_BIT 0x0100
#endif
namespace osgUtil {
class StateToCompile;
}
#define OE_GL_ZONE osgEarth::ScopedGLDebugGroup __oe_glscope(typeid(*this).name())
#define OE_GL_ZONE_NAMED(X) osgEarth::ScopedGLDebugGroup __oe_glscope(X)
#define OE_GL_PUSH(X) if (GLUtils::isGLDebuggingEnabled()) GLUtils::pushDebugGroup(X)
#define OE_GL_POP if (GLUtils::isGLDebuggingEnabled()) GLUtils::popDebugGroup()
namespace osgEarth
{
struct OSGEARTH_EXPORT GLUtils
{
//! Sets any default uniforms required by the implementation
static void setGlobalDefaults(osg::StateSet* stateSet);
//! Configure lighting (GL_LIGHTING)
static void setLighting(osg::StateSet* stateSet, osg::StateAttribute::OverrideValue ov);
//! Configure line width (GL_LINE_WIDTH)
static void setLineWidth(osg::StateSet* stateSet, float value, osg::StateAttribute::OverrideValue ov);
//! Configure line stippling (GL_LINE_STIPPLE)
static void setLineStipple(osg::StateSet* stateSet, int factor, unsigned short pattern, osg::StateAttribute::OverrideValue ov);
//! Configure line antialiasing (GL_LINE_SMOOTH)
static void setLineSmooth(osg::StateSet* stateSet, osg::StateAttribute::OverrideValue ov);
//! Configure point rendering size (GL_POINT_SIZE)
static void setPointSize(osg::StateSet* stateSet, float value, osg::StateAttribute::OverrideValue ov);
//! Configure point rounding/antialiasing (GL_POINT_SMOOTH)
static void setPointSmooth(osg::StateSet* stateSet, osg::StateAttribute::OverrideValue ov);
//! Removes the state associated with a GL capability, causing it to inherit from above.
//! and if one of: GL_LIGHTING, GL_LINE_WIDTH, GL_LINE_STIPPLE, GL_LINE_SMOOTH, GL_POINT_SIZE
static void remove(osg::StateSet* stateSet, GLenum cap);
//! Enables global GL debugging
static void enableGLDebugging();
//! Whether global GL debugging is enabled
static bool isGLDebuggingEnabled() { return _gldebugging; }
//! Push a GL debugging group
static void pushDebugGroup(const char* name);
//! Pop a GL debugging group
static void popDebugGroup();
//! Whether to use NVGL if available
static void useNVGL(bool value);
//! Whether NVGL is requested AND supported.
static bool useNVGL();
//! Unique ID associated with this State object (and by extension
//! its unique graphics context). You can use this to track
//! GL objects that cannot be used across shared GCs.
static unsigned getUniqueStateID(const osg::State& state);
//! Graphics context ID shared between all states sharing
//! the same graphics context. Only use for GL object that can
//! be shared by multiple graphics contexts (multi-window setup)
static unsigned getSharedContextID(const osg::State& state);
private:
static bool _gldebugging;
static bool _useNVGL;
};
/**
* Scope-based GL debug group
*/
struct ScopedGLDebugGroup
{
ScopedGLDebugGroup(const char* name) { OE_GL_PUSH(name); }
~ScopedGLDebugGroup() { OE_GL_POP; }
};
struct OSGEARTH_EXPORT CustomRealizeOperation : public osg::Operation
{
virtual void operator()(osg::Object*);
void setSyncToVBlank(bool);
optional<bool> _vsync;
optional<bool> _gldebug;
};
struct OSGEARTH_EXPORT GL3RealizeOperation : public CustomRealizeOperation
{
virtual void operator()(osg::Object*);
};
//! A draw command for indirect rendering
struct DrawElementsIndirectCommand
{
GLuint count = 0u; // how many indices comprise this draw command
GLuint instanceCount = 1u; // how many instances of the geometry to draw
GLuint firstIndex = 0u; // index of the first element in the EBO to use
GLuint baseVertex = 0u; // offset to add to each element index (lets us use USHORT even when >65535 verts)
GLuint baseInstance = 0u; // offset to instance # when fetching vertex attrs (does NOT affect gl_InstanceID)
};
//! A dispatch command for indirect compute
struct DispatchIndirectCommand
{
GLuint num_groups_x = 1u;
GLuint num_groups_y = 1u;
GLuint num_groups_z = 1u;
};
//! A pointer record for bindless buffer usage
struct BindlessPtrNV
{
GLuint index = 0u; // unused
GLuint reserved = 0u; // unused
GLuint64 address = 0u; // GPU address of bindless buffer
GLuint64 length = 0u; // length in bytes of bindless buffer
};
//! A draw command for NVIDIA bindless buffer draws with a single VBO
struct DrawElementsIndirectBindlessCommandNV
{
DrawElementsIndirectCommand cmd;
GLuint reserved = 0u;
BindlessPtrNV indexBuffer;
BindlessPtrNV vertexBuffer;
};
//! template for fetching an object that is stored per-State; this would typically
//! be something that needs different data for each camera/view.
class OSGEARTH_EXPORT PerStateGLObjects
{
public:
template<typename T>
static inline T& get(osg::buffered_object<T>& arr, const osg::State& state) {
return arr[GLUtils::getUniqueStateID(state)];
}
};
//! template for fetching an object that is shareable across GL context
//! because it is bindless
class OSGEARTH_EXPORT BindlessShareableGLObjects
{
public:
template<typename T>
static inline T& get(osg::buffered_object<T>& arr, const osg::State& state) {
return arr[GLUtils::getSharedContextID(state)];
}
};
//! Base class for GL object containers
class OSGEARTH_EXPORT GLObject
{
public:
using Ptr = std::shared_ptr<GLObject>;
using Compatible = std::function<bool(GLObject*)>;
//! GL object "name" (an integer returned from glGen*)
GLuint name() const { return _name; }
//! debug category
const std::string& category() const { return _category; }
//! debug uid
const std::string& uid() const { return _uid; }
//! full debug label (unique ID if set)
std::string label() const {
return category() + (uid().empty() ? "" : (":" + uid()));
}
//! namespace (GL_BUFFER, GL_TEXTURE, etc)
GLenum ns() const { return _ns; }
//! whether this object can be re-used without reallocation
bool recyclable() const { return _recyclable; }
//! whether this object can be shared between shared GCs
bool shared() const { return _shared; }
//! number of times this object's been recycled
unsigned recycles() const { return _recycles; }
//! true if this object is OK to use
bool valid() const { return _name != 0 && _ext != nullptr; }
//! OSG extensions API
osg::GLExtensions* ext() const { return _ext; }
//! GC under which this object was created
const osg::GraphicsContext* gc() const { return _gc; }
//! Sets the GL debugging category and unique ID
void debugLabel(
const std::string& category,
const std::string& uniqueid = "");
public:
virtual void release() = 0;
virtual GLsizei size() const = 0;
protected:
GLObject(GLenum ns, osg::State& state);
GLuint _name = 0u; // GL name assigned on creation
std::string _uid; // debugging unique id (optional)
std::string _category; // debugging category (optional)
GLenum _ns = (GLenum)0; // object namespace
bool _shared = false; // is this object shared by all GCs?
bool _recyclable = false; // can this object by re-used?
unsigned _recycles = 0u; // number of times this object has been recycled
osg::GLExtensions* _ext = nullptr;
osg::GraphicsContext* _gc = nullptr;
unsigned _orphan_frames = 0u;
friend class GLObjectPool;
struct Resident {
bool value = false;
operator bool() const { return value; }
};
};
class OSGEARTH_EXPORT GLQuery : public GLObject
{
public:
using Ptr = std::shared_ptr<GLQuery>;
//! Create a new query object.
static Ptr create(GLenum target, osg::State& state);
//! Start the query
void begin();
//! Are the results ready to read?
bool isReady() const;
//! End the query and fetch its value.
//! This will stall the pipeline if the result is not ready yet.
//! Call isReady() to check.
void getResult(GLuint* result);
//! End a query. Usually called after getResult.
void end();
public:
void release() override;
GLsizei size() const override { return sizeof(GLuint); }
private:
GLQuery(GLenum target, osg::State& state);
GLenum _target;
bool _active;
};
class OSGEARTH_EXPORT GLVAO : public GLObject
{
public:
using Ptr = std::shared_ptr<GLVAO>;
//! Create a new VAO.
static Ptr create(osg::State& state);
//! Bind the VAO.
void bind();
//! Unbind the VAO. (bind to 0)
void unbind();
public:
void release() override;
GLsizei size() const override { return sizeof(GLuint); }
private:
GLVAO(osg::State& state);
};
//! A buffer object
class OSGEARTH_EXPORT GLBuffer : public GLObject
{
public:
using Ptr = std::shared_ptr<GLBuffer>;
//! Creates a new unallocated buffer.
static Ptr create(GLenum target, osg::State& state);
//! Creates a new unallocated buffer that can be shared by OSG states
//! within the same OpenGL context.
static Ptr create_shared(GLenum target, osg::State& state);
//! Creates a new unallocated buffer that is tied to a specific OSG state.
//! The "size hint" allows it to potentially be created from recycled
//! The "chunk size" is an alignment value to improve recycling efficiency
//! GPU memory.
static Ptr create(GLenum target, osg::State& state, GLsizei sizeHint, GLsizei chunkSize = 1);
//! Creates a new unallocated buffer that can be shared by OSG states
//! within the same OpenGL context.
//! The "size hint" allows it to potentially be created from recycled
//! The "chunk size" is an alignment value to improve recycling efficiency
//! GPU memory.
static Ptr create_shared(GLenum target, osg::State& state, GLsizei sizeHint, GLsizei chunkSize = 1);
//! Bind this buffer to target() in the active context.
void bind() const;
//! bind to something other than target()
void bind(GLenum target) const;
//! binds this buffer's target to zero.
void unbind() const;
//! target to which this buffer is bound
GLenum target() const { return _target; }
//! allocated size of this buffer, from a call to bufferData or bufferStorage
GLsizei size() const override { return _alloc_size; }
//! de-allocate and release this buffer
virtual void release();
//! Upload data to the GPU. This method uses bufferData() or bufferSubData()
//! depending on whether it needs to allocate more space.
//! Automatically calls bind()/unbind().
void uploadData(GLsizei size, const GLvoid* data, GLbitfield flags=GL_DYNAMIC_DRAW) const;
//! Convenience template to upload a vector
template<class T>
void uploadData(const std::vector<T>& v, GLbitfield flags = GL_DYNAMIC_DRAW) {
uploadData(v.size() * sizeof(T), v.data(), flags);
}
//! Convenience template to upload a vector to a custom target
template<class T>
void uploadData(GLenum target, std::vector<T>& v, GLbitfield flags = GL_DYNAMIC_DRAW) {
uploadData(target, v.size() * sizeof(T), v.data(), flags);
}
//! glBufferData - reallocate entire buffer
void bufferData(GLsizei size, const GLvoid* data, GLbitfield flags = GL_DYNAMIC_DRAW) const;
//! Convenience template to reallocate and upload a collection
template<class T>
void bufferData(const T& v, GLbitfield flags) const {
bufferData(v.size() * sizeof(T::value_type), v.data(), flags);
}
//! glBufferSubData - upload a subset of data to the GPU
void bufferSubData(GLintptr offset, GLsizei size, const GLvoid* data) const;
//! Convenience template to subdata a typed collection
template<class T>
void bufferSubData(const T& v) const {
bufferSubData(0, v.size() * sizeof(T::value_type), v.data());
}
//! glBufferStorage - allocate with immutable storage
//! @param buffersize The size of the buffer AND the data in bytes
//! @param data The data to upload
void bufferStorage(GLsizei datasize, const GLvoid* data, GLbitfield flags = 0) const;
//! glBufferStorage - allocate with immutable storage
//! @param buffersize The size of the buffer in bytes
//! @param datasize The size of the data in bytes
//! @param data The data to upload
void bufferStorage(GLsizei buffersize, GLsizei datasize, const GLvoid* data, GLbitfield flags = 0) const;
//! map the buffer to a pointer
void* map(GLbitfield access) const;
//! map a range of the buffer to a pointer
void* mapRange(GLintptr offset, GLsizei length, GLbitfield access) const;
//! unmap a pointer mapped with map()
void unmap() const;
//! GPU copy this buffer to another buffer
void copyBufferSubData(GLBuffer::Ptr dest, GLintptr readOffset, GLintptr writeOffset, GLsizei size) const;
//! Read data from the buffer to the CPU
void getBufferSubData(GLintptr offset, GLsizei size, void* ptr) const;
//! bind the buffer to the layout index as specified in the shader
//! (for SSBO/UBO/ACBO/TFBO only)
void bindBufferBase(GLuint index) const;
//! Creates and/or returns the GPU address of this buffer
//! (for use with bindless buffers only)
GLuint64 address();
//! Makes a bindless buffer resident within the state's context/
//! Note: You must make a bindless buffer resident in each
//! graphics context in which you use it (spec).
void makeResident(osg::State&);
//! Make a bindless buffer non-resident within the state's context.
void makeNonResident(osg::State&);
//! Align a value for the storage target.
size_t align(size_t val);
//! Sets the storage alignment for this buffer. Calls to bufferData will align
//! to the next multiple of this value. Higher values can help with recycling.
void setChunkSize(GLsizei value);
protected:
GLBuffer(GLenum target, osg::State& state);
GLenum _target = (GLenum)0;
GLuint64 _address = 0ULL; // bindless GPU address
mutable GLsizei _alloc_size = 0;
mutable bool _immutable = false;
GLsizei _chunk_size = 1;
#ifdef OSGEARTH_SINGLE_GL_CONTEXT
mutable Resident _isResident;
#else
mutable std::unordered_map<const osg::GraphicsContext*, Resident> _isResident;
#endif
};
//! A texture object with optional resident handle
class OSGEARTH_EXPORT GLTexture : public GLObject
{
public:
using Ptr = std::shared_ptr<GLTexture>;
struct OSGEARTH_EXPORT Profile : public osg::Texture::TextureProfile
{
Profile(GLenum target);
Profile(
GLenum target,
GLint numMipmapLevels,
GLenum internalFormat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLint minFilter,
GLint magFilter,
GLint wrapS,
GLint wrapT,
GLint wrapR,
GLfloat maxAnisotropy);
GLint _minFilter = GL_LINEAR;
GLint _magFilter = GL_LINEAR;
GLint _wrapS = GL_CLAMP_TO_EDGE;
GLint _wrapT = GL_CLAMP_TO_EDGE;
GLint _wrapR = GL_CLAMP_TO_EDGE;
GLfloat _maxAnisotropy = 1.0f;
bool operator == (const Profile& rhs) const;
};
//! Creates a new GL texture object.
static Ptr create(
GLenum target,
osg::State& state);
//! Creates a new GL texture object.
//! The profileHint describes its configuration so it can potentially
//! be made from recycled GPU memory.
static Ptr create(
GLenum target,
osg::State& state,
const Profile& profileHint);
//! Binds this texture to `target`
void bind(osg::State& state);
//! Returns the GPU address of a bindless texture
GLuint64 handle(osg::State& state);
//! Toggles the bindless texture residency in the unique
//! graphics context associated with `state`.
void makeResident(const osg::State& state, bool toggle);
//! Whether the texture is resident in the unique
//! graphics context associated with `state`.
bool isResident(const osg::State& state) const;
void release();
const Profile& profile() const { return _profile; }
std::string& id() { return _id; }
GLsizei size() const override { return _size; }
void storage2D(const Profile& profile);
void storage3D(const Profile& profile);
void subImage2D(GLint level, GLint xoff, GLint yoff, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) const;
void subImage3D(GLint level, GLint xoff, GLint yoff, GLint zoff, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) const;
void compressedSubImage2D(GLint level, GLint xoff, GLint yoff, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) const;
void compressedSubImage3D(GLint level, GLint xoff, GLint yoff, GLint zoff, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) const;
protected:
GLTexture(GLenum target, osg::State& state);
private:
void reset(GLenum, const std::string&, const std::string&, osg::State&);
GLenum _target = (GLenum)0;
GLuint64 _handle = 0ULL;
std::string _id;
GLsizei _size = 0;
Profile _profile;
#ifdef OSGEARTH_SINGLE_GL_CONTEXT
mutable Resident _isResident;
#else
mutable std::unordered_map<const osg::GraphicsContext*, Resident> _isResident;
#endif
};
/**
* GL Framebuffer object - for rendering to a texture.
*/
class OSGEARTH_EXPORT GLFBO : public GLObject
{
public:
using Ptr = std::shared_ptr<GLFBO>;
using DrawFunction = std::function<void(osg::State&)>;
static Ptr create(
osg::State& state);
//! Render something to a texture using this FBO.
GLTexture::Ptr renderToTexture(
GLsizei width,
GLsizei height,
DrawFunction draw,
osg::State& state);
public: // GLObject
void release() override;
GLsizei size() const override;
protected:
GLFBO(osg::State&);
};
/**
* Mechanism that will automatically delete a GL object
* when its container goes out of scope. The object pool uses
* a deferred deletion policy (of one frame) to ensure an object
* is not deleted before the GL pipeline has finished using it.
*/
class OSGEARTH_EXPORT GLObjectPool : public osg::GraphicsObjectManager
{
public:
using Collection = std::vector<GLObject::Ptr>;
//! construct an object pool under the given graphics context ID
GLObjectPool(unsigned contextID);
//! Whether to support recycling of GL objects with identical properties
static void setEnableRecycling(bool value) { _enableRecycling = value; }
//! Fetch the object pool for the graphics context represented
//! by the state object
static GLObjectPool* get(osg::State& state);
//! Release all GL objects that were created in the given state's
//! graphics context
static void releaseGLObjects(osg::State* state);
//! Start watching a GL object for automatic release
void watch(GLObject::Ptr);
//! Report the total memory size of all objects in the pool
GLsizeiptr totalBytes() const;
//! Get a vector of all extant object pools
static std::unordered_map<int, GLObjectPool*> getAll();
//! Set the target memory size to attempt to delete per frame
static inline void setBytesToDeletePerFrame(unsigned value) { _bytes_to_delete_per_frame = value; }
static inline unsigned getBytesToDeletePerFrame() { return _bytes_to_delete_per_frame; }
unsigned recycleHits() const { return _hits; }
unsigned recycleMisses() const { return _misses; }
Collection objects() const;
//! Attempt to grab an existing object from the recycling bin and re-use it.
//! Returns nullptr if no compatible object is found.
template<typename T, typename PREDICATE>
typename T::Ptr recycle(PREDICATE&& is_compatible)
{
if (!_enableRecycling)
return {};
std::lock_guard<std::mutex> lock(_mutex);
typename T::Ptr result;
for (auto& object : _objects)
{
// an object can be recycled if and only if:
// - the use_count() is 1, meaning no one else is using it
// - the object's GPU memory is allocated and usable ("valid")
// - the object's configuration is compatible with the request. For example,
// you can only recycle an RGBA texture to another RGBA texture of the same
// dimensions and format; and you can only recycle and SSBO into another SSBO
// of the same size.
if (object.use_count() == 1 && object->valid() && is_compatible(object.get()))
{
result = std::dynamic_pointer_cast<T>(object);
++_hits;
++result->_recycles;
result->_orphan_frames = 0u;
break;
}
}
if (result == nullptr)
++_misses;
return result;
}
void releaseOrphans(const osg::GraphicsContext* gc);
public: //osg::GraphicsObjectManager
void flushDeletedGLObjects(double now, double& avail) override;
void flushAllDeletedGLObjects() override;
void deleteAllGLObjects() override;
void discardAllGLObjects() override;
protected:
mutable std::mutex _mutex;
Collection _objects; // objects being monitored
GLsizeiptr _totalBytes = 0;
unsigned _hits = 0u;
unsigned _misses = 0u;
// note: these values affect the availability of memory for recycling.
static bool _enableRecycling;
static unsigned _bytes_to_delete_per_frame;
static unsigned _frames_to_delay_deletion;
std::vector<const osg::GraphicsContext*> _gcs;
//! Start tracking object lifetime for a GC
void track(osg::GraphicsContext*);
//! Release all objects created in the provided GC
void releaseAll(const osg::GraphicsContext*);
};
/**
* Interface class for OSG GL functions
*/
class OSGEARTH_EXPORT OSG_GL_API
{
public:
virtual void apply(osg::State& state) const { }
virtual void compileGLObjects(osg::State& state) const = 0;
virtual void resizeGLObjectBuffers(unsigned maxsize) = 0;
virtual void releaseGLObjects(osg::State* state) const = 0;
};
// State attribute containing an object with the OSG GL API
class OSGEARTH_EXPORT StateAttributeAdapter : public osg::StateAttribute
{
public:
StateAttributeAdapter(OSG_GL_API* object) : _object(object) { }
void apply(osg::State& state) const override
{
if (_object)
_object->apply(state);
}
void resizeGLObjectBuffers(unsigned maxSize) override
{
if (_object)
_object->resizeGLObjectBuffers(maxSize);
}
void releaseGLObjects(osg::State* state) const override
{
if (_object)
_object->releaseGLObjects(state);
}
META_StateAttribute(osgEarth, StateAttributeAdapter, (osg::StateAttribute::Type)12131416);
StateAttributeAdapter() : _object(nullptr) { }
StateAttributeAdapter(const StateAttributeAdapter& rhs, const osg::CopyOp& op) { }
int compare(const osg::StateAttribute& rhs) const override { return -1; }
private:
OSG_GL_API* _object;
};
/**
* An osg::Operation can takes a lambda function and runs
* on a graphics context.
*/
class OSGEARTH_EXPORT GPUOperation : public osg::Operation
{
public:
using Function = std::function<bool(osg::State&)>;
GPUOperation(const std::string& name, Function func) :
osg::Operation(name, true),
_func(func) {
//nop
}
GPUOperation(Function func) :
osg::Operation("osgEarth::GPUOperation", true),
_func(func) {
//nop
}
void operator()(osg::Object* obj) override
{
if (getKeep())
{
setKeep(_func(*static_cast<osg::GraphicsContext*>(obj)->getState()));
}
}
private:
Function _func;
};
/**
* API for launching GPU thread jobs. Any function dispatched here will
* execute on the OSG graphics thread and return a future result.
*
* NOTE: This implementation will run the job under an arbitrary graphics
* context. So it is not currently suitable for operations that must be
* executed on multiple contexts.
*
* Example usage (graphics thread operation returning a bool):
*
* // Dispatch the asynchronous job:
* GPUJob<bool>::Result result = GPUJob<bool>::dispatch(
* [=](osg::State* state, Cancelable* progress)
* {
* // do something
* return bool;
* }
* );
*
* // Block until the result is ready:
* bool value = result.get();
*/
template<typename RESULT_TYPE>
class GPUJob
{
public:
//! Result type - future that will eventually contain the return value
using Result = jobs::future<RESULT_TYPE>;
//! Function type of async job
using Function = std::function<RESULT_TYPE(osg::State*, Cancelable*)>;
//! Dispatch the asynchronous function.
//! @param function Function to execute in the graphics thread
//! @return Future result value. If this object goes out of scope,
//! the job may by canceled.
static Result dispatch(const Function& function);
};
/**
* GL pipeline for asynchronous GPU tasks.
*
* Dispatch a job on the GPU like so:
*
* auto task =
* [&](osg::State& state, Promise<MyObject>& promise, int invocation)
* {
* MyObject result;
* // do some GPU work
* ...
* promise.resolve(result);
* return false;
* };
*
* Future<MyObject> job = GLPipeline::get(state)->dispatch<MyObject>(task);
*
* Return "true" from the function to request another invocation.
* Each invocation will increment the "invocation" argument so you
* can run multi-pass operations that don't stall the GL pipeline.
*/
class OSGEARTH_EXPORT GLPipeline
{
public:
using Ptr = std::shared_ptr<GLPipeline>;
using WeakPtr = std::weak_ptr<GLPipeline>;
// Delegate function that fulfills a promise of type T.
// Return "true" from the delegate to re-run the delegate.
// Each run of the delegate will increase the "invocation"
// by one, to support multi-pass computation.
// Return "false" from the delegate when finished.
template<typename T>
using Delegate = std::function<bool(
osg::State& state,
jobs::promise<T>& promise,
int invocation)>;
public:
//! Gets the GL pipeline for a State
static GLPipeline::Ptr get(osg::State& state);
private:
// Internal delegation operation for the GC queue:
template<typename T>
struct DelegateOperation : public osg::Operation {
Delegate<T> _delegate;
jobs::promise<T> _promise;
int _invocation;
DelegateOperation(Delegate<T> d) :
osg::Operation("GLPipeline", true),
_delegate(d),
_invocation(0) { }
void operator()(osg::Object* obj) {
if (getKeep()) {
auto gc = static_cast<osg::GraphicsContext*>(obj);
setKeep(_delegate(*gc->getState(), _promise, _invocation++));
}
}
};
// Internal delegation operation for the GC queue
// that reference a user-created Promise object
template<typename T>
struct DelegateOperation2 : public osg::Operation {
Delegate<T> _delegate;
jobs::promise<T>& _promise;
int _invocation;
DelegateOperation2(Delegate<T> d, jobs::promise<T>& promise) :
osg::Operation("GLPipeline", true),
_delegate(d),
_promise(promise),
_invocation(0) { }
void operator()(osg::Object* obj) {
if (getKeep()) {
auto gc = static_cast<osg::GraphicsContext*>(obj);
setKeep(_delegate(*gc->getState(), _promise, _invocation++));
}
}
};
public:
// Launch an operation on this GL Pipeline.
template<typename T>
jobs::future<T> dispatch(Delegate<T> delegate)
{
auto operation = new DelegateOperation<T>(delegate);
jobs::future<T> future = operation->_promise;
if (_dispatcher.valid())
_dispatcher->push(operation);
else
_gc->add(operation);
return future;
}
// Launch an operation on this GL Pipeline, supplying your own Promise.
// Be sure to call getFuture() prior to calling this function.
template<typename T>
void dispatch(Delegate<T> delegate, jobs::promise<T>& promise)
{
auto operation = new DelegateOperation2<T>(delegate, promise);
if (_dispatcher.valid())
_dispatcher->push(operation);
else
_gc->add(operation);
}
private:
osg::ref_ptr<osg::GraphicsContext> _gc;
static std::mutex _mutex;
static std::unordered_map<osg::State*, GLPipeline::Ptr> _lut;
struct Dispatcher : public osg::GraphicsOperation
{
Dispatcher(GLPipeline::Ptr);
void operator()(osg::GraphicsContext*) override;
using OpQ = std::queue<osg::ref_ptr<osg::Operation>>;
OpQ _thisQ;
std::mutex _queue_mutex;
GLPipeline::WeakPtr _pipeline_ref;
osg::ref_ptr<osg::GraphicsContext> _myGC;
void push(osg::Operation*);
};
osg::ref_ptr<Dispatcher> _dispatcher;
};
/**
* Base class for a GPU Compute job that generates or modifies
* a raster image on the GPU and reads it back to the CPU.
* The name of the image in the compute shader is "buf" and it
* is bound to layout location zero (0).
*/
class OSGEARTH_EXPORT ComputeImageSession
{
public:
//! construct a new session
ComputeImageSession();
//! Sets the compute shader program to use
void setProgram(osg::Program* program);
//! Sets the image to submit to the compute shader
void setImage(osg::Image* image);
//! Runs the compute shader and waits for the result
//! to be read back to the CPU.
void execute(osg::State&);
protected:
osg::ref_ptr<osg::Image> _image;
osg::ref_ptr<osg::StateSet> _stateSet;
virtual void renderImplementation(osg::State* state) = 0;
private:
GLuint _pbo;
osg::Texture2D* _tex;
void render(osg::State* state);
void readback(osg::State* state);
};
/**
* Utility to "pre-compile" a node by running it through the ICO
* if one exists in the Options. If there is no ICO, this is a no-op
*/
class OSGEARTH_EXPORT GLObjectsCompiler
{
public:
//! Analyze the node and collect the compilable state
osg::ref_ptr<osgUtil::StateToCompile> collectState(
osg::Node* node) const;
//! Request that the OSG ICO compile GL object state asychnornously.
//! @node Node to compile
//! @state Collected state to compile for the given node
//! @host Object hosting the ICO itself. If this null, or does not host an ICO,
//! the data will not compile and the promise will be resolved immediately.
//! @promise Promise that the ICO should resolve when the compile is complete.
void requestIncrementalCompile(
const osg::ref_ptr<osg::Node>& node,
osgUtil::StateToCompile* state,
const osg::Object* host,
jobs::promise<osg::ref_ptr<osg::Node>> promise) const;
void compileNow(
const osg::ref_ptr<osg::Node>& node,
const osg::Object* host,
osgEarth::Cancelable* progress) const;
jobs::future<osg::ref_ptr<osg::Node>> compileAsync(
const osg::ref_ptr<osg::Node>& node,
const osg::Object* host,
osgEarth::Cancelable* progress) const;
jobs::future<osg::ref_ptr<osg::Node>> compileAsync(
const osg::ref_ptr<osg::Node>& node,
osgUtil::StateToCompile* state,
const osg::Object* host,
osgEarth::Cancelable* progress) const;
static int totalJobs() { return (int)_jobsActive; }
private:
static std::atomic_int _jobsActive;
};
}